Compare commits

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

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

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

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

Fixes #2794

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

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

---------

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

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

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

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

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

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

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

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

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

* Add changelog entry for #2772

---------

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

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

(cherry picked from commit 86cafce196)

* Add changelog entry for #2773

---------

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

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

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

Refs #2621

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

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

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

* Add CHANGELOG entry for #2513 fix

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

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

* Move CHANGELOG entry

* Fix Python 2.7 compatibility

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

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

---------

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

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

Fixes #2693

* Cleanup test and add CHANGELOG

---------

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

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

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

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

Fixes #2550

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

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

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

Fixes #2591

* Update CHANGELOG

* still fall back to the other endianness

---------

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

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

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

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

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

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

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

Fixes #2594

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

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

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

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

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

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

* update changelog

---------

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

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

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

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

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

* Remove partial test implementation

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

---------

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

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

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

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

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

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

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

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

* Do not unresolve zeros
* Do not add undefined symbols

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

* chore: update comment on Apport core path handling

* Update CHANGELOG

---------

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

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

* Update CHANGELOG
2025-01-26 23:18:16 +01:00
Peace-Maker d7a7376eca Merge branch 'stable' into beta 2025-01-15 20:34:56 +01:00
Peace-Maker 9472ec1e93 Fix building docs on stable
Pin to Python 3.12.
2025-01-15 16:49:27 +01:00
peace-maker e4b939dc1a Poll before reading /proc/maps in process.libs() (#2516)
Try to avoid race condition of reading memory maps before libraries are loaded.
2025-01-15 16:49:02 +01:00
Peace-Maker c02190b5d4 Release 4.15.0beta0 2025-01-15 08:48:06 +01:00
Peace-Maker e341e238ca Release 4.14.0 2025-01-15 08:46:49 +01:00
Luca Bancale 7741a187ea Ignore a warning with unused args in asm on NIX (#2508)
* Ignore a warning when compiling with asm

* Add pr to changelog
2025-01-14 22:50:23 +01:00
Findus 4ef3c16d09 ssh: replaced nonexistent key with str(e) in error handling (#2512) 2025-01-06 22:43:21 +01:00
fb2ee19b90 Deprecate direct commandline scripts invocation and exclude nonsense ones (#2364)
* deprecate direct commandline scripts invocation and exclude nonsense ones

* Don't deprecate all commandline tools

---------

Co-authored-by: Arusekk <[email protected]>
Co-authored-by: Peace-Maker <[email protected]>
2024-12-10 22:47:47 +01:00
Jakub Nowakandpeace-maker 74a300db6c Deprecate 'keepends' argument in favor of 'drop' (#2476)
* Replace 'keepends' with 'drop'

Add 'drop' argument in place of 'keepends' for tubes. 'keepends' still
works but raises depreciation warning.

* changelog

* Ignore line numbers in PyLint check

---------

Co-authored-by: peace-maker <[email protected]>
2024-12-10 22:31:29 +01:00
peace-maker 57b9eb9107 Fix loading ELF files without valid .dynamic section (#2502)
* Fix loading ELF files without valid .dynamic section

This allows to load separate debuginfo files and access their symbols.

* Update CHANGELOG
2024-12-10 21:36:33 +01:00
Peace-Maker b2d56fa37c Merge branch 'beta' into dev 2024-12-10 21:17:16 +01:00
Peace-Maker a0ddbf5e16 Tests: Revert timeout changes from gdb tests
There were additional `timeout=X` additions in #2382 which caused tests to fail randomly when a timeout was reached. The tests run through occationally, so it's not an infinite loop. But flakey tests are annoying and I don't see a reason for the timeout additions to the doctests for the core patch of that PR other than failing early during manual tests of new functionality. CI running smoothly is more important than fast failing during development on the gdb module imo.
2024-12-10 20:34:39 +01:00
Peace-Maker 1be8ad7770 Merge branch 'stable' into beta 2024-12-10 20:34:14 +01:00
Peace-Maker 24d217cfbd Docs: Fix link to source for class properties
The property has setters and getters. Link to the getter if it's available. This adds line numbers to the source link for properties.

Fixes #2401
2024-12-09 13:09:10 +01:00
peace-maker ec262d8d01 Update sphinx for Python 3.13 support (#2503)
* Fix sphinx warnings in docstrings

* Update sphinx for Python 3.13 support

Could not import extension sphinx.builders.epub3 (exception: No module named 'imghdr')

`imghdr` was removed in Python 3.13.
2024-12-08 21:16:39 +01:00
Justin ApplegateandPeace-Maker 55ac6e11fc Update documentation for format strings (#2501)
* Update fmtstr.py documentation

Clarified that the `value` in the `writes` dict (for `fmtstr_payload()`) should be a bytestring if the value to be written is shorter than a long

* Update fmtstr.py documentation for FmtStr class

Updated example and definition for `write()` function in `FmtStr` class to include bytes as the value.

* Fix code formatting

---------

Co-authored-by: Peace-Maker <[email protected]>
2024-12-08 19:33:32 +01:00
Leopold 96d98cf192 fix fromsocket to deal with ipv6 socket (#2497)
* fix fromsocket to deal with ipv6 socket

* add remote.fromsocket ipv6 test and update CHANGELOG.md

* skip fromsocket ipv6 test
2024-11-01 13:36:30 +00:00
peace-maker 6f0793eebc Add tube.upload_manually to upload files in chunks (#2410)
* Add `tube.upload_manually`

Upload data in chunks when having a tube connected to a shell.
This is useful when doing kernel or qemu challenges where you can't use the ssh tube's file upload features.

* Add tests and fix corner cases

* Update CHANGELOG

* Fix character escaping in tests

* Fix gzip compression on Python 2
2024-10-28 21:11:36 +01:00
ckxckxandPeace-Maker 51cbdb465d Fix attaching to a gdbserver with tuple gdb.attach(('0.0.0.0',12345)) (#2291)
* Patch for #issues/2290

* Return listening process on `pidof(("0.0.0.0", 1234))`

Instead of returning the process which is connected to port 1234,
return the process which is listening on that port.

* Update CHANGELOG

---------

Co-authored-by: Peace-Maker <[email protected]>
2024-10-28 21:06:56 +01:00
Peace-Maker 61804b169b Merge branch 'beta' into dev 2024-10-24 23:28:53 +02:00
Peace-Maker 04e90bc754 Merge branch 'stable' into beta 2024-10-24 23:28:20 +02:00
Peace-Maker 8926a3a2bc Fix checksec commandline output after Python 3.12 fixes
This reverts #2346 which apparently didn't do what was expected. The logger has to be initialized for the checksec output to show up, so some other file imported * from pwn before too.
With the double registration of subparsers removed in f28d3ebb6c this didn't happen anymore making checksec print nothing.
2024-10-24 23:24:43 +02:00
peace-maker bb7a85c8df Allow to disable caching (#2484)
* Allow to disable caching

By setting `context.cache_dir = None`. Generate default cache dir again by setting `context.cache_dir = True`.

* Disable cache in `asm` caching test

* Update CHANGELOG

* Fix asm cache test
2024-10-24 17:04:50 +02:00
Peace-Maker 3303ea949d Merge branch 'beta' into dev 2024-10-24 16:44:19 +02:00
Peace-Maker cef48f4085 Merge branch 'stable' into beta 2024-10-24 16:32:29 +02:00
Peace-Maker 2ce27dab0d CI: Install coveralls without --break-system-packages
ubuntu-latest doesn't seem to point to Ubuntu 24.04 anymore? Allow this to work on Ubuntu 22.04 too.
2024-10-24 16:20:50 +02:00
Th3Sandpeace-maker fa14663243 libcdb-cli: add --offline-only, refactor unstrip and add fetch parser for download libc-database (#2478)
* Add `return_raw` for `search_by_symbol_offsets`

* Add `--offline-only` and unstrip libc as default behavior

* Add short arg name for `--download-libc`

* Fix bugs

* Add fetch parser

* Fix bugs

* file parse not unstrip default

* Update function docs

* Update CHANGELOG

* Edit dest of `--no-strip`

* Update CHANGELOG

* Unstrip before `return cache`

* Revert `--unstrip` help

* Improve `libcdb fetch` default behavior

* Perf fetch command

* Fix fetch command `-u`

---------

Co-authored-by: peace-maker <[email protected]>
2024-10-24 16:11:07 +02:00
peace-maker d2253110bd Add caching proxy for libcdb debuginfod files to CI (#2487) 2024-10-24 15:59:24 +02:00
peace-maker a3b22b748d Throw error when using sni and setting server_hostname manually in remote (#2482)
* Throw error when using `sni` and setting `server_hostname` manually in `remote`

Prevents silently replacing the `server_hostname` that was provided in `ssl_args`.

Fixes #2425

* Update CHANGELOG
2024-10-24 15:56:47 +02:00
peace-maker 584eccaa33 Only print checksec output of ELF.libc when it was printed for the ELF already (#2483)
* Only print `checksec` output of `ELF.libc` when it was printed for the `ELF` already

* Update CHANGELOG
2024-10-24 15:55:37 +02:00
robbert1978 7dceeddca8 Stop using cmd.exe to keep current directory (#2488)
* Stop using cmd.exe to keep current directory

* Update misc.py

* Update misc.py, deal with cmd.exe

* Update misc.py
2024-10-24 15:51:07 +02:00
Peace-Maker 34da249779 Bump Ubuntu versions in README and bug report template 2024-10-12 18:04:01 +02:00
Peace-Maker c68c00c316 Merge branch 'beta' into dev 2024-10-12 18:01:08 +02:00
Peace-Maker 2e86d8afe2 Merge branch 'stable' into beta 2024-10-12 17:48:59 +02:00
peace-maker f28d3ebb6c Test Python version >= 3.10 in CI and fix tests on Python 3.12 (#2486)
* Bump tested python version to >= 3.10

Some workflows were using 3.8 still. Start testing on 3.12 too.

* Fix installing rpyc in CI on ubuntu 24.04

`ubuntu-latest` now points to 24.04 which requires the --break-system-packages dance.

* Test Python 2 on Ubuntu 22.04

Ubuntu 24.04 dropped the python2.7 packages.

* Test on android-34

* Fix ARM binutils disassembly output test expectation

It used to print "; 0x4" but changed to "@ 0x4" in some version.

* Skip QEMU LD_PREFIX path test

Ubuntu 24.04 seemed to have switched the qemu-user --help output to show `/usr/gnemul/qemu-$ARCH` instead of `/etc/qemu-binfmt`. Ignore the actual path in the test.

* Fix x86 ascii shellcode encoder test

The alphabet was using `\` escape sequences badly.

<stdin>:1: SyntaxWarning: invalid escape sequence '\]'

* Fix mips xor encoder unaligned memory access

qemu throws a SIGBUS error when the shellcode tries to access unaligned memory since some version. Align the "stack" properly.

* Fix util.lists.partition test output on Python 3.12

The __repr__ output of OrderedDict was changed to look like ordinary {} dicts.

* Fix safeeval test on Python 3.12

There is a 151 RESUME opcode in Python 3.12 now.

* Fix registering commandline subparsers multiple times for Python 3.11

Python 3.11 added a sanity check to argparse to prevent registering the same subparser multiple times.

argparse.ArgumentError: argument command: conflicting subparser: cyclic

Avoid importing the command twice.
`python -m pwnlib.commandline.cyclic` failed on Python 3.11 triggered in CI when running the commandline tools while collecting coverage.

* Fix pip cache in CI
2024-10-12 17:48:12 +02:00
Lorenzo Cian cfc021d88e Extract libraries from Docker image (#2479)
* feat: extract libraries from Docker image

* docs: update CHANGELOG.md

* fix: python2.7 compatibility

* address comments

* address linter
2024-10-07 11:46:05 +02:00
Peace-Maker 9f92ed04fc Fix docs of ELF.{libs,maps}
Fixes #2400
2024-10-03 12:17:06 +02:00
k4lizen b08f4b7bc8 redirect kitty kill command stderr to /dev/null (#2472) 2024-10-01 19:37:52 +02:00
Peace-Maker d664bb49d4 Merge branch 'beta' into dev 2024-09-30 00:45:22 +02:00
Peace-Maker c0d6e07f1c Release 4.14.0beta1 2024-09-30 00:34:36 +02:00
peace-maker fa5a288a69 Fix waiting for gdb under WSL2 (#2470)
* Fix returning wrong pid in WSL for run_in_new_terminal

We run cmd.exe which and other Windows processes before going back into WSL. The returned pid would be the one for the ephemeral cmd.exe process instead of the real command we wanted to launch.

I don't know how to properly trace the execution and get the command pid, so scan for newer pids matching the command for a while instead as a workaround.

We wrap the command in a script so psutil.Process.exe() returns the path to the shell instead of the real command. Look at psutil.Process.cmdline() too which contains the real program running right now.

* Make `proc.wait_for_debugger` return the debugger pid

If we fail to get the pid when launching gdb, grab it after tracing the debugger at least. gdb won't be closed when the exploit exits but at least we have the correct pid.

* Fix working directory of run_in_new_terminal in WSL

The process' cwd would be %WINDIR% due to cmd.exe not supporting WSL paths.

* Update CHANGELOG
2024-09-30 00:27:40 +02:00
peace-maker 69ab2054e8 Fix collecting coverage in CI (#2477)
The upload-artifact action v4.4.0 excluded hidden files from uploads by default. We want to upload only hidden `.coverage*` files though. Enable hidden files again using the `include-hidden-files` input.

https://github.com/actions/upload-artifact/releases/tag/v4.4.0
2024-09-30 00:20:59 +02:00
Peace-Maker 907e921043 Merge branch 'beta' into dev 2024-09-30 00:08:31 +02:00
Peace-Maker a3f14df14d Merge branch 'stable' into beta 2024-09-29 23:56:06 +02:00
Peace-Maker 78dd777735 Release 4.13.1 2024-09-29 23:41:04 +02:00
Th3Sandpeace-maker ee63072930 libcdb: improve the search speed of search_by_symbol_offsets (#2413)
* Add `hash_type` for `search_by_symbol_offsets`

* Add docs

* Update CHANGELOG

* Allow search `id` in search_by_hash

* Fix py2.7 test

* Rename `hash_type` to `search_type`

* Rename `TYPES['id']` to `TYPES['libs_id']`

* Rename part `hex_encoded_id` to `search_target`

* Turbofast extract build id

* Fix docs

* Add a map for types key

* Extract proper buildid

* Fix docs

* Fix E0606

* Simplify get `mapped_type`

* Add `search_by_libs_id` to `__all__`

* Modify docs

* Fix py2.7

* Fix reference

---------

Co-authored-by: peace-maker <[email protected]>
2024-09-29 01:53:06 +02:00
Arusekk 824d67d8f4 gdb: fix pid attach (#2474) 2024-09-28 07:32:15 +00:00
k4lizen 785ed9fdda Properly close spawned kitty window (#2471)
* properly close spawned kitty window

* python2 support

* changelog
2024-09-27 09:23:06 +02:00
Jordan ERNST 5c1fb1d11b Fix: GDB 15.1 rpyc detection (#2469)
* Fix: GDB 15 rpyc detection

* Update CHANGELOG.md

* Changelog on the right version

* Remove extra newline in CHANGELOG.md
2024-09-26 22:25:04 +02:00
peace-maker cdfd64f6de Install pwntools on Windows and import it once (#2450)
This is just a basic smoke test for now.
2024-09-26 13:44:52 +02:00
peace-maker 3727c93859 Add ELF.close() to release resources (#2444)
* Add ELF.close() to release resources

`ELFFile.close()` just closes the mmap'd file, but our own wrapper keeps the file handle open.

This is annoying when using a temporary file on Windows since they can't be deleted if there is
a dangling handle. This can be used together with the inherited context manager from ELFFile.

* Update CHANGELOG
2024-09-26 13:41:18 +02:00
Peace-Maker bc453b46c3 Merge branch 'beta' into dev 2024-09-26 01:57:52 +02:00
Peace-Maker f47e417e4e Merge branch 'stable' into beta 2024-09-26 01:29:24 +02:00
peace-maker 7c79c73a5f Backport: Fix typo for fallback to default number of console colors on windows (#2467)
* Fix typo for fallback to default number of console colors on windows

* Update CHANGELOG
2024-09-25 23:20:04 +00:00
peace-maker 3e9849f0ca Fix PLT emulation with Unicorn 2.1.0 (#2466)
* Fix PLT emulation with Unicorn 2.1.0

You cannot add multiple hooks at once anymore, so add them individually - still to the same callback.

https://github.com/unicorn-engine/unicorn/blob/f164769a9a973a3e981f279ed7aa90459ae68545/bindings/python/unicorn/unicorn_py3/unicorn.py#L970-L976

* Update CHANGELOG

* Switch to PyPi Simple API for update checks

Fault: <Fault -32500: 'RuntimeError: PyPI no longer supports the XMLRPC package_releases method. Use JSON or Simple API instead. See https://github.com/pypi/warehouse/issues/16642 and https://warehouse.pypa.io/api-reference/xml-rpc.html#deprecated-methods for more information.'>

* Update CHANGELOG

* Disable ssh.user_shstk test

Github Actions Runners have userland shadow stack enabled now apparently.
Don't test for this system state.
2024-09-25 23:01:08 +00:00
peace-maker 149ebe0c47 Fix parsing the PLT on Windows (#2445)
* Fix parsing the PLT on Windows

The Unicorn Engine 1GB workaround doesn't work since `mmap` has different semantics on Windows.

* Update CHANGELOG

* Catch ImportError instead

* Remove unused sys import
2024-09-25 23:14:58 +02:00
tesuji d7817a7eac Nicely handle non ELF files in checksec (#2457) 2024-09-11 11:22:13 +02:00
patryk4815 df620d7449 darwin: generate syscalls sdk 15.1 (#2448) 2024-08-25 12:59:14 +02:00
peace-maker 67678c2e1c Cache output of asm() (#2358)
* Cache output of `asm()`

To speed up repeated runs of an exploit, cache the assembled output.

Use a sha1 hash of the shellcode as well as relevant context values
like `context.arch` and `context.bits` to see if the exact same
shellcode was assembled for the same context before.

Fixes #2312

* Return path to cache file if `not extract`

* Update CHANGELOG

* Create temporary copy of cached file

* Add debug log about using the cache

* Include full assembler and linker commandlines in hash

This should catch any changes across pwntools updates and system environment changes.

* Include pwntools version in hash
2024-08-12 20:59:22 +02:00
Peace-Maker 734cb3bf82 Begin working on 4.15.0 2024-08-12 19:45:10 +02:00
Peace-Maker adaa6abc08 Release 4.14.0beta0 2024-08-12 19:27:23 +02:00
Peace-Maker dbb034a867 Pin colored_traceback < 0.4 for Python 2 2024-08-12 18:39:04 +02:00
Peace-Maker 997e51c709 Release 4.13.0 2024-08-12 18:33:09 +02:00
Peace-Maker 3e21808da9 Cleanup CHANGELOG order 2024-08-12 18:13:05 +02:00
k4lizen 405357c075 Add resolution_addr parameter to Ret2dlresolvePayload (#2436)
* add documentation for data_addr and resolution_addr

* add resolution_addr

* changelog

* changelog^2
2024-08-09 12:33:38 +02:00
peace-maker 248f4510c6 Support asm/disasm on Windows (#2437)
* Support asm/disasm on Windows

Find binutils toolchains like https://gnutoolchains.com/ if they exist in the PATH.

* Update CHANGELOG

* Update binutils install docs

* cpp reads from stdin when no infile is given

No need for a tempfile here.
2024-08-08 14:13:37 +02:00
Peace-Maker de67c86432 Fix tty_escape on Python 2 2024-08-07 23:52:33 +02:00
Peace-Maker 101f9bc76b Add documentation for tty_escape 2024-08-07 23:27:05 +02:00
Peace-Maker 4879590a37 Fix typo for fallback to default number of console colors on windows 2024-08-07 00:14:54 +02:00
dependabot[bot] 7194ced044 Bump docker/build-push-action from 5 to 6 (#2420) 2024-08-06 14:17:36 +00:00
k4lizenandpeace-maker 6a9deffbfb Add functions for retrieving process mappings (#2371)
* add to process mapping function, and stack, heap, vdso, vvar, libc _mapping properties

* better libc string mapping detection #2370

* return all mappings instead of just the first one

* add single/multiple option to every _mapping function

* undo @property tags

* added function doc comments

* added elf_mapping

* add get_mapping and refactor helpers to use it

* better doc

* add _location functions

* _location_from_mappings for libc and musl locations

* _location_from_mappings doc

* change comment

* fix executable -> path

* change comment

* python2.7 support

* changelog

* add address_mapping()

* get_mapping comment typo

* mention the path is an exact match

* add references to wrapped functions

* add missing 'the's, make sentences one line, fix 'exact' path description in get_mapping_location

* add private and shared fields to permissions object

* maps doctest

* get_mapping doctest

* stack_mapping doctest

* heap_mapping doctest

* improve stack_mapping and heap_mapping with address example

* vvar and vdso _mapping doctest

* libc_mapping doctest

* elf_mapping doctest

* _location_from_mappings doctest

* get_mapping_location doctest

* heap and stack _location doctest

* vdso vvar _location doctest

* improved vdso and vvar doctest

* elf_location doctest

* libc_location doctest

* address_mapping bugfix

* address_mapping doctest

* fix meow check

* fix len(mappings) check

* actually fix meow check

* why dont docs support format string?

* removed _location functions

* removed the rest of the _location related functions

* added lib_size()

* add lib_size() example

* replace f'' with correct syntax

* remove _location() from other doctests

* cleanup == in doctest

* change " to ' in doctests

---------

Co-authored-by: peace-maker <[email protected]>
2024-08-06 11:10:10 +02:00
mephi42 26e72f2b3e gdb.debug: avoid 2s timeout if possible (#2435)
On Ubuntu 22.04 gdb.debug() takes at least 2 seconds. This is because
it prints only "Remote debugging from host 127.0.0.1, port 33398", but
the code expects 2 lines.

It's very unclear what the second line is supposed to be; the only lead
is "* Handle extra newline printed by gdb" in commit 338fbeba ("Improve
pwnup template, gdbserver detection (#1148)"), but I could not trace it
back to the GDB source code, both historic and modern. Perhaps, there
is a non-upstreamed patch in some distro that introduces it.

It should still be safe to skip waiting for the second line if the
first one already starts with "Remote debugging ...", so do it.
2024-08-05 13:05:25 +02:00
00663aa1bf Add port, gdb_args, and gdbserver_args to gdb.debug() (#2382)
* added two arguments to gdb.debug:
+ port: specifies the port that should be used by the gdbserver (this is useful for alpine)
+ gdb_args: allows forwarding arguments to the gdb binary spawned by gdb.attach() (just a passthrough)

aslo downgraded "GDB Python API is supported only for local processes" to a warning, because it seems to work on some systems.

* added changelog entry and fixed python2.7 compatibility

* also added optional gdbserver_args parameter to gdb.debug()

* implemented requested fixes for #2382:
+ gdb.debug port is now also used for qemu
+ GDB Python API is now tested for tubes.process.process a warning for ssh.process and an error for everything else
+ updated docs to use mention that gdbserver ports are randomized by default
+ now using gdbserver_port to check if the correct port was set
+ fixed CHANGELOG.md structure

* implemented requested fixes for #2382:
+ gdb.debug port is now also used for qemu
+ GDB Python API is now tested for tubes.process.process a warning for ssh.process and an error for everything else
+ updated docs to use mention that gdbserver ports are randomized by default
+ now using gdbserver_port to check if the correct port was set
+ fixed CHANGELOG.md structure
+ added timeouts for recvline() gdb tests
+ also run gdb api tests for ssh runner

---------

Co-authored-by: gfelber <@users.noreply.github.com>
Co-authored-by: peace-maker <[email protected]>
2024-07-23 13:53:35 +02:00
Peace-Maker 18c8bfba3d Pin colored_traceback < 0.4 for Python 2 2024-07-23 13:37:26 +02:00
Jakub Nowak 4284c3fcdf Improve remote() documentation (#2427)
* Improve remote() documentation

* changelog
2024-07-09 09:23:46 +02:00
Arusekk a366e91038 Add TTY escape function for file transfer (#2422) 2024-07-02 07:04:14 +02:00
CocoaandArusekk 736cf9d112 feat: multi shellcraft cmd (#2398)
* added support for multiple shellcraft commands

* updated CHANGELOG.md

* fix `pwn shellcraft -l shellcode`

* fix `get_template`

* strip strings after spliting by the delimiter

* fix `pwn shellcraft --show shellcode`

* added some tests in ci

* better handling for splitting by delimiter

---------

Co-authored-by: Arusekk <[email protected]>
2024-06-23 16:51:23 +02:00
peace-maker 4dc8d60b9e Add "none" ssh authentication method (#2405)
* Add "none" ssh authentication method

Allow to "disable" ssh authentication if the remote server has it enabled.

Fixes #2380

* Add CHANGELOG
2024-06-17 10:03:55 +02:00
Dvorhack d5fffb6769 Shellcode socket IPV6 (#2415) 2024-06-15 18:56:45 +02:00
Th3Sandpeace-maker 51e8eb09ee libcdb: add offline_only to search_by_symbol_offsets (#2388)
* Add `offline_only` for `search_by_symbol_offsets`

* Fix bug

* Update CHANGELOG

* Remove redundant code

* Update

---------

Co-authored-by: peace-maker <[email protected]>
2024-05-23 16:57:19 +02:00
Sanjit Kumarandpeace-maker e92a30bbf7 Convert bytes-like object to string in apport_corefile() method (#2387)
* converting bytes-like object to string

* updated changelog

---------

Co-authored-by: peace-maker <[email protected]>
2024-05-12 13:26:08 +02:00
peace-maker fcec11784a Fix access of undefined ssh._tried_sftp in raw mode (#2396)
* Fix access of undefined `ssh._tried_sftp` in `raw` mode

The attribute was never set when setting `ssh(raw=True)` which caused the code to run `ssh.__getattr__` which raised an AttributeError due to `_tried_sftp` starting with an `_` on Python 3.12.

* Document all `ssh` attributes
2024-05-12 11:31:46 +02:00
qux-bbb fe560c9835 Add sudo for apt-get (#2395) 2024-04-24 12:46:07 +02:00
Peace-Maker 27366fdeb8 Merge branch 'beta' into dev 2024-04-22 00:05:28 +02:00
Peace-Maker 5f79e1dc51 ci: Install rpyc from pip instead of apt
The apt package appears outdated and incompatible with the latest version on pypi.

>>> ValueError: invalid message type: 18
2024-04-21 23:44:04 +02:00
Peace-Maker af6c8c8878 gdb.debug: gdbserver --wrapper requires startup-with-shell
Only add it when we don't want to manipulate the environment or argv[0].
This will cause the problem with fish shell to reappear in those cases, but
it's another step for compatibility at least.

#2378
2024-04-21 23:31:29 +02:00
Peace-Maker 55390f1bf6 Merge branch 'stable' into beta 2024-04-21 22:46:36 +02:00
peace-maker f2f55f3e1c Return buffered data on first EOF in tube.readline() (#2376)
* Return buffered data on first EOF in tube.readline()

When there is still data available in the tube buffer when an EOFError occurs in
`tube.recvline()`, return that data even though it doesn't contain a newline.
The next time `tube.recvline()` is called afterwards will raise EOFError normally.

This behavior is in line with the GNU readline implementation and avoids
loss of data. It allows `tube.stream()` to print everything that's received before
the receiving end terminates.

A new warning is logged when data is returned due to an EOF informing
about the lack of the trailing newline character.

Fixes #2366

* Update CHANGELOG

* Add context.throw_eof_on_incomplete_line

Allow to control the behavior of `tube.recvline` and
be able to suppress the new warning.

* Cleanup docs
2024-04-21 22:37:46 +02:00
peace-maker db98e5edfb Don't go through a shell in gdb.debug (#2378)
* Don't go though a shell in `gdb.debug`

gdbserver starts a shell and runs the target process through it.
This behavior was added in gdbserver 8.0 together with the
commandline flag --no-startup-with-shell to disable it.

* Update CHANGELOG
2024-04-21 20:33:34 +00:00
peace-maker 60072487f1 Fix displaying bright color variation in terminal output (#2373)
* Recompute text.has_bright when num_colors changes

The number of simultaneous colors in the current terminal are queried
later during initialization and the text.num_colors attribute is updated accordingly.
The text.has_bright attribute wasn't updated when that happened and always
remained set to False. Calculate those properties based on num_colors dynamically.

* Update CHANGELOG

* Enable bright colors on Windows too

Only get "colors" on Windows to avoid #1201 again.

* Fix support for old terminals on windows
2024-04-21 22:24:35 +02:00
7ac5a34a59 Change from pop to keys for reporting proper error message (#2391)
* Change from pop to keys for reporting proper error message

* update changelog

* add doctest for xor

* Add newline to run xor doctests

* Switch to `dict.popitem()`

---------

Co-authored-by: marinelay <[email protected]>
Co-authored-by: Peace-Maker <[email protected]>
2024-04-21 22:22:46 +02:00
eec3321ae9 Fix passing bytes to context.log_file and crc.BitPolynom (#2389)
* Fix incosistent usage of bytes/str

* update changelog

* Update CHANGELOG

---------

Co-authored-by: marinelay <[email protected]>
Co-authored-by: Peace-Maker <[email protected]>
2024-04-21 22:18:28 +02:00
Lewis Watson d2a02a7b37 Updated Mac OS Install Documentation (#2392)
* Added Missing MacOS Requirements

* Updated Binutils Install Docs

Due to security reasons Homebrew has removed direct formula references, this is the workaround.

* Updated changelog for #2392

* reverting changelog
2024-04-21 22:14:55 +02:00
peace-maker c1ce1897df Fix duplicate definition of ssh.sftp (#2394) 2024-04-21 22:13:56 +02:00
peace-maker 5981c7290d Change newline when setting context.os to "windows" (#2330)
* Change newline when setting `context.os`

Windows uses `b'\r\n'` for newlines while unix uses `b'\n'`. Change the expected newline to `b'\r\n'` when setting `context.os = "windows"` automatically for convenience.

* Update CHANGELOG

* Fix other tests setting os
2024-03-29 17:52:34 +01:00
peace-makerandLevitatingLion cb540857cc Add basic RISCV64 shellcraft support (#2322)
* Add basic RISCV64 shellcraft support

The `mov` template isn't 100% null-byte and newline free
for all inputs. Certain larger values are just emitted using
the `li` pseudo-instruction by the assembler which might
contain null-bytes.

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

* Generate RISCV syscall constants from musl-libc

diet-libc doesn't support RISCV, so borrow from musl.
Need to consider their license.

* Update CHANGELOG

* Fix CHANGELOG

---------

Co-authored-by: LevitatingLion <[email protected]>
2024-03-29 17:28:50 +01:00
peace-maker 8ba1bdf0b6 Add basic support to debug processes on Windows (#2327)
* Add basic support to debug processes on Windows

Currently only `windbg.debug()` and `windbg.attach()` are implemented,
which open a WinDbg instance and attach to the process.

* Update CHANGELOG

* Cleanup CheckRemoteDebuggerPresent call

Only require PROCESS_QUERY_INFORMATION access and check for errors when opening the process.

* process.close: Move closing of std fds after kill

Windows processes would block on fd.close() when the main thread is suspended.
2024-03-29 16:04:56 +01:00
Th3S 4ac98cd11a Add offline parameter for libcdb.search_by_hash series function (#2360)
* Add offline parameter

* Add CHANGELOG

* Rename `offline` to `offline_only`
2024-03-29 15:27:38 +01:00
Swastik Sarkar 75cc3c3d5c libcdb.unstrip_libc: debug symbols are fetched only if not present (#2374)
* libcdb.unstrip_libc: debug symbols are fetched only if not present

* Usage of `ELF.debuginfo` to check if debug symbols exist

closes #2324

---------

Signed-off-by: sswastik02 <[email protected]>

* Add CHANGELOG

Signed-off-by: sswastik02 <[email protected]>

---------

Signed-off-by: sswastik02 <[email protected]>
2024-03-24 13:19:31 +01:00
Peace-Maker a2acdf9bd9 Pin pyelftools<0.30 for Python 2 in docs/requirements.txt
Docs only installs fail under python2 due to trying to install pyelftools>=0.30 which dropped py2 support.

Fixes #2372
2024-03-22 20:32:57 +01:00
Pratik Raj 14c0d73f89 feat : use --no-cache-dir flag to pip in dockerfiles to save space (#2357)
using the "--no-cache-dir" flag in pip install, make sure downloaded packages by pip don't cache on the system. This is a best practice that makes sure to fetch from a repo instead of using a local cached one. Further, in the case of Docker Containers, by restricting caching, we can reduce image size. In terms of stats, it depends upon the number of python packages multiplied by their respective size. e.g for heavy packages with a lot of dependencies it reduces a lot by don't cache pip packages.

Further, more detailed information can be found at

https://medium.com/sciforce/strategies-of-docker-images-optimization-2ca9cc5719b6

Signed-off-by: Pratik Raj <[email protected]>
2024-03-07 13:38:36 +01:00
Th3S 3b4b261b5a Add local libc database provider for libcdb (#2356)
* Add local libc database provider for libcdb

* Remove unnecessary assert

* Add docstring for local_libcdb

* Suppress warning output while `context.libdb` sets default

* Testing the local system's libc first

* Set falsely `context.lcoal_libcdb` to turn off local libc-database integration

* Fix docstring

* Make path check in validator

* Fix doctests

* Add CHANGELOG
2024-03-01 13:01:09 +01:00
Peace-Maker 000c31c155 ci: Install rpyc from pip instead of apt
The apt package appears outdated and incompatible with the latest version on pypi.

>>> ValueError: invalid message type: 18
2024-02-25 14:39:51 +01:00
Peace-Maker 197f00806e Merge branch 'beta' into dev 2024-02-22 23:08:02 +01:00
Peace-Maker a06d378e8c Merge branch 'stable' into beta 2024-02-22 23:05:43 +01:00
Peace-Maker e1770a3045 Begin working on 4.14.0 2024-02-22 22:49:41 +01:00
Peace-Maker c983f1c232 Release 4.13.0beta0 2024-02-22 22:43:33 +01:00
Peace-Maker f046fdd93e Release 4.12.0 2024-02-22 22:15:10 +01:00
Peace-Maker eb7e2c3568 Merge branch 'beta' into dev 2024-02-22 22:01:37 +01:00
Peace-Maker 4c0e08c2c8 Merge branch 'stable' into beta 2024-02-22 21:59:14 +01:00
peace-maker 2b7ec5e77a Fix gdb and other doctests (#2355)
* Fix gdb.debug doctests

* Accept bytes in ssh.which

But still return a `str` return for backwards-compatibility.

* CI: Make rpyc visible in gdb's python

* Fix not running all doctests

They appear to have to be a free standing block of `>>>` with an empty line above.

* Remove unnecessary text type check in ssh.which
2024-02-22 18:30:23 +01:00
peace-maker c17f835b3e pwn template: Don't search for challenge binary when already given (#2354) 2024-02-21 15:21:57 +01:00
eriklefflerandPeace-Maker 1b47ae1c20 Add RETURN_CONST as an allowed _const_code in safeeval (#2352)
* better error message when process creation fails in ssh.process

* add RETURN_CONST as an allowed _const_code in safeeval

* fix lint

* add to changelog

* fix changelog

* add couldn't find python warning

* Deduplicate error message code

---------

Co-authored-by: Peace-Maker <[email protected]>
2024-02-20 18:28:20 +00:00
peace-maker eb0043afc6 Enable UI tests on dev branch again (#2353)
They might be more stable now after the term changes.
Ref #1647
2024-02-19 12:25:20 +01:00
d9b3e17b66 gdb.debug: exe parameter now respected, allow empty argv (#2233)
* Bugfix gdb.debug: exe parameter now respected

This commit now properly supports the exe parameter in
`pwnlib/gdb.py:debug()`, allowing a different argv[0] than the
executable.

It achieves this by leveraging the gdbsever`--wrapper` argument
with a python script that calls execve with the specified args.

* Backintegration for python2

* Update Changelog.md

* pwnlib gdb.py, try to pass these tests...

* Encode script in ssh.process(run=False) for tests

* Re-trigger Pull request tests

* gdb.py: easier script if argv[0] == exe

* gdb.py: Add test case for LD_Preload

* Add check that "=" not in misc.normalize_argv_env

This check checks prevents the use of "=" in the
key of an environment variable, which is generally
impossible.

* gdb.py Correct handling of LD_ env-variables

* Update pwnlib/gdb.py

Co-authored-by: peace-maker <[email protected]>

* gdb.py address comments

1. explicit ctypes.CDLL('libc.so.6'), handle execve failing
2. consistent namedTempFile
3. drop packing._encode() since it's done earlier
4. testcases solve argv-args confusion

* gdb.py: Fix Namedtempfile-prefix + Bugfix

* Restore prefix for gdbscript

* Unify execve wrapper script under one function

* gdb.py, Remove leftover script

* Fix logging scope and ignore_environ argument

---------

Co-authored-by: Youheng Lü <[email protected]>
Co-authored-by: peace-maker <[email protected]>
Co-authored-by: peace-maker <[email protected]>
2024-02-17 00:47:56 +01:00
peace-maker 2114692513 Fix readline omitting a trailing \n (#2349)
* Fix readline omitting a trailing \n

This is a regression from #2129 which was based on a bogus test which mixed `input` and `readline` calls.

Only `input` (and `raw_input`) are documented to strip the newline, but `readline` itself should include it.

Fixes #2342

* Update CHANGELOG
2024-02-16 23:43:19 +01:00
Peace-Maker b5e4ff137a Android: Install emulator in CI and switch to x86_64 platform 2024-02-16 23:11:12 +01:00
Peace-Maker c049837c54 Update docs for running doctests locally 2024-02-14 21:08:06 +01:00
Disconnect3dandpeace-maker 9e4b11adb7 Fix Unicorn Engine 1GB limit that calls exit: raise OSError instead (Fixes #2343) (#2347)
* Fix Unicorn Engine 1GB limit exit(): raise OSError instead (Fixes #2343)

This commit fixes #2343: an issue where `pwn checksec <binary>` would fail with a bogus error of:

```
$ pwn checksec /root/x/bin/main
Could not allocate dynamic translator buffer
```

This actually comes from Unicorn Engine which tries to allocate a 1GB RWX mapping:
```
root@pwndbg:~# strace -e openat,mmap pwn checksec /root/x/bin/main 2>&1 | tail -n 10
openat(AT_FDCWD, "/usr/lib/python3/dist-packages/mpmath-0.0.0.egg-info/PKG-INFO", O_RDONLY|O_CLOEXEC) = 7
openat(AT_FDCWD, "/usr/local/lib/python3.10/dist-packages/unicorn/lib/libunicorn.so.2", O_RDONLY|O_CLOEXEC) = 7
mmap(NULL, 22447520, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 7, 0) = 0x7f2604f9d000
mmap(0x7f2605339000, 13496320, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x39c000) = 0x7f2605339000
mmap(0x7f2606018000, 3039232, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x107b000) = 0x7f2606018000
mmap(0x7f26062fe000, 1601536, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 7, 0x1360000) = 0x7f26062fe000
mmap(0x7f2606485000, 525728, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f2606485000
mmap(NULL, 1073741824, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = -1 ENOMEM (Cannot allocate memory)
Could not allocate dynamic translator buffer
+++ exited with 1 +++
```

...and if it fails, it calls `exit()`. This can be seen in Unicorn Engine code: https://github.com/unicorn-engine/unicorn/blob/56f3bdedb42d26bee1532cc01baf5eaf44a9aa23/qemu/accel/tcg/translate-all.c#L960-L963

This issue has been reported to Unicorn Engine in https://github.com/unicorn-engine/unicorn/issues/1766 but since it hasn't been fixed, this commit applies a workaround for it.

* CI: add test for pwn checksec with 500MB limit for UE 1GB limit

* Add changelog entry

* Update .github/workflows/ci.yml

---------

Co-authored-by: peace-maker <[email protected]>
2024-02-09 16:39:12 +01:00
47dfc57922 Add a flatten argument to ssh.libs (#2268)
* Add a `flatten` argument to `ssh.libs`

This option makes us able to avoid getting the file tree of the remote
server and just downloads the desired files in the output folder

* Update changelog

* Add a warning in ssh.libs for duplicate filenames

* Add documentation in ssh.libs for duplicate filenames fallback

* [ssh.libs] Fix flatten's duplicate check

Fix this: https://github.com/Gallopsled/pwntools/pull/2268#discussion_r1325404026

* [ssh.libs] Add better warning for flatten's duplicates

Fix this: https://github.com/Gallopsled/pwntools/pull/2268#discussion_r1325405234

* [ssh.libs] Add doctests

This commit adds:

- The `no_duplicate` binary that depends on
    - `a/lib.so`
    - `b/lib2.so`
- The `duplicate` binary that depends on
    - `a/lib.so`
    - `b/lib.so`

Then, the doctest tries to pull the libs from both binaries with
different `flatten` values.

* [ssh.libs] Add `remote` arg to documentation

* [ssh.libs] Remove fallback to unflattened when flatten fails

* [ssh.libs] Fix doctests

* [ssh.libs] Import pwnlib.data.elf.ssh_libs in pwnlib.data.elf

* [ssh.libs] Fix corrupted binaries

* [ssh.libs] We actually need to set the cwd since libs paths are relative

* [ssh.libs] Fix permission issues for CI

* [ssh.libs] Fix doctest

* Remove doctest attempts

---------

Co-authored-by: peace-maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
Co-authored-by: Peace-Maker <[email protected]>
2024-02-09 16:06:26 +01:00
Peace-Maker f5a119ffe5 Fix checksec commandline wrapper invocation 2024-02-09 10:56:14 +01:00
Peace-Maker 545d8f748a Remove f-string for Python 2 2024-02-08 10:41:47 +01:00
Stefan StefanovandBl4ckC4t a2b6771fe6 Improved DynELF address resolutions and symbol lookups (#2335)
* Optimized dynelf when using an ELF object

* Fixed minor bugs, added a new way to lookup symbols

Improved _rel_lookup and added Elf64_Rel datatype

Added support for _rel_lookup in x86 binaries

* Spelling fix

* Converted prints to python2 style

    + changelog entry

* Fixed a bug with real leaker not being resotred

---------

Co-authored-by: Bl4ckC4t <[email protected]>
2024-02-08 10:24:51 +01:00
teddavandpeace-maker 73f2a31d8b fix: split current iterm window during gdb.debug process (#2341)
* fix: split current iterm window during gdb.debug process

* add change to changelog

* escape cmd before writing osascript file

* use the previously sanitized command to run the osascript

---------

Co-authored-by: peace-maker <[email protected]>
2024-02-08 10:22:15 +01:00
Disconnect3d 604b98c035 Fix pwn constgrep when it matches a non-constant type (Fixes #2344) (#2345)
* Fix pwn constgrep when it matches a non-constant type

This commit fixes the following issue:

```
root@pwndbg:~# pwn constgrep a
Traceback (most recent call last):
  File "/usr/local/bin/pwn", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.10/dist-packages/pwnlib/commandline/main.py", line 58, in main
    commands[args.command](args)
  File "/usr/local/lib/python3.10/dist-packages/pwnlib/commandline/constgrep.py", line 110, in main
    for _, k in sorted(out):
TypeError: '<' not supported between instances of 'Constant' and 'type'
```

Note that it was caused because of the following type object being matched and fetched from the module object:

```
ipdb> out[25:27]
[(Constant('CS', 0xd), 'CS'), (<class 'pwnlib.constants.constant.Constant'>, 'Constant')]
ipdb> sorted(out[24:27])
*** TypeError: '<' not supported between instances of 'type' and 'Constant'
```

* Add test for `pwn constgrep C` to the CI

* Add changelog entry
2024-02-08 10:17:47 +01:00
Disconnect3d 3fbd21a6e4 checksec.py: import ELF instead of * (#2346) 2024-02-08 10:15:29 +01:00
Arusekk b2cba039d4 Merge pull request #2161 from patryk4815/basic-darwin
basic darwin support for shellcrafter
2024-02-04 16:34:56 +01:00
Arusekk 10d1c5a1ab Merge branch 'dev' into basic-darwin 2024-02-04 16:24:02 +01:00
psondej 954d54665f #2161 Add basic support for darwin shellcraft/asm/disasm/run_shellcode/run_assembly 2024-02-04 16:22:02 +01:00
psondej e78fb02b9e -macosx_version_min has been renamed to -macos_version_min 2024-02-04 03:15:02 +01:00
peace-maker bf7abc054a Add ELF.stripped and ELF.debuginfo properties (#2336)
* Add `ELF.stripped` and `ELF.debuginfo`

Show status in checksec output too.

* Update CHANGELOG

* Show stripped and debuginfo status in red in checksec
2024-01-30 12:40:28 +01:00
peace-maker 27bc31e978 Match against local system libc first in libcdb (#2325)
* Match against local system libc first in libcdb

Don't do any requests if the libc currently in use on the system
running the exploit matches already. This is a small short circuit
optimization when the remote target uses the same libc as the
local one.

This looks at the libc loaded by the local shell binary. This appears
more dynamic than hardcoding library paths.

Refs #983

* Update CHANGELOG

* Handle missing SHELL envvar

* Fix hash lookup
2024-01-30 12:20:20 +01:00
peace-maker 993f590ac2 Retry failed lookups after one week in libcdb (#2323)
* Retry failed lookups after one week in libcdb

The libc databases might be updated to include the searched version,
so a request that failed once might work in the future.

Refs #983

* Update CHANGELOG
2024-01-30 11:29:31 +01:00
peace-maker e466f5c2a6 Make sure TERM_PROGRAM points to a valid program (#2329)
Visual Studio Code sets the $TERM_PROGRAM environment variable to `"vscode"` in its terminal pane,
but doesn't support opening new panes from the command line and there is no "vscode" binary.

Make sure the target binary exists before trying to launch it.
2024-01-30 11:08:57 +01:00
Nils1729 171d9dfb39 Fix: Allow setting attributes on gdb Breakpoints (#2339)
* fix: Allow setting attributes on gdb Breakpoints

* Update CHANGELOG

* Add __getattr__ accidentally deleted from FinishBreakpoint
2024-01-29 17:20:31 +01:00
teddav cb3fda4a7e fix: follow symlink for libs() on ssh connection (#2338) 2024-01-23 00:24:58 +01:00
dependabot[bot] 4e11ee7d99 Bump actions/cache from 3 to 4 (#2337) 2024-01-22 12:17:27 +00:00
Arusekk f6bee59b46 elf.maps: Less nullbytes + update hardcoded shellcodes 2024-01-22 13:06:10 +01:00
peace-maker 6dc1c2f72f Explicitly define p64/u64 functions for IDE support (#2189)
* Explicitly define p64/u64 functions for IDE support

Instead of dynamically generating the packing and unpacking helper functions using `setattr(module, name, routine)` at runtime, unroll the loop and explicitly declare all 8 helpers.

This allows IDEs to know about the function statically without running the code.

Fixes  #1657

* Update CHANGELOG

* Fix u16 comment
2024-01-21 18:22:09 +01:00
Arusekk 14309dc111 elf.maps: Update hardcoded shellcodes 2024-01-21 12:14:53 +01:00
Arusekk b5253fa510 shellcraft.amd64.mov: fix logic once again 2024-01-21 05:19:41 +01:00
Arusekk ea22cc8cef shellcraft.amd64.mov: fix logic 2024-01-20 22:39:34 +01:00
peace-maker d427844465 Lookup using $PATHEXT file extensions in which on Windows (#2328)
* Lookup process executable using PATHEXT file extensions

Windows uses some file extensions to determine which file to run when given
a non-existing file.

This allows to run `calc` instead of `calc.exe`.

* Try PATHEXT in `which` directly

* Update CHANGELOG
2024-01-18 00:57:43 +01:00
Peace-Maker 7b78334ac4 Android: Install emulator in CI 2024-01-17 21:57:38 +01:00
vsylandPeace-Maker 5805f5e9d7 Speed up disasm with color (#2334)
* Speed up disasm with color

* Update CHANGELOG

---------

Co-authored-by: Peace-Maker <[email protected]>
2024-01-17 21:44:52 +01:00
vsylandArusekk 000ca1f637 Fix unhex for odd length bytes (#2333)
* Fix unhex for odd length bytes

* Update pwnlib/util/fiddling.py

---------

Co-authored-by: Arusekk <[email protected]>
2024-01-13 17:47:51 +01:00
Arusekk 0339b46450 Update pwnlib/elf/corefile.py 2024-01-03 21:26:12 +01:00
Arusekk ad6ef0e372 Update pwnlib/shellcraft/templates/aarch64/mov.asm 2024-01-03 21:25:42 +01:00
psondej a814be2604 refactor aarch64 pushstr_array, use "xzr" reg 2024-01-03 19:40:05 +01:00
psondej bc6228e8bc fix freebsd amd64 syscall arguments 2024-01-03 18:56:14 +01:00
psondej 976f7cc175 revert "powerpc64" typo 2024-01-03 18:37:38 +01:00
psondej 15748d082d fix aarch64: mov, pushstr, pushstr_array
fixes:
- https://github.com/Gallopsled/pwntools/issues/2160
- https://github.com/Gallopsled/pwntools/issues/2284
2024-01-03 18:34:26 +01:00
psondej 8b56039d88 add auto generated files for darwin (make generate_darwin) 2024-01-03 18:28:05 +01:00
psondej 5d32b24998 add darwin shellcrafter templates 2024-01-03 18:27:33 +01:00
psondej 43aef76a37 initial darwin support 2024-01-03 18:26:36 +01:00
dependabot[bot]andArusekk d289e177fe Bump actions/upload-artifact from 3 to 4 (#2318)
* Bump actions/upload-artifact from 3 to 4

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>

* ci: use separate coverage artifacts

---------

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Arusekk <[email protected]>
2024-01-03 12:23:39 +01:00
peace-maker c4a3d349d6 Add support to start a process on Windows (#2310)
* Add support to start a process on Windows

This is heavily inspired by https://github.com/masthoon/pwintools

Allows to use the `process` tube on a Windows host running local
processes. I've tried to keep the changes at a minimum. Windows doesn't
support non-blocking reads, so I've opted to handle the reading in a
separate thread to simulate the non-blocking check.

* Fix `misc.which()` on Windows

`os.getuid()` is only supported on unix.

* Support [read|write]mem, libs and cwd

* Update CHANGELOG

* Don't cache loaded modules

* Wait output on stdout in `process.can_recv_raw`

Instead of handling the timeout for reading poorly in `process.recv_raw`,
wait for any bytes to be available in `process.can_recv_raw` like on linux.

This prevents a 100% spin of one core in `tube.interactive()` since it
expects the thread to block for `timeout` seconds while we would return
immediately previously.

* Remove PythonForWindows dependency

This loses the `readmem` and `writemem` feature, but that's not
necessary for this basic process startup support.

* Fix cyclic imports

* Ignore errors in reading thread

This could cause a deadlock / fastfail on shutdown when stdout is closed, thus read(1) fails
and the exception is tried to be printed to the console while stderr is in the process of getting
closed.
2024-01-02 21:11:57 +01:00
Lennard Hofmann 3bb756db45 Add timeout to gdbserver startup (#2321) 2024-01-02 15:40:09 +01:00
Florian Kothmeierandpeace-maker cd0c34a6ed Make pwn template always set context.binary (#2279)
* Make pwn template always set context.binary

* Update CHANGELOG.md

* Allow args.EXE override in all templates

* Fall back to old behavior for remote only challenges

---------

Co-authored-by: peace-maker <[email protected]>
2023-12-30 19:35:29 +01:00
Arusekk 5409174aed elf/corefile: Clean up pyelftools workarounds (#2319) 2023-12-30 00:01:07 +01:00
c7649c95e2 Make sure winexec shellcode is 16 byte aligned and add nCmdShow option (#2308)
* Make sure winexec is 16 byte aligned and add nCmdShow option

* fix typo and add changelog

* Update pwnlib/shellcraft/templates/amd64/windows/winexec.asm

Co-authored-by: peace-maker <[email protected]>

* tidied up winexec 16-byte alignment

* fix stack alignment on return

* Use stable alignment

* Avoid null-bytes in `add` instruction for long commands

---------

Co-authored-by: peace-maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2023-12-14 16:39:36 +01:00
peace-maker 8367da6500 Bump unicorn dependency to >=2.0.1 (#2315)
* Bump unicorn dependency to >=2.0.1

We're using riscv constants now which were added in unicorn2.

Fixes #2303

* Add RISCV64 plt emulation test binaries
2023-12-14 16:29:46 +01:00
dependabot[bot] b9cece5526 Bump actions/setup-python from 4 to 5 (#2316) 2023-12-12 21:39:27 +00:00
Peace-Maker 95fd121e1f android: suppress filenotfound error when looking for partitions 2023-12-12 21:31:49 +01:00
Peace-Maker 8ec092522e android: fix tests for amd64 emulator 2023-12-12 21:11:30 +01:00
Peace-Maker 8baa27e7a6 android: switch to x86_64 emulator
armeabi-v7a crashes :(
2023-12-12 20:24:02 +01:00
Arusekk d094156606 android: disable accel 2023-12-12 19:57:58 +01:00
peace-makerandArusekk 5f5554fa1c Detect challenge binary and libc in pwn template (#2309)
* Detect challenge binary and libc in `pwn template`

When the `args.exe` or `args.libc` arguments are missing, look for the
files in the current working directory.
This allows to quickly get a template by just running `pwn template`
(and maybe --host --port for remote instances).

This is inspired by https://github.com/io12/pwninit

* Update CHANGELOG

* Add --no-auto argument to disable automatic detection

Allow to keep the old static template behavior.

* Add logging about detection outcome

Inform the user that we're looking for the binaries and if we found one.

* Be more strict about libc filename detection

Check about common name patterns instead of plainly the presence of "libc" in the name.

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

* Change --no-auto argument to reduce double negation

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

---------

Co-authored-by: Arusekk <[email protected]>
2023-12-12 19:02:13 +01:00
Peace-Maker 0c6d5f5e71 android: Disable emulator GPU
Android Emulator 33.1.23 received a Graphics backend upgrade
2023-12-11 23:35:55 +01:00
Peace-Maker fbce30675a Enable unprotected grpc in Android tests
WARNING | The emulator now requires a signed jwt token for gRPC access! Use the -grpc flag if you really want an open unprotected grpc port
2023-12-11 23:04:08 +01:00
Arusekk 6f4682a41a android: enable kvm 2023-12-11 20:00:50 +01:00
Arusekk 07ea0fa185 term: hotfix 2023-12-11 18:41:15 +01:00
Arusekk 84b6c82ff3 term: simplify even further 2023-12-11 18:10:21 +01:00
peace-maker b1e2b56445 Nicer error when running tmux exploit outside tmux (#2314)
When you set the `context.terminal` to be tmux related but then try
to run the exploit outside a tmux session, you'd get a cryptic ValueError
while trying to parse the pid of the new pane in tmux.

Print a nicer reminder to start tmux first.
2023-12-06 23:48:33 +01:00
peace-maker b3d40438fb Fix support for amd64 x32 ABI (#2305)
* Add back support for amd64 x32 ABI

Recognize x32 ELF files as amd64.

* Add x32 test binaries

Regressed in fbf2727ac6
2023-12-04 20:18:20 +01:00
Arusekk 43071cd6e5 Term rework (#2242)
* term rework

* towards floating cells

* redraw when screen clears

* term: working completion

* Display non-printable characters

* ui: fix options with new term

* term: Clean up and fix examples/spinners

* changelog and lint

* Fix SSH in NOTERM mode

* defer termios setup

* term: py2 compat

* py2: compat fixup

* appease pylint

* fix scroll on winch

* term: fix hang on winch during readline
2023-12-02 13:31:40 +01:00
Michal AmbrozandPeace-Maker a8f4093695 Fix pwn libcdb file crashing if "/bin/sh" string was not found (#2307)
* next fails if string is not found in binary

fixes #2306

* Only display "str_bin_sh" symbol if it's available

* Update CHANGELOG

---------

Co-authored-by: Peace-Maker <[email protected]>
2023-11-29 22:29:35 +01:00
peace-maker f03582fb92 Explicitly select readthedocs theme (#2311)
The `sphinx_rtd_theme` has to be selected manually and isn't the default
on RTD anymore. This seems to be part of a wave of deprecations in their
build system.

https://github.com/readthedocs/tutorial-template/blob/f594c40889a4d9949339bd4e0d64275fc99c22f5/docs/source/conf.py#L32
2023-11-29 16:08:55 +01:00
d10c70fb32 Allow to add to the existing environment in ssh instead of replacing it (#1764)
* Add env_add to the ssh module

* Use ignore_environ like process

---------

Co-authored-by: peace-maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2023-11-25 17:08:48 +01:00
65f9d5761d Allow to add to the existing environment in process instead of replacing it (#1763)
* Add env_add to the process module

* Don't mutate passed `env` parameter dictionary

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

* Update CHANGELOG

* Use a boolean flag instead of env_add

There should be preferably only one obvious way to pass env.

* Update CHANGELOG

---------

Co-authored-by: peace-maker <[email protected]>
Co-authored-by: peace-maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2023-11-24 14:02:53 +01:00
Michal AmbrozandPeace-Maker 4ef4746243 Remove python2 shebang lines from commandline tools (#2301)
* remove python2 shabangs

* Remove execute bit from commandline implementation

---------

Co-authored-by: Peace-Maker <[email protected]>
2023-11-24 13:37:29 +01:00
Michal Ambrozandpeace-maker 77957b4b41 Fix SyntaxWarning in Python 3.12 (#2302)
* libcdb.py - python 3.12

Python 3.12 would complain if the \d atom is not escaped in the binary string.

/usr/lib/python3.12/site-packages/pwnlib/commandline/libcdb.py:224: SyntaxWarning: invalid escape sequence '\d'
  libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data)

* Use raw string

---------

Co-authored-by: peace-maker <[email protected]>
2023-11-24 13:32:08 +01:00
Arusekk bb1d16c3c6 rop: Make stack move faster (#2300)
This defers calculation of all 'add (e|r)sp, 0xHUGE' payload until the gadget
gets actually used in a chain.

Fixes #2253
2023-11-20 15:31:42 +01:00
Arusekk e4d3c82501 ssh: Fix unstable cpuinfo on dynscaled CPUs 2023-11-14 23:41:41 +01:00
ArusekkandMichał Radwański 2f2e0dc48c elf: Resolve more relocations into GOT entries (#2277)
So far only relocations pointing to external symbols were considered
when filling GOT.  Relocations in libc are a different thing, often
found in CTF challenges (libc with partial RELRO has many interesting
overridable function pointers, like strlen & strchrnul).

Co-authored-by: Michał Radwański <[email protected]>
2023-11-14 23:13:21 +01:00
peace-maker b53d476977 Add x86 CET status to checksec output (#2293)
* Add x86 CET status to checksec output

The ELF class now features two properties:
- ibt for indirect branch tracking `-fcf-protection=branch`
- shstk for shadow stack `-fcf-protection=return`

Both checking the presence of the respective feature bit in the
note property x86 feature segment.

* Check if user shadowstack is enabled on SSH target

Look for "user_shstk" in /proc/cpuinfo for ssh.checksec()

* Add ELF.iter_notes and ELF.iter_properties helpers

Allows to easily iterate the PT_NOTE segment and the properties in "GNU"
notes.

* Check for ibt on remote ssh machine

* Update CHANGELOG

* Suppress error when /proc/cpuinfo cannot be read
2023-11-14 21:08:55 +01:00
Arusekk 0bf914e9e2 Merge branch 'beta' into dev 2023-11-14 20:59:20 +01:00
Arusekk 1b21f7a043 Release 4.12.0beta1 2023-11-14 20:15:36 +01:00
Arusekk 89650ace0d Merge branch 'beta' into dev 2023-11-14 19:48:03 +01:00
Arusekk d643a33233 Merge branch 'stable' into beta 2023-11-14 19:47:35 +01:00
Arusekk a7c03851fe Merge branch 'beta' into dev 2023-11-01 23:44:23 +01:00
Arusekk 2f58fe7ae5 Merge branch 'stable' into beta 2023-11-01 23:44:00 +01:00
Arusekk 5ff96409fc Merge branch 'beta' into dev 2023-10-29 16:30:25 +01:00
Arusekk 8cf3ecdc5b Merge branch 'stable' into beta 2023-10-29 16:30:08 +01:00
Arusekk 48fbffe449 Merge branch 'beta' into dev 2023-10-01 14:07:45 +02:00
Arusekk e9f73bf440 Merge branch 'stable' into beta 2023-10-01 13:48:12 +02:00
Gordiig 32840ad806 FIX: Getting right amount of data for search fix (#2281)
* Getting right amount of data for search fix

* Add line to CHANGELOG.md
2023-10-01 13:39:11 +02:00
dependabot[bot] 81d17c701c Bump docker/login-action from 2 to 3 (#2274)
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 18:48:47 +02:00
dependabot[bot] dd6b754453 Bump docker/setup-buildx-action from 2 to 3 (#2275)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 18:48:28 +02:00
dependabot[bot] 57d810acb1 Bump docker/build-push-action from 4 to 5 (#2273)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4...v5)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-23 18:48:05 +02:00
Peace-Maker fd50d4c995 Begin working on 4.13.0 2023-09-16 00:44:57 +02:00
Peace-Maker 01e6970971 Release 4.12.0beta0 2023-09-16 00:20:18 +02:00
92cd96999f Allow empty argv in ssh.process() (#2217) (#2234)
* Fix bug at ssh.py:process() - empty argv[0] Error

Before this, process.py relied on `os.execve` which disallows an empty
argv or argv[0] == "".
This commit replaces `os.execve` with  `ctypes.CDLL(None).execve` to
call the C-Library function directly which allows an empty argv.

* Add #2225 to stable changelog

* Better ctypes syntax

* Add error message if cytpes.execve fails.

* Updata CHANGELOG.md

* ssh.py: python2 compatibility for os.environb

* Add check that "=" not in misc.normalize_argv_env

This check checks prevents the use of "=" in the
key of an environment variable, which is generally
impossible.

* ssh.process: Seperate cases for empty argv[0]

This commit seperates the cases for an empty argv[0]
from normal cases.

* ssh.py delete leftover comment

---------

Co-authored-by: Youheng Lü <[email protected]>
Co-authored-by: Arusekk <[email protected]>
Co-authored-by: peace-maker <[email protected]>
2023-09-15 01:10:14 +02:00
Hannes WeissteinerandHannes Weissteiner 8b86cc3ba6 Allow creating custom templates in user-config directory (#2257)
* Allow creating custom templates in user-config directory

* Update changelog

* add cmdline parameter for template

* direct template file selection, added more help

* added template path to help string

* remove testing template

* allow crash on invalid template file

* python2 compatibility

* improved documentation for template

---------

Co-authored-by: Hannes Weissteiner <[email protected]>
2023-09-13 18:03:29 +02:00
dependabot[bot] 20f07795f0 Bump actions/checkout from 3 to 4 (#2265)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-12 19:00:26 +02:00
Peace-Maker d12d2ea78c Merge branch 'beta' into dev 2023-09-12 13:37:43 +02:00
peace-maker 8be72238b0 Rename wd parameter to cwd in ssh.system and ssh.run_to_end (#2251)
The current working directory should be called the same everywhere.
The `wd` argument is deprecated and `cwd` takes precedence.
2023-09-12 09:37:27 +02:00
Arusekk e608a46490 Merge branch 'beta' into dev 2023-08-20 20:54:18 +02:00
peace-maker 340b0bf0bc Only run docker workflow on upstream repo (#2252)
Don't run it in forks which can't push to the dockerhub.
2023-08-02 01:34:17 +02:00
Furkan 2e5a33cfea Remove unused and broken rop.find_stack_adjustment (#2249)
* Added missing return to rop.find_stack_adjustment

* Removed ROP.find_stack_adjustment()
2023-07-31 21:44:37 +02:00
peace-maker b8b29e1cc7 Fix pushing docker images on manual workflow run (#2250)
* Fix pushing docker images on manual workflow run

Running the docker workflow manually would associate the run with the
`dev` branch. The generated tags would all point to the `dev` tag, so
tag the image manually to the correct branch.

* Fix building ci image

It's called `python2.7` instead of `python`.

* Add riscv64 binutils to base image
2023-07-30 17:49:54 +02:00
dependabot[bot] ff178aad6a Bump actions/download-artifact from 2 to 3 (#2247)
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-30 15:34:24 +02:00
dependabot[bot] 8c8e49cc10 Bump actions/upload-artifact from 2 to 3 (#2246)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 2 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-30 15:33:47 +02:00
dependabot[bot] 0b0f786002 Bump actions/checkout from 2 to 3 (#2248)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-30 09:38:24 +02:00
Arusekk 9ba65eb414 Merge branch 'beta' into dev 2023-07-29 22:31:37 +02:00
peace-maker 5b783feabd Keep Github Actions up-to-date with Dependabot (#2238)
Check every week if there are newer versions of the Github Actions we use.
2023-07-29 22:16:08 +02:00
Furkan 5589e15051 Decoded data from recv_all in packages to a string (#2237)
* Decoded data from recv_all in packages to a string

* Added sources for UTF-8 usage in Android

* Updated adb source
2023-07-29 21:23:27 +02:00
peace-maker 40c1ef5890 Do not overwrite global bytes in examples (#2240) 2023-07-28 15:50:18 +02:00
Arusekk e5af2bc3a3 Fix format string badbytes inconsistency (#1895)
* Fix format string badbytes inconsistency

Fixes #1888

* fmtstr: Make sure initial atom is considered
2023-07-28 15:49:00 +02:00
Furkan 6fa13de917 Added MAP_ADD opcode to list of opcodes in safeeval.py (#2243) 2023-07-28 14:45:22 +02:00
Arusekk 216e3edcde ci: stabilize coverage (#2235)
* ci: stabilize coverage

* fix cache dirs
2023-07-27 16:41:05 +02:00
peace-maker c334d46026 Publish Docker images through CI (#2236)
Rebuild the images on push to the respective branch.
2023-07-27 14:41:10 +02:00
peace-makerandArusekk 3e4e8b7055 Add --libc libc.so argument to pwn template (#2212)
* Add ELF.set_runpath()

Shells out to the `patchelf` tool to patch the ELF's RUNPATH. This lets the dynamic loader look for needed shared libraries in the given path first before the system libraries when running the binary.

* Add ELF.set_interpreter()

Shells out to the `patchelf` tool to patch the ELF's PT_INTERP segment. This allows to change the ld.so used when running the binary.

* Add convenience wrapper to set runpath & interpreter

A helper function to patch the ELF such that it uses the dynamic loader and other libraries in the given folder.

* Add method to download libraries matching a libc

Download the matching libraries for the given libc binary and cache them in a local directory using `libcdb.download_libraries()`. The libraries are looked up using libc.rip and fetched from the official package repositories if available.

Only .deb and .pkg.tar.* packages are currently supported (Debian/Ubuntu, Arch).

* Add --libc argument to pwnup template

This generates code into the template which allows you to run the binary using the given libc.

The foreign libc is used by default, but you can choose to run the binary against your system's local libc using the `LOCAL_LIBC` command line argument when executing the exploit script.

* Work around Python 2 tarfile not supporting xz

* Fix crash when libc wasn't found on libc.rip

* Update README

* Use subprocess.check_output instead of pwnlib.process

* Special case Python 2 instead of 3

* Only catch Exceptions instead of everything

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

* Check launchpad.net for Ubuntu libcs

This mimics the way io12/pwninit obtains the ld.so.
If the download from libc.rip fails, try launchpad.net.

* Hide comment in template when --quiet

* Please confused pylint in PY2 context

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

---------

Co-authored-by: Arusekk <[email protected]>
2023-07-26 13:58:32 +02:00
Arusekk 0696c5cb4f shellcraft/*/freebsd: match linux in switching cs (#2232)
* shellcraft/*/freebsd: match linux in switching cs

* shellcraft: note trashed registers in switching cs
2023-07-24 19:34:12 +02:00
Arusekk ac855ab179 shellcraft/i386: optimize stackhunter (#2231)
* shellcraft/i386: optimize stackhunter

* shellcraft/i386: add+opt long-gone stackhunter_helper

* shellcraft/i386: concise doctests
2023-07-24 19:33:26 +02:00
Arusekk 42c0798077 Merge branch 'beta' into dev 2023-07-17 19:26:41 +02:00
Arusekk d69e294d54 py2: fix long hex in shellcraft etc. (#2230) 2023-07-17 17:10:15 +02:00
whokilleddbandDebjeet Banerjee 94fc5d2769 Update ssh.py (#1946)
Two minor changes were made in the commit:
- `which` was replaced with `command -v`
- Minor bug fix

## Why bother with `which` ?
- `POSIX` compatible
-  `which` is an external process you're launching for doing very little (meaning builtins like hash, type or command are way cheaper), you can also rely on the builtins to actually do what you want, while the effects of external commands can easily vary from system to system.
- Many operating systems have a which that doesn't even set an exit status, meaning the if which foo won't even work there and will always report that foo exists, even if it doesn't (note that some POSIX shells appear to do this for hash too).
- Many operating systems make which do custom and evil stuff like change the output or even hook into the package manager.

Reference [here](https://stackoverflow.com/a/677212)

Co-authored-by: Debjeet Banerjee <[email protected]>
2023-07-16 20:44:59 +02:00
Furkan fbe88c06db Improve error handling of serial-tube constructor (#2228)
* Resolved issue #1411 abt serial-tube misbehaving.

* Removed redundant docstrings
2023-07-15 12:41:10 +02:00
Disconnect3d dd1edaa6c9 shellcraft: more explicit sleep.asm docstring (#2226)
This commit adds information that the sleep shellcraft function does not check if it returned as part of an interrupt and it does not retry the syscall if this occured.
2023-07-12 00:24:54 +02:00
peace-maker b23251d05b Fix Python 2 editable user install (#2224)
This appears to be fixed in setuptools 62, but Python 2 is stuck at
version 44. Use a workaround which overrides the disabled user installs
temporarily.

This fixes our develop Dockerfile too!
2023-07-10 08:53:15 +02:00
peace-maker 83e19216ab Don't change log level for Corefile._parse_stack() (#2222)
* Don't change log level for Corefile._parse_stack()

If we don't want to see the log, don't print it. There are no spammy
debug logs anymore.

Fixes #1666

* Fix doctest
2023-07-09 22:02:44 +02:00
ArusekkandPeace-Maker 1399c7a969 Shellcraft stackargs (#2219)
From: Peace-Maker <[email protected]>

* Shellcraft: Fix spilling syscall args to the stack

There was a typo in the generated syscall template causing only the last
stack argument to be generated.

$ shellcraft -f asm mips.linux.sendto 3 0x123456 0x100 0 0xabcdefff 0x10
The 0xabcdefff argument was missing.

* Shellcraft: Generate 6 syscall arguments for unknown functions

There are syscalls with 6 arguments like mmap and sendto which couldn't
be called with all 6 arguments set.

$ shellcraft -f asm mips.linux.mmap2 0 0x1000
'PROT_READ | PROT_WRITE | PROT_EXEC' 'MAP_PRIVATE | MAP_ANONYMOUS' -1 0

* Shellcraft: Fix typo in error message

The `syscalls` variable is only present in the generate.py, not the
generated template.

* Regenerate syscall templates

* MIPS shellcraft: Push last two syscall arguments to the stack

The mips.linux.syscall template was only handling 4 syscall arguments
and silently discarded the remaining ones. Push the arguments to the stack
instead.

$ shellcraft -f asm mips.linux.syscall SYS_sendto 3 0x123456 0x100 0 0xabcdefff 0x10

Fixes #2153

* Fix mips syscall doctest

* Update CHANGELOG

Co-authored-by: Peace-Maker <[email protected]>
2023-07-09 22:00:54 +02:00
peace-makerandArusekk 8d104851ad Add shellcraft.sleep template wrapping SYS_nanosleep (#2221)
* Add shellcraft.sleep template wrapping SYS_nanosleep

Accepts the time in seconds as a float argument and calls SYS_nanosleep.

Fixes #1428

* Update CHANGELOG

* Use nanosleep wrapper

Co-authored-by: Arusekk <[email protected]>
2023-07-09 21:27:53 +02:00
peace-maker 9c83fa1099 Remove Travis CI Badge (#2220)
We don't use Travis CI anymore.

Closes #1740
2023-07-09 20:36:26 +02:00
Arusekk f1d5afe328 Merge branch 'beta' into dev 2023-07-09 15:25:06 +02:00
LY 05783e98c9 Change temp path in binutils install script (#2097)
Use $TMPDIR first. If $TMPDIR is unset/empty, use /tmp
(AFAIK most distros don't set $TMPDIR by default)
2023-07-09 15:21:40 +02:00
Arusekk 7547518e7c Merge branch 'beta' into dev 2023-07-09 12:57:50 +02:00
zt20xx 96a8db0243 Update elf.py (#2211) 2023-07-09 10:36:39 +02:00
Arusekk 5f15b53ee6 Merge branch 'beta' into dev 2023-07-02 22:16:47 +02:00
maple b73e74f1ff Replace isSet with is_set as the former is deprecated in 3.10+ (#2208)
`threading.Event.isSet` is deprecated here: https://github.com/python/cpython/commit/9825bdfbd5c966abf1f1b7264992d722a94c9613
2023-06-26 13:50:11 +02:00
Arusekk 909d5833cd Merge branch 'beta' into dev 2023-06-26 01:45:34 +02:00
91be8af121 Add -p (--prefix) and -s (--separator) arguments to hex command (#2117)
* Add formats for hex

* Add formats for hex coverage

Relates to #2117. Adding coverage as requested.

* Use util.lists.group

* Update CHANGELOG

* Fix Python 2 support

* Simplify hex formatting

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

---------

Co-authored-by: marcan2020 <[email protected]>
Co-authored-by: carydrew <[email protected]>
Co-authored-by: Peace-Maker <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2023-06-04 22:23:11 +02:00
peace-maker 61bce7f6cf Fix remote and listen in sagemath (#2202)
* Fix `remote` and `listen` in sagemath

sage's Integer doesn't seem to inherit from python's base int.

```
----> 1 listen(Integer(1337))
OSError: Int or String expected
```

Fixes #2201

* Update CHANGELOG
2023-06-02 16:57:00 +02:00
Arusekk c195c14b13 Merge branch 'beta' into dev 2023-06-01 11:40:37 +02:00
Arusekk e09112b7d4 Merge branch 'beta' into dev 2023-05-21 16:38:42 +02:00
Arusekk d590a0b77f Begin working on 4.12.0 2023-05-21 16:16:11 +02:00
701 changed files with 27916 additions and 4071 deletions

No files matched your search

-12
View File
@@ -1,12 +0,0 @@
[run]
branch = True
parallel = True
concurrency = thread,multiprocessing
omit =
*/constants/*
source =
pwn
pwnlib
~/.cache/.pwntools-cache-2.7/
~/.cache/.pwntools-cache-3.8/
disable_warnings = module-not-imported
+1 -1
View File
@@ -39,7 +39,7 @@ You should see `[DEBUG]` statements that show what's happening behind the scenes
## Verify on Ubuntu
If possible, please verify that your issue occurs on 64-bit Ubuntu 18.04. We provide a Dockerfile based on Ubuntu 18.04 via `docker.io` to make this super simple, no VM required!
If possible, please verify that your issue occurs on 64-bit Ubuntu 22.04. We provide a Dockerfile based on Ubuntu 22.04 via `docker.io` to make this super simple, no VM required!
```sh
# Download the Docker image
+8
View File
@@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
+21 -10
View File
@@ -6,24 +6,21 @@ jobs:
android-test:
strategy:
matrix:
python-version: [3.8]
python-version: ['3.10']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Cache for pip
uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
**/pyproject.toml
**/requirements*.txt
- name: Install Linux dependencies
run: |
@@ -35,8 +32,22 @@ jobs:
binutils-arm-linux-gnueabihf \
libc6-dbg
- name: Cache for avd
uses: actions/cache@v4
id: cache-avd
with:
path: |
~/.android
/usr/local/lib/android/sdk/emulator
/usr/local/lib/android/sdk/platform-tools
/usr/local/lib/android/sdk/system-images
key: ${{ matrix.os }}-cache-avd-${{ hashFiles('travis/setup_avd*.sh') }}
restore-keys: |
${{ matrix.os }}-cache-avd-
- name: Install Android AVD
run: |
sudo usermod -aG kvm $USER
source travis/setup_avd_fast.sh
sed -i 's/skip_android = True/skip_android = False/' docs/source/conf.py
set | grep ^PATH >.android.env
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
+144 -13
View File
@@ -5,32 +5,86 @@ jobs:
test:
strategy:
matrix:
python_version: [2.7, 3.8]
python_version: ['3.10', '3.12']
os: [ubuntu-latest]
include:
- python_version: '2.7'
os: ubuntu-22.04
runs-on: ${{ matrix.os }}
timeout-minutes: 30
services:
libcdb-cache:
image: nginx
volumes:
- /home/runner/libcdb-cache:/var/cache/nginx
ports:
- 3000:3000 # https://debuginfod.elfutils.org proxy cache
- 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/ http://localhost:3004/"
PWN_LIBCRIP_URL: http://localhost:3001/
PWN_UBUNTU_ARCHIVE_URL: http://localhost:3002/
PWN_GITLAB_LIBCDB_URL: http://localhost:3003/
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-depth: 20
- name: Git History
run: |
git fetch origin
git log --oneline --graph -10
- name: Fix libcdb-cache permissions
id: fix-perms
run: |
sudo chown -R runner:runner /home/runner/libcdb-cache
echo "date=$(/bin/date -u "+%Y%m%d%H%M%S")" >> $GITHUB_OUTPUT
- name: Cache for libcdb requests
uses: actions/cache@v4
with:
path: ~/libcdb-cache
key: libcdb-python${{ matrix.python_version }}-${{ steps.fix-perms.outputs.date }}
restore-keys: |
libcdb-python${{ matrix.python_version }}-
libcdb-
- name: Install libcdb-cache service config
run: |
sudo chown -R 101:101 /home/runner/libcdb-cache
container_id=$(docker ps --all --filter volume=/home/runner/libcdb-cache --no-trunc --format "{{.ID}}")
docker cp ./travis/libcdb_nginx_cache.conf $container_id:/etc/nginx/nginx.conf
docker restart $container_id
- name: Install RPyC for gdb
run: |
# The version packaged in python3-rpyc is too old on Ubuntu 24.04
# We use ^6.0 from pip.
sudo apt-get update && sudo apt-get install -y python3-pip gdb gdbserver
/usr/bin/python -m pip install --break-system-packages rpyc || /usr/bin/python -m pip install rpyc
gdb --batch --quiet --nx --nh --ex 'py import rpyc; print(rpyc.version.version)'
- name: Cache for pip
uses: actions/cache@v3
id: cache-pip
uses: actions/cache@v4
if: matrix.python_version == '2.7'
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
key: ${{ matrix.os }}-${{ matrix.python_version }}-cache-pip-${{ hashFiles('**/pyproject.toml', '**/requirements*.txt') }}
restore-keys: ${{ matrix.os }}-${{ matrix.python_version }}-cache-pip-
- name: Set up Python ${{ matrix.python_version }}
if: matrix.python_version != '2.7'
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
cache: 'pip'
cache-dependency-path: |
**/pyproject.toml
**/requirements*.txt
- name: Set up Python 2.7
if: matrix.python_version == '2.7'
@@ -62,19 +116,21 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends -o Acquire::Retries=3 \
ash bash-static dash ksh mksh zsh \
python3-rpyc \
gdb gdbserver socat \
binutils-multiarch qemu-user-static \
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 \
binutils-sparc64-linux-gnu \
binutils-riscv64-linux-gnu \
gcc-multilib \
libc6-dbg \
elfutils
elfutils \
patchelf
- name: Testing Corefiles
run: |
@@ -94,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)'
@@ -111,6 +169,10 @@ jobs:
- name: Coverage doctests
run: |
# Python version installed using setup-python interferes with gdb's python
# by setting LD_LIBRARY_PATH and gdb's python becoming unable to load built-in modules
# like _socket. This is a workaround.
unset LD_LIBRARY_PATH
PWNLIB_NOTERM=1 python -bb -m coverage run -m sphinx -b doctest docs/source docs/build/doctest
- name: Coverage running examples
@@ -138,11 +200,18 @@ jobs:
pwn shellcraft --list |tail
pwn shellcraft -l --syscalls |tail
pwn shellcraft -l execve
pwn shellcraft -l execve + exit
pwn shellcraft --show i386.linux.loader_append
pwn shellcraft --show i386.linux.loader_append + i386.linux.sh
pwn shellcraft -f asm --color amd64.linux.sh
pwn shellcraft -f asm --color amd64.linux.setreuid + amd64.linux.cat /etc/passwd
pwn shellcraft -f asm --color amd64.linux.setreuid = amd64.linux.cat /key+secret --delim =
pwn shellcraft -f elf amd64.linux.syscalls.exit 0 </dev/null |pwn hex
pwn shellcraft -f elf amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null |pwn hex
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -f i --color amd64.linux.cat /etc/passwd + amd64.linux.sh </dev/null
pwn shellcraft -f c amd64.linux.syscalls.exit 0 </dev/null
pwn shellcraft -f c amd64.linux.cat /etc/passwd + amd64.linux.syscalls.exit 0 </dev/null
pwn shellcraft -f str aarch64.linux.sh </dev/null
pwn shellcraft -abr -f elf -o /dev/null amd64.linux.cat /etc/passwd </dev/null
pwn shellcraft -nzr thumb.linux.syscalls.execve /bin/cat '["/bin/cat", "/etc/os-release"]' </dev/null
@@ -151,6 +220,11 @@ jobs:
pwn disasm --color ff3424c3ebfe
pwn asm -f hex nop
pwn hex ABCD
pwn hex ABCD --separator ' '
pwn hex ABCD --prefix '\x'
pwn hex ABCD -p '0x' -s ' '
pwn hex abcd
pwn unhex 4141 4141
@@ -158,6 +232,7 @@ jobs:
pwn phd -l 0x3d --color=always /etc/os-release
pwn checksec /bin/bash
(ulimit -v 500000 && pwn checksec /bin/bash)
pwn errno 2
pwn errno -1
@@ -166,32 +241,87 @@ jobs:
pwn constgrep -c freebsd -m ^PROT_ '3 + 4'
pwn constgrep ^MAP_ 0
pwn constgrep -e O_RDWR
pwn constgrep C
pwn libcdb file /lib/x86_64-linux-gnu/libc.so.6
pwn libcdb lookup puts 5f0 __libc_start_main_ret d0a
pwn libcdb hash b229d1da1e161f95e839cf90cded5f719e5de308
- name: Build source and wheel distributions
if: matrix.python_version > '2.7'
if: matrix.python_version != '2.7'
run: |
python -m build
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
if: matrix.python_version == '3.10'
with:
name: packages
path: dist/
include-hidden-files: true
- uses: actions/upload-artifact@v4
with:
name: coverage-${{ matrix.python_version }}
path: .coverage*
include-hidden-files: true
- name: Fix libcdb-cache permissions
run: |
container_id=$(docker ps --filter volume=/home/runner/libcdb-cache --no-trunc --format "{{.ID}}")
docker stop $container_id
sudo chown -R runner:runner /home/runner/libcdb-cache
windows-test:
runs-on: windows-latest
timeout-minutes: 30
continue-on-error: true
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install --upgrade pip
pip install --upgrade --editable .
- name: Sanity checks
run: |
python -bb -c 'from pwn import *'
python -bb examples/text.py
upload-coverage:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 20
- uses: actions/download-artifact@v4
with:
pattern: coverage-*
merge-multiple: true
- name: Install coveralls
run: |
pip install --break-system-packages tomli coveralls || pip install tomli coveralls
- name: Upload coverage to coveralls.io
run: |
coverage combine
COVERALLS_REPO_TOKEN=PP20MEgztXIQJJTguQwe2jeCh6Bm4lkbv coveralls
staging-merge:
runs-on: ubuntu-latest
if: github.repository_owner == 'Gallopsled' && github.event_name == 'push' && startsWith(github.event.ref, 'refs/heads/') && endsWith(github.event.ref, '-staging')
needs: test
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
with:
fetch-depth: 20
- name: Push changes to protected branch
@@ -204,6 +334,7 @@ jobs:
git push origin "$TARGET"
git push origin --delete "$BRANCH"
pypi:
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
@@ -212,7 +343,7 @@ jobs:
needs: test
steps:
- name: Download artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
with:
name: packages
path: dist
+64
View File
@@ -0,0 +1,64 @@
name: Publish Docker images
on:
push:
branches:
- dev
- beta
- stable
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
if: github.repository_owner == 'Gallopsled'
steps:
# Required for subdirectories in Git context
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Build and push base image
uses: docker/build-push-action@v6
if: github.event_name == 'workflow_dispatch'
with:
context: "{{defaultContext}}:extra/docker/base"
push: true
tags: pwntools/pwntools:base
- name: Build and push stable image
uses: docker/build-push-action@v6
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/stable')
with:
context: "{{defaultContext}}:extra/docker/stable"
push: true
tags: pwntools/pwntools:stable
- name: Build and push beta image
uses: docker/build-push-action@v6
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/beta')
with:
context: "{{defaultContext}}:extra/docker/beta"
push: true
tags: pwntools/pwntools:beta
- name: Build and push dev image
uses: docker/build-push-action@v6
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/dev')
with:
context: "{{defaultContext}}:extra/docker/dev"
push: true
tags: |
pwntools/pwntools:dev
pwntools/pwntools:latest
- name: Build and push ci image
uses: docker/build-push-action@v6
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'push' && github.ref == 'refs/heads/dev')
with:
context: "{{defaultContext}}:travis/docker"
push: true
tags: pwntools/pwntools:ci
+7 -9
View File
@@ -5,23 +5,21 @@ jobs:
lint:
strategy:
matrix:
python-version: [3.8]
python-version: ['3.10']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Cache for pip
uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
**/pyproject.toml
**/requirements*.txt
- name: Critical lint
run: |
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
+11 -12
View File
@@ -5,33 +5,32 @@ jobs:
build:
strategy:
matrix:
python-version: [3.8]
python-version: ['3.10']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- name: Cache for pip
uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
**/pyproject.toml
**/requirements*.txt
- name: PyLint
run: |
set -x
pip install pylint
pip install 'pylint<4'
pip install --upgrade -e .
pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- > current.txt
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
git fetch origin
git checkout origin/"$GITHUB_BASE_REF"
pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- > base.txt
run_pylint > base.txt
if diff base.txt current.txt | grep '>'; then
false
fi
+4 -1
View File
@@ -5,7 +5,10 @@ version: 2
build:
os: ubuntu-22.04
tools:
python: "3"
python: "3.12"
sphinx:
configuration: docs/source/conf.py
python:
install:
-67
View File
@@ -1,67 +0,0 @@
language: python
addons:
apt:
packages:
- lib32stdc++6
- ash
- bash
- dash
- ksh
- mksh
- zsh
- pandoc
- gdb
- socat
- sshpass
- binutils
- qemu-user-static
- binutils-multiarch
- binutils-aarch64-linux-gnu
- binutils-arm-linux-gnueabihf
- binutils-mips-linux-gnu
- binutils-msp430
- binutils-powerpc-linux-gnu
- binutils-s390x-linux-gnu
- binutils-sparc64-linux-gnu
- bash-static
- python3-pip
- elfutils
cache:
- pip
- directories:
- usr
- /home/travis/virtualenv/python2.7.15/lib/python2.7/site-packages/
- /home/travis/virtualenv/python2.7.15/bin/
- /home/travis/virtualenv/python3.8.*/lib/python3.8/site-packages/
- /home/travis/virtualenv/python3.8.*/bin/
- docs/build/doctest/.doctrees
python:
- "2.7"
- "3.8"
before_install:
- echo $-
- source travis/install.sh
- source travis/ssh_setup.sh
- echo $-
install:
- /usr/bin/python3 -m pip install rpyc # for GDB
- pip install --upgrade pip
- pip install --upgrade flake8 appdirs # https://github.com/ActiveState/appdirs/issues/89#issuecomment-282326570
- python setup.py egg_info # pypa/pip#6275
- pip install --upgrade --editable .
- pip install --upgrade --requirement docs/requirements.txt
before_script:
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
- PWNLIB_NOTERM=1 python -c 'from pwn import *; print(pwnlib.term.term_mode)'
- sudo sh -c "echo 0 > /proc/sys/kernel/yama/ptrace_scope"
script:
# stop the build if there are Python syntax errors or undefined names
- flake8 . --count --select=E9,F63,F7 --show-source --statistics --exclude=android-?dk # TODO: Add F82
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- PWNLIB_NOTERM=1 coverage run -m sphinx -b doctest docs/source docs/build/doctest
- coverage combine
after_success:
- coveralls
- source travis/update_demo.sh
+289 -8
View File
@@ -9,9 +9,14 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [4.13.0](#4130-dev) | `dev` |
| [4.12.0](#4120-beta) | `beta` |
| [4.11.1](#4111-stable) | `stable` | Nov 14, 2023
| [5.0.0](#500-dev) | `dev` |
| [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
| [4.11.1](#4111) | | Nov 14, 2023
| [4.11.0](#4110) | | Sep 15, 2023
| [4.10.0](#4100) | | May 21, 2023
| [4.9.0](#490) | | Dec 29, 2022
@@ -68,12 +73,284 @@ The table below shows which release corresponds to each branch, and what date th
| [3.0.0](#300) | | Aug 20, 2016
| [2.2.0](#220) | | Jan 5, 2015
## 4.13.0 (`dev`)
## 5.0.0 (`dev`)
- [#2598][2598] aarch64: Fix ABI definition
- [#2419][2419] riscv: avoid compressed instructions (if you need compressed, use .option rvc)
- [#2551][2551] Detect when kitty is being used as terminal
- [#2519][2519] Drop Python 2.7 support / Require Python 3.10
- [#2507][2507] Add `+LINUX` and `+WINDOWS` doctest options and start proper testing on Windows
- [#2522][2522] Support starting a kitty debugging window with the 'kitten' command
- [#2524][2524] Raise EOFError during `process.recv` when stdout closes on Windows
- [#2526][2526] Properly make use of extra arguments in `packing` utilities. `sign` parameter requires keyword syntax to specify it.
- [#2517][2517] Allow to passthru kwargs on `ssh.__getattr__` convenience function to fix SSH motd problems
- [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool.
- [#2529][2529] Add LoongArch64 support
- [#2506][2506] ROP: fix `ROP(ELF(exe)).leave` is `None` in some ELF
- [#2504][2504] doc: add example case for `tuple` (host, port pair) in `gdb.attach`
- [#2546][2546] ssh: Allow passing disabled_algorithms keyword argument from ssh to paramiko
- [#2538][2538] Add `ssh -L` / `ssh.connect_remote()` workaround when `AllowTcpForwarding` is disabled
- [#2574][2574] Allow creating an ELF from in-memory bytes
- [#2575][2575] Detect when Terminator is being used as terminal
- [#2578][2578] Add gnome-terminal, Alacritty, Ttilix for run_in_new_terminal
- [#2590][2590] Add support for finding corefiles under WSL2
- [#2496][2496] Add linux ko file search support
- [#2542][2542] Decode `_IO_*` flags in `FileStructure` member
- [#2592][2592] pwnlib.config: Fix customization of `context.timeout`
- [#2608][2608] Abort on `libcdb file libc.so --unstrip` if eu-unstrip is not installed
- [#2611][2611] Cleanup `pwnlib.lexer` exports and imports
- [#2610][2610] Fix `log.progress` ignoring `context.log_console`
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
[2598]: https://github.com/Gallopsled/pwntools/pull/2598
[2419]: https://github.com/Gallopsled/pwntools/pull/2419
[2551]: https://github.com/Gallopsled/pwntools/pull/2551
[2519]: https://github.com/Gallopsled/pwntools/pull/2519
[2507]: https://github.com/Gallopsled/pwntools/pull/2507
[2522]: https://github.com/Gallopsled/pwntools/pull/2522
[2524]: https://github.com/Gallopsled/pwntools/pull/2524
[2526]: https://github.com/Gallopsled/pwntools/pull/2526
[2517]: https://github.com/Gallopsled/pwntools/pull/2517
[2530]: https://github.com/Gallopsled/pwntools/pull/2530
[2529]: https://github.com/Gallopsled/pwntools/pull/2529
[2506]: https://github.com/Gallopsled/pwntools/pull/2506
[2504]: https://github.com/Gallopsled/pwntools/pull/2504
[2546]: https://github.com/Gallopsled/pwntools/pull/2546
[2538]: https://github.com/Gallopsled/pwntools/pull/2538
[2574]: https://github.com/Gallopsled/pwntools/pull/2574
[2575]: https://github.com/Gallopsled/pwntools/pull/2575
[2578]: https://github.com/Gallopsled/pwntools/pull/2578
[2590]: https://github.com/Gallopsled/pwntools/pull/2590
[2496]: https://github.com/Gallopsled/pwntools/pull/2496
[2542]: https://github.com/Gallopsled/pwntools/pull/2542
[2592]: https://github.com/Gallopsled/pwntools/pull/2592
[2608]: https://github.com/Gallopsled/pwntools/pull/2608
[2611]: https://github.com/Gallopsled/pwntools/pull/2611
[2610]: https://github.com/Gallopsled/pwntools/pull/2610
[2615]: https://github.com/Gallopsled/pwntools/pull/2615
## 4.15.1
- [#2694][2694] fix: pad bytes fields to correct field size in FileStructure
- [#2701][2701] Fix `adb._build_date()` crash on devices with non-standard locale date strings
- [#2721][2721] Allow running with Unicorn 2.1.[34] but throw when emulating MIPS
- [#2773][2773] Disable debuginfod doctests in libcdb.unstrip_libc
- [#2772][2772] fix(packing): `dd()` no longer crashes on a `bytearray` destination
- [#2787][2787] gdb.corefile: Run gdb directly to prevent detach race
- [#2795][2795] regsort: Fix self-assignment detection through register aliases
[2694]: https://github.com/Gallopsled/pwntools/pull/2694
[2701]: https://github.com/Gallopsled/pwntools/pull/2701
[2721]: https://github.com/Gallopsled/pwntools/pull/2721
[2773]: https://github.com/Gallopsled/pwntools/pull/2773
[2772]: https://github.com/Gallopsled/pwntools/pull/2772
[2787]: https://github.com/Gallopsled/pwntools/pull/2787
[2795]: https://github.com/Gallopsled/pwntools/pull/2795
## 4.15.0 (`stable`)
- [#2508][2508] Ignore a warning when compiling with asm on nix
- [#2471][2471] Properly close spawned kitty window
- [#2358][2358] Cache output of `asm()`
- [#2457][2457] Catch exception of non-ELF files in checksec.
- [#2444][2444] Add `ELF.close()` to release resources
- [#2413][2413] libcdb: improve the search speed of `search_by_symbol_offsets` in local libc-database
- [#2470][2470] Fix waiting for gdb under WSL2
- [#2479][2479] Support extracting libraries from Docker image in `pwn template`
- [#2483][2483] Only print `checksec` output of `ELF.libc` when it was printed for the `ELF` already
- [#2482][2482] Throw error when using `sni` and setting `server_hostname` manually in `remote`
- [#2478][2478] libcdb-cli: add `--offline-only`, refactor unstrip and add fetch parser for download libc-database
- [#2484][2484] Allow to disable caching
- [#2291][2291] Fix attaching to a gdbserver with tuple `gdb.attach(('0.0.0.0',12345))`
- [#2410][2410] Add `tube.upload_manually` to upload files in chunks
- [#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
[2358]: https://github.com/Gallopsled/pwntools/pull/2358
[2457]: https://github.com/Gallopsled/pwntools/pull/2457
[2444]: https://github.com/Gallopsled/pwntools/pull/2444
[2413]: https://github.com/Gallopsled/pwntools/pull/2413
[2470]: https://github.com/Gallopsled/pwntools/pull/2470
[2479]: https://github.com/Gallopsled/pwntools/pull/2479
[2483]: https://github.com/Gallopsled/pwntools/pull/2483
[2482]: https://github.com/Gallopsled/pwntools/pull/2482
[2478]: https://github.com/Gallopsled/pwntools/pull/2478
[2484]: https://github.com/Gallopsled/pwntools/pull/2484
[2291]: https://github.com/Gallopsled/pwntools/pull/2291
[2410]: https://github.com/Gallopsled/pwntools/pull/2410
[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.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
- [#2388][2388] libcdb: add `offline_only` to `search_by_symbol_offsets`
- [#2374][2374] libcdb.unstrip_libc: debug symbols are fetched only if not present
- [#2327][2327] Add basic support to debug processes on Windows
- [#2437][2437] Support asm/disasm on Windows
- [#2330][2330] Change `context.newline` when setting `context.os` to `"windows"`
- [#2322][2322] Add basic RISCV64 shellcraft support
- [#2376][2376] Return buffered data on first EOF in tube.readline()
- [#2371][2371] Add functions for retrieving process mappings
- [#2398][2398] Add support for generating multiple shellcodes at a time in shellcraft
- [#2389][2389] Fix passing bytes to `context.log_file` and `crc.BitPolynom`
- [#2391][2391] Fix error message when passing invalid kwargs to `xor`
- [#2387][2387] Convert apport_corefile() output from bytes-like object to string
- [#2415][2415] Add shellcraft template for IPv6 socket
- [#2405][2405] Add "none" ssh authentication method
- [#2427][2427] Document behaviour of remote()'s sni argument as string.
- [#2382][2382] added optional port, gdb_args and gdbserver_args parameters to gdb.debug()
- [#2435][2435] Speed up gdbserver handshake in gdb.debug()
- [#2436][2436] Add resolution_addr parameter to Ret2dlresolvePayload
- [#2497][2497] Fix remote.fromsocket() to handle AF_INET6 socket
[2436]: https://github.com/Gallopsled/pwntools/pull/2436
[2371]: https://github.com/Gallopsled/pwntools/pull/2371
[2360]: https://github.com/Gallopsled/pwntools/pull/2360
[2356]: https://github.com/Gallopsled/pwntools/pull/2356
[2374]: https://github.com/Gallopsled/pwntools/pull/2374
[2327]: https://github.com/Gallopsled/pwntools/pull/2327
[2322]: https://github.com/Gallopsled/pwntools/pull/2322
[2330]: https://github.com/Gallopsled/pwntools/pull/2330
[2389]: https://github.com/Gallopsled/pwntools/pull/2389
[2391]: https://github.com/Gallopsled/pwntools/pull/2391
[2376]: https://github.com/Gallopsled/pwntools/pull/2376
[2387]: https://github.com/Gallopsled/pwntools/pull/2387
[2388]: https://github.com/Gallopsled/pwntools/pull/2388
[2398]: https://github.com/Gallopsled/pwntools/pull/2398
[2415]: https://github.com/Gallopsled/pwntools/pull/2415
[2405]: https://github.com/Gallopsled/pwntools/pull/2405
[2427]: https://github.com/Gallopsled/pwntools/pull/2405
[2382]: https://github.com/Gallopsled/pwntools/pull/2382
[2435]: https://github.com/Gallopsled/pwntools/pull/2435
[2437]: https://github.com/Gallopsled/pwntools/pull/2437
[2497]: https://github.com/Gallopsled/pwntools/pull/2497
## 4.13.1
- [#2445][2445] Fix parsing the PLT on Windows
- [#2466][2466] Fix PLT emulation with Unicorn 2.1.0
- [#2466][2466] Switch to PyPi Simple API for update checks
- [#2467][2467] Fix loading at all on Windows
- [#2469][2469] GDB 15+ rpyc detection
[2445]: https://github.com/Gallopsled/pwntools/pull/2445
[2466]: https://github.com/Gallopsled/pwntools/pull/2466
[2467]: https://github.com/Gallopsled/pwntools/pull/2467
[2469]: https://github.com/Gallopsled/pwntools/pull/2469
## 4.13.0
- [#2242][2242] Term module revamp: activating special handling of terminal only when necessary
- [#2277][2277] elf: Resolve more relocations into GOT entries
- [#2281][2281] FIX: Getting right amount of data for search fix
- [#2293][2293] Add x86 CET status to checksec output
- [#1763][1763] Allow to add to the existing environment in `process` instead of replacing it
- [#2307][2307] Fix `pwn libcdb file` crashing if "/bin/sh" string was not found
- [#2309][2309] Detect challenge binary and libc in `pwn template`
- [#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
- [#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
- [#2339][2339] Fix: Allow setting attributes on gdb Breakpoints
- [#2323][2323] Retry failed lookups after one week in libcdb
- [#2325][2325] Match against local system libc first in libcdb
- [#2336][2336] Add `ELF.stripped` and `ELF.debuginfo` properties
- [#2161][2161] Add basic support for darwin shellcraft/asm/disasm/run_shellcode/run_assembly
- [#2161][2161] Fix freebsd amd64 SyscallABI
- [#2160][2161] Fix invalid shellcraft.mov on arm64
- [#2284][2161] Fix invalid shellcraft.pushstr_array on arm64
- [#2345][2345] Fix pwn constgrep when it matches a non-constant type
- [#2338][2338] Fix: follow symlink for libs on ssh connection
- [#2341][2341] Launch GDB correctly in iTerm on Mac
- [#2268][2268] Add a `flatten` argument to `ssh.libs`
- [#2347][2347] Fix/workaround Unicorn Engine 1GB limit that calls exit()
- [#2233][2233] Fix gdb.debug: exe parameter now respected, allow empty argv
- [#2373][2373] Fix displaying bright color variation in terminal output
- [#2378][2378] Don't go though a shell in `gdb.debug`
[2242]: https://github.com/Gallopsled/pwntools/pull/2242
[2277]: https://github.com/Gallopsled/pwntools/pull/2277
[2281]: https://github.com/Gallopsled/pwntools/pull/2281
[2293]: https://github.com/Gallopsled/pwntools/pull/2293
[1763]: https://github.com/Gallopsled/pwntools/pull/1763
[2307]: https://github.com/Gallopsled/pwntools/pull/2307
[2309]: https://github.com/Gallopsled/pwntools/pull/2309
[2308]: https://github.com/Gallopsled/pwntools/pull/2308
[2279]: https://github.com/Gallopsled/pwntools/pull/2279
[2310]: https://github.com/Gallopsled/pwntools/pull/2310
[2335]: https://github.com/Gallopsled/pwntools/pull/2335
[2334]: https://github.com/Gallopsled/pwntools/pull/2334
[2328]: https://github.com/Gallopsled/pwntools/pull/2328
[2189]: https://github.com/Gallopsled/pwntools/pull/2189
[2339]: https://github.com/Gallopsled/pwntools/pull/2339
[2323]: https://github.com/Gallopsled/pwntools/pull/2323
[2325]: https://github.com/Gallopsled/pwntools/pull/2325
[2336]: https://github.com/Gallopsled/pwntools/pull/2336
[2161]: https://github.com/Gallopsled/pwntools/pull/2161
[2345]: https://github.com/Gallopsled/pwntools/pull/2345
[2338]: https://github.com/Gallopsled/pwntools/pull/2338
[2341]: https://github.com/Gallopsled/pwntools/pull/2341
[2268]: https://github.com/Gallopsled/pwntools/pull/2268
[2347]: https://github.com/Gallopsled/pwntools/pull/2347
[2233]: https://github.com/Gallopsled/pwntools/pull/2233
[2373]: https://github.com/Gallopsled/pwntools/pull/2373
[2378]: https://github.com/Gallopsled/pwntools/pull/2378
## 4.12.0
## 4.12.0 (`beta`)
- [#2202][2202] Fix `remote` and `listen` in sagemath
- [#2117][2117] Add -p (--prefix) and -s (--separator) arguments to `hex` command
- [#2221][2221] Add shellcraft.sleep template wrapping SYS_nanosleep
@@ -81,6 +358,8 @@ The table below shows which release corresponds to each branch, and what date th
- [#2212][2212] Add `--libc libc.so` argument to `pwn template` command
- [#2257][2257] Allow creation of custom templates for `pwn template` command
- [#2225][2225] Allow empty argv in ssh.process()
- [#2349][2349] Fix term.readline omitting a trailing \n
- [#2352][2352] add `RETURN_CONST` as an allowed `_const_code` in safeeval
[2202]: https://github.com/Gallopsled/pwntools/pull/2202
[2117]: https://github.com/Gallopsled/pwntools/pull/2117
@@ -89,8 +368,10 @@ The table below shows which release corresponds to each branch, and what date th
[2212]: https://github.com/Gallopsled/pwntools/pull/2212
[2257]: https://github.com/Gallopsled/pwntools/pull/2257
[2225]: https://github.com/Gallopsled/pwntools/pull/2225
[2349]: https://github.com/Gallopsled/pwntools/pull/2349
[2352]: https://github.com/Gallopsled/pwntools/pull/2352
## 4.11.1 (`stable`)
## 4.11.1
- [#2271][2271] FIX: Generated shebang with path to python invalid if path contains spaces
- [#2272][2272] Fix `tube.clean_and_log` not logging buffered data
@@ -106,7 +387,7 @@ The table below shows which release corresponds to each branch, and what date th
## 4.11.0
- [#2185][2185] make fmtstr module able to create payload without $ notation
- [#2185][2185] make fmtstr module able to create payload without $ notation
- [#2103][2103] Add search for libc binary by leaked function addresses `libcdb.search_by_symbol_offsets()`
- [#2177][2177] Support for RISC-V 64-bit architecture
- [#2186][2186] Enhance `ELF.nx` and `ELF.execstack`
@@ -381,7 +662,7 @@ In memoriam — [Zach Riggle][zach] — long time contributor and maintainer of
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
## 4.1.7 (`stable`)
## 4.1.7
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
+3 -4
View File
@@ -3,7 +3,6 @@
[![PyPI](https://img.shields.io/pypi/v/pwntools?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![Docs](https://readthedocs.org/projects/pwntools/badge/?version=stable)](https://docs.pwntools.com/)
[![Travis](https://img.shields.io/travis/Gallopsled/pwntools/dev?logo=Travis)](https://travis-ci.org/Gallopsled/pwntools)
[![GitHub Workflow Status (dev)](https://img.shields.io/github/actions/workflow/status/Gallopsled/pwntools/ci.yml?branch=dev&logo=GitHub)](https://github.com/Gallopsled/pwntools/actions/workflows/ci.yml?query=branch%3Adev)
[![Coveralls](https://img.shields.io/coveralls/github/Gallopsled/pwntools/dev?logo=coveralls)](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat)](http://choosealicense.com/licenses/mit/)
@@ -33,13 +32,13 @@ To get you started, we've provided some example solutions for past CTF challenge
# Installation
Pwntools is best supported on 64-bit Ubuntu LTS releases (14.04, 16.04, 18.04, and 20.04). Most functionality should work on any Posix-like distribution (Debian, Arch, FreeBSD, OSX, etc.).
Pwntools is best supported on 64-bit Ubuntu LTS releases (18.04, 20.04, 22.04, and 24.04). Most functionality should work on any Posix-like distribution (Debian, Arch, FreeBSD, OSX, etc.).
Python3 is suggested, but Pwntools still works with Python 2.7. Most of the functionality of pwntools is self-contained and Python-only. You should be able to get running quickly with
```sh
apt-get update
apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
sudo apt-get update
sudo apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
python3 -m pip install --upgrade pip
python3 -m pip install --upgrade pwntools
```
+6 -4
View File
@@ -1,14 +1,16 @@
capstone
coveralls
coverage[toml]
python-dateutil
doc2dash
docutils<0.18
docutils<0.18; python_version<'3'
docutils>=0.18; python_version>='3'
intervaltree
isort
mako>=1.0.0
paramiko>=1.15.2
pip>=6.0.8
pyelftools>=0.2.3
pyelftools>=0.29, <0.30; python_version<'3'
pyelftools>=0.29; python_version>='3'
pygments>=2.0
pypandoc
pyserial>=2.7
@@ -17,6 +19,6 @@ psutil
requests>=2.5.1
ropgadget>=5.3
sphinx==1.8.6; python_version<'3'
sphinx>=4.5.0; python_version>='3'
sphinx>=8.1.3, <9; python_version>='3'
sphinx_rtd_theme
sphinxcontrib-autoprogram<=0.1.5
+8 -6
View File
@@ -71,6 +71,7 @@ doctest_global_setup = '''
import sys, os
os.environ['PWNLIB_NOTERM'] = '1'
os.environ['PWNLIB_RANDOMIZE'] = '0'
import six
import pwnlib.update
import pwnlib.util.fiddling
import logging
@@ -97,8 +98,8 @@ pwnlib.context.ContextType.defaults['log_console'] = stdout()
github_actions = os.environ.get('USER') == 'runner'
travis_ci = os.environ.get('USER') == 'travis'
local_doctest = os.environ.get('USER') == 'pwntools'
branch_dev = os.environ.get('GITHUB_BASE_REF') == 'dev'
skip_android = True
is_python2 = six.PY2
'''
autoclass_content = 'both'
@@ -169,7 +170,7 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'default'
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
@@ -265,8 +266,8 @@ latex_documents = [
u'2016, Gallopsled et al.', 'manual'),
]
intersphinx_mapping = {'python': ('https://docs.python.org/3.8', None),
'paramiko': ('https://paramiko-docs.readthedocs.org/en/2.1/', None)}
intersphinx_mapping = {'python': ('https://docs.python.org/3/', None),
'paramiko': ('https://docs.paramiko.org/en/2.1/', None)}
# The name of an image file (relative to this directory) to place at the top of
# the title page.
@@ -359,6 +360,9 @@ def linkcode_resolve(domain, info):
else:
filename = info['module'].replace('.', '/') + '.py'
if isinstance(val, property):
val = val.fget
if isinstance(val, (types.ModuleType, types.MethodType, types.FunctionType, types.TracebackType, types.FrameType, types.CodeType) + six.class_types):
try:
lines, first = inspect.getsourcelines(val)
@@ -382,8 +386,6 @@ if build_dash:
html_theme_path = [alabaster.get_path()]
html_theme_options = { 'nosidebar' : True }
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# -- Customization to Sphinx autodoc generation --------------------------------------------
import sphinx.ext.autodoc
+1
View File
@@ -3,6 +3,7 @@
from pwn import *
from glob import glob
from pwnlib.elf.maps import CAT_PROC_MAPS_EXIT
import shutil
:mod:`pwnlib.elf.elf` --- ELF Files
===========================================================
+1 -1
View File
@@ -77,12 +77,12 @@ Each of the ``pwntools`` modules is documented here.
update
useragents
util/*
windbg
.. toctree::
:hidden:
testexample
rop/call
.. only:: not dash
+11 -10
View File
@@ -15,6 +15,9 @@ following system libraries installed.
install/*
Note: For Mac OS X you will need to have cmake ``brew install cmake`` and pkg-config ``brew install pkg-config`` installed.
Released Version
-----------------
@@ -25,8 +28,8 @@ Python3
.. code-block:: bash
$ apt-get update
$ apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
$ sudo apt-get update
$ sudo apt-get install python3 python3-pip python3-dev git libssl-dev libffi-dev build-essential
$ python3 -m pip install --upgrade pip
$ python3 -m pip install --upgrade pwntools
@@ -40,8 +43,8 @@ Additionally, due to `pip` dropping support for Python2, a specfic version of `p
.. code-block:: bash
$ apt-get update
$ apt-get install python python-pip python-dev git libssl-dev libffi-dev build-essential
$ sudo apt-get update
$ sudo apt-get install python python-pip python-dev git libssl-dev libffi-dev build-essential
$ python2 -m pip install --upgrade pip==20.3.4
$ python2 -m pip install --upgrade pwntools
@@ -51,13 +54,11 @@ Command-Line Tools
When installed with ``sudo`` the above commands will install Pwntools' command-line tools to somewhere like ``/usr/bin``.
However, if you run as an unprivileged user, you may see a warning message that looks like this:
However, if you run as an unprivileged user, you may see a warning message that looks like this::
.. code-block::
WARNING: The scripts asm, checksec, common, constgrep, cyclic, debug, disablenx, disasm,
elfdiff, elfpatch, errno, hex, main, phd, pwn, pwnstrip, scramble, shellcraft, template,
unhex, update and version are installed in '/home/user/.local/bin' which is not on PATH.
WARNING: The scripts asm, checksec, common, constgrep, cyclic, debug, disablenx, disasm,
elfdiff, elfpatch, errno, hex, main, phd, pwn, pwnstrip, scramble, shellcraft, template,
unhex, update and version are installed in '/home/user/.local/bin' which is not on PATH.
Follow the instructions listed and add ``~/.local/bin`` to your ``$PATH`` environment variable.
+14 -7
View File
@@ -18,28 +18,35 @@ Ubuntu Xenial (16.04) has official packages for most architectures, and does not
.. code-block:: bash
$ apt-get install software-properties-common
$ apt-add-repository ppa:pwntools/binutils
$ apt-get update
$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:pwntools/binutils
$ sudo apt-get update
Then, install the binutils for your architecture.
.. code-block:: bash
$ apt-get install binutils-$ARCH-linux-gnu
$ sudo apt-get install binutils-$ARCH-linux-gnu
Mac OS X
^^^^^^^^^^^^^^^^
Mac OS X is just as easy, but requires building binutils from source.
However, we've made ``homebrew`` recipes to make this a single command.
However, we've made ``homebrew`` recipes to make this just two commands.
After installing `brew <https://brew.sh>`__, grab the appropriate
recipe from our `binutils
repo <https://github.com/Gallopsled/pwntools-binutils/>`__.
.. code-block:: bash
$ brew install https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/macos/binutils-$ARCH.rb
$ wget https://raw.githubusercontent.com/Gallopsled/pwntools-binutils/master/macos/binutils-$ARCH.rb
$ brew install ./binutils-$ARCH.rb
Windows
^^^^^^^^^^^^^^^^
Windows support is experimental. You can try installing a prebuilt version of binutils
for your desired architecture from the `GNU Toolchains <https://gnutoolchains.com/>`__ project.
Alternate OSes
^^^^^^^^^^^^^^^^
@@ -54,7 +61,7 @@ OSes, ``binutils`` is simple to build by hand.
V=2.38 # Binutils Version
ARCH=arm # Target architecture
cd $TMP
cd ${TMPDIR:-/tmp}
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.gz
wget -nc https://ftp.gnu.org/gnu/binutils/binutils-$V.tar.gz.sig
+1 -1
View File
@@ -10,7 +10,7 @@ Ubuntu
.. code-block:: bash
$ apt-get install python-dev
$ sudo apt-get install python-dev
Mac OS X
^^^^^^^^^^^^^^^^
+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 ...'
+9
View File
@@ -1 +1,10 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.protocols.adb` --- Protocol implementations
========================================================
.. automodule:: pwnlib.protocols.adb
:members:
+19
View File
@@ -0,0 +1,19 @@
.. testsetup:: *
from pwn import *
context.clear(arch='riscv64')
:mod:`pwnlib.shellcraft.riscv64` --- Shellcode for RISCV64
==========================================================
:mod:`pwnlib.shellcraft.riscv64`
--------------------------------
.. automodule:: pwnlib.shellcraft.riscv64
:members:
:mod:`pwnlib.shellcraft.riscv64.linux`
--------------------------------------
.. automodule:: pwnlib.shellcraft.riscv64.linux
:members:
+9
View File
@@ -0,0 +1,9 @@
.. testsetup:: *
from pwn import *
:mod:`pwnlib.windbg` --- Working with WinDbg
============================================
.. automodule:: pwnlib.windbg
:members:
+1 -1
View File
@@ -4,5 +4,5 @@ Example showing `pwnlib.ui.options()`
from pwn import *
opts = [string.letters[x] for x in range(10)]
opts = [string.ascii_letters[x] for x in range(12)]
print('You choose "%s"' % opts[options('Pick one:', opts)])
+8 -4
View File
@@ -26,22 +26,26 @@ RUN apt-get update \
python3 \
python3-pip \
python3-dev \
python-is-python3 \
qemu-user-static \
binutils-arm-linux-gnueabihf \
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 \
patchelf \
&& locale-gen en_US.UTF-8 \
&& update-locale LANG=en_US.UTF-8 \
&& PYTHONPATH=`echo /usr/share/python-wheels/pip-*.whl` python2.7 -m pip install --upgrade pip setuptools wheel \
&& python2.7 -m pip install --upgrade pwntools \
&& python3 -m pip install --upgrade pip \
&& python3 -m pip install --upgrade pwntools \
&& PYTHONPATH=`echo /usr/share/python-wheels/pip-*.whl` python2.7 -m pip install --no-cache-dir --upgrade pip setuptools wheel \
&& python2.7 -m pip install --no-cache-dir --upgrade pwntools \
&& python3 -m pip install --no-cache-dir --upgrade pip \
&& python3 -m pip install --no-cache-dir --upgrade pwntools \
&& PWNLIB_NOTERM=1 pwn update \
&& useradd -m pwntools \
&& passwd --delete --unlock pwntools \
+2 -2
View File
@@ -1,7 +1,7 @@
FROM pwntools/pwntools:stable
USER root
RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@beta \
&& python3 -m pip install --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@beta
RUN python2.7 -m pip install --no-cache-dir --upgrade git+https://github.com/Gallopsled/pwntools@beta \
&& python3 -m pip install --no-cache-dir --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@beta
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+1 -1
View File
@@ -5,4 +5,4 @@ RUN apt-get -y dist-upgrade
RUN apt-get -y install python3 python3-pip
RUN apt-get -y install git wget unzip
RUN pip3 install --upgrade git+https://github.com/Gallopsled/pwntools@dev
RUN pip3 install --no-cache-dir --upgrade git+https://github.com/Gallopsled/pwntools@dev
+1 -1
View File
@@ -34,7 +34,7 @@ RUN python2.7 -m pip install -U ipython ipdb \
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ="UTC"
RUN sudo apt-get update && sudo -E apt-get install -y \
tzdata \
tzdata \
ash \
bash \
bash-static \
+2 -2
View File
@@ -1,7 +1,7 @@
FROM pwntools/pwntools:base
USER root
RUN python2.7 -m pip install --upgrade git+https://github.com/Gallopsled/pwntools@stable \
&& python3 -m pip install --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@stable
RUN python2.7 -m pip install --no-cache-dir --upgrade git+https://github.com/Gallopsled/pwntools@stable \
&& python3 -m pip install --no-cache-dir --force-reinstall --upgrade git+https://github.com/Gallopsled/pwntools@stable
RUN PWNLIB_NOTERM=1 pwn update
USER pwntools
+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
View File
@@ -36,6 +36,7 @@ __all__ = [
'util',
'update',
'version',
'windbg',
]
from . import args
+39 -6
View File
@@ -48,6 +48,8 @@ class ABI(object):
(32, 'mips', 'linux'): linux_mips,
(32, 'powerpc', 'linux'): linux_ppc,
(64, 'powerpc', 'linux'): linux_ppc64,
(32, 'riscv32', 'linux'): linux_riscv32,
(64, 'riscv64', 'linux'): linux_riscv64,
(32, 'i386', 'freebsd'): freebsd_i386,
(64, 'aarch64', 'freebsd'): freebsd_aarch64,
(64, 'amd64', 'freebsd'): freebsd_amd64,
@@ -58,6 +60,8 @@ class ABI(object):
(64, 'powerpc', 'freebsd'): freebsd_ppc64,
(32, 'i386', 'windows'): windows_i386,
(64, 'amd64', 'windows'): windows_amd64,
(64, 'amd64', 'darwin'): darwin_amd64,
(64, 'aarch64', 'darwin'): darwin_aarch64,
}[(context.bits, context.arch, context.os)]
@staticmethod
@@ -76,6 +80,8 @@ class ABI(object):
(64, 'aarch64', 'linux'): linux_aarch64_syscall,
(32, 'powerpc', 'linux'): linux_ppc_syscall,
(64, 'powerpc', 'linux'): linux_ppc64_syscall,
(32, 'riscv32', 'linux'): linux_riscv32_syscall,
(64, 'riscv64', 'linux'): linux_riscv64_syscall,
(32, 'i386', 'freebsd'): freebsd_i386_syscall,
(64, 'amd64', 'freebsd'): freebsd_amd64_syscall,
(64, 'aarch64', 'freebsd'): freebsd_aarch64_syscall,
@@ -85,6 +91,8 @@ class ABI(object):
(64, 'aarch64', 'freebsd'): freebsd_aarch64_syscall,
(32, 'powerpc', 'freebsd'): freebsd_ppc_syscall,
(64, 'powerpc', 'freebsd'): freebsd_ppc64_syscall,
(64, 'amd64', 'darwin'): darwin_amd64_syscall,
(64, 'aarch64', 'darwin'): darwin_aarch64_syscall,
}[(context.bits, context.arch, context.os)]
@staticmethod
@@ -99,13 +107,18 @@ class ABI(object):
(32, 'arm', 'linux'): linux_arm_sigreturn,
(32, 'thumb', 'linux'): linux_arm_sigreturn,
(64, 'aarch64', 'linux'): linux_aarch64_sigreturn,
(32, 'riscv32', 'linux'): linux_riscv32_sigreturn,
(64, 'riscv64', 'linux'): linux_riscv64_sigreturn,
(32, 'i386', 'freebsd'): freebsd_i386_sigreturn,
(64, 'amd64', 'freebsd'): freebsd_amd64_sigreturn,
(32, 'arm', 'freebsd'): freebsd_arm_sigreturn,
(32, 'thumb', 'freebsd'): freebsd_arm_sigreturn,
(64, 'aarch64', 'freebsd'): freebsd_aarch64_sigreturn,
(64, 'amd64', 'darwin'): darwin_amd64_sigreturn,
(64, 'aarch64', 'darwin'): darwin_aarch64_sigreturn,
}[(context.bits, context.arch, context.os)]
class SyscallABI(ABI):
"""
The syscall ABI treats the syscall number as the zeroth argument,
@@ -115,6 +128,7 @@ class SyscallABI(ABI):
super(SyscallABI, self).__init__(*a, **kw)
self.syscall_register = self.register_arguments[0]
class SigreturnABI(SyscallABI):
"""
The sigreturn ABI is similar to the syscall ABI, except that
@@ -132,6 +146,8 @@ linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3'], 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)
linux_riscv32 = ABI('sp', ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7'], 8, 0)
linux_riscv64 = ABI('sp', ['a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7'], 8, 0)
sysv_i386 = linux_i386
sysv_amd64 = linux_amd64
@@ -140,24 +156,33 @@ sysv_aarch64 = linux_aarch64
sysv_mips = linux_mips
sysv_ppc = linux_ppc
sysv_ppc64 = linux_ppc64
sysv_riscv32 = linux_riscv32
sysv_riscv64 = linux_riscv64
# Docs: https://man7.org/linux/man-pages/man2/syscall.2.html
linux_i386_syscall = SyscallABI('esp', ['eax', 'ebx', 'ecx', 'edx', 'esi', 'edi', 'ebp'], 4, 0)
linux_amd64_syscall = SyscallABI('rsp', ['rax', 'rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
linux_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
linux_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6'], 16, 0)
linux_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3', 'r4', 'r5', 'r6'], 4, 0)
linux_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5'], 16, 0)
linux_mips_syscall = SyscallABI('$sp', ['$v0','$a0','$a1','$a2','$a3'], 4, 0)
linux_ppc_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 4, 0)
linux_ppc64_syscall = ABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 8, 0)
linux_ppc_syscall = SyscallABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9'], 4, 0)
linux_ppc64_syscall = SyscallABI('sp', ['r0', 'r3', 'r4', 'r5', 'r6', 'r7', 'r8'], 8, 0)
linux_riscv32_syscall = SyscallABI('sp', ['a7', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5'], 4, 0)
linux_riscv64_syscall = SyscallABI('sp', ['a7', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5'], 8, 0)
linux_i386_sigreturn = SigreturnABI('esp', ['eax'], 4, 0)
linux_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 4, 0)
linux_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 8, 0)
linux_arm_sigreturn = SigreturnABI('sp', ['r7'], 4, 0)
linux_aarch64_sigreturn = SigreturnABI('sp', ['x8'], 16, 0)
linux_riscv32_sigreturn = SigreturnABI('sp', ['a7'], 4, 0)
linux_riscv64_sigreturn = SigreturnABI('sp', ['a7'], 8, 0)
sysv_i386_sigreturn = linux_i386_sigreturn
sysv_amd64_sigreturn = linux_amd64_sigreturn
sysv_arm_sigreturn = linux_arm_sigreturn
sysv_aarch64_sigreturn = linux_aarch64_sigreturn
sysv_riscv32_sigreturn = linux_riscv32_sigreturn
sysv_riscv64_sigreturn = linux_riscv64_sigreturn
freebsd_i386 = sysv_i386
freebsd_amd64 = sysv_amd64
@@ -168,7 +193,7 @@ freebsd_ppc = sysv_ppc
freebsd_ppc64 = sysv_ppc64
freebsd_i386_syscall = SyscallABI('esp', ['eax'], 4, 0)
freebsd_amd64_syscall = SyscallABI('rsp', ['rax','rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
freebsd_amd64_syscall = SyscallABI('rsp', ['rax','rdi','rsi','rdx','r10','r8','r9'], 8, 0)
freebsd_arm_syscall = SyscallABI('sp', ['r7', 'r0', 'r1', 'r2', 'r3'], 8, 0)
freebsd_aarch64_syscall = SyscallABI('sp', ['x8', 'x0', 'x1', 'x2', 'x3'], 16, 0)
freebsd_mips_syscall = SyscallABI('$sp', ['$v0','$a0','$a1','$a2','$a3'], 4, 0)
@@ -182,3 +207,11 @@ freebsd_aarch64_sigreturn = sysv_aarch64_sigreturn
windows_i386 = ABI('esp', [], 4, 0)
windows_amd64 = ABI('rsp', ['rcx','rdx','r8','r9'], 32, 32)
darwin_aarch64 = sysv_aarch64
darwin_aarch64_syscall = SyscallABI('sp', ['x16', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5'], 16, 0)
darwin_aarch64_sigreturn = SigreturnABI('sp', ['x16'], 16, 0)
darwin_amd64 = sysv_amd64
darwin_amd64_syscall = SyscallABI('rsp', ['rax', 'rdi', 'rsi', 'rdx', 'r10', 'r8', 'r9'], 8, 0)
darwin_amd64_sigreturn = SigreturnABI('rsp', ['rax'], 8, 0)
+36 -11
View File
@@ -66,6 +66,7 @@ from pwnlib import tubes
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.device import Device
from pwnlib.exception import PwnlibException
from pwnlib.log import getLogger
from pwnlib.protocols.adb import AdbClient
from pwnlib.util.packing import _decode
@@ -122,7 +123,7 @@ def current_device(any=False):
>>> device = adb.current_device(any=True)
>>> device # doctest: +ELLIPSIS
AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_...phone_armv7', model='sdk ...phone armv7', device='generic')
AdbDevice(serial='emulator-5554', type='device', port='emulator', product='sdk_...phone..._...', model='...', device='...')
>>> device.port
'emulator'
"""
@@ -252,13 +253,13 @@ class AdbDevice(Device):
>>> device = adb.wait_for_device()
>>> device.arch
'arm'
'amd64'
>>> device.bits
32
64
>>> device.os
'android'
>>> device.product # doctest: +ELLIPSIS
'sdk_...phone_armv7'
'sdk_...phone..._...'
>>> device.serial
'emulator-5554'
"""
@@ -879,7 +880,7 @@ def which(name, all = False, *a, **kw):
>>> adb.which('sh')
'/system/bin/sh'
>>> adb.which('sh', all=True)
['/system/bin/sh']
['/system/bin/sh', '/vendor/bin/sh']
>>> adb.which('foobar') is None
True
@@ -987,7 +988,7 @@ def proc_exe(pid):
:skipif: skip_android
>>> adb.proc_exe(1)
b'/init'
b'/system/bin/init'
"""
with context.quiet:
io = process(['realpath','/proc/%d/exe' % pid])
@@ -1274,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):
@@ -1364,7 +1385,7 @@ def compile(source):
>>> filename = adb.compile(temp)
>>> sent = adb.push(filename, "/data/local/tmp")
>>> adb.process(sent).recvall() # doctest: +ELLIPSIS
b'... /system/bin/linker\n...'
b'... /system/lib64/libc++.so\n...'
"""
ndk_build = misc.which('ndk-build')
@@ -1490,8 +1511,9 @@ class Partitions(object):
@context.quietfunc
def by_name_dir(self):
try:
return next(find('/dev/block/platform','by-name'))
except StopIteration:
with context.local(log_level=logging.FATAL):
return next(find('/dev/block/platform','by-name'))
except (StopIteration, PwnlibException):
return '/dev/block'
@context.quietfunc
@@ -1591,7 +1613,10 @@ def uninstall(package, *arguments):
@context.quietfunc
def packages():
"""Returns a list of packages installed on the system"""
packages = process(['pm', 'list', 'packages']).recvall()
# Decodes the received bytes as UTF-8 per:
# https://developer.android.com/reference/java/nio/charset/Charset#defaultCharset()
# where it is specified that UTF-8 is the default charset for Android.
packages = process(['pm', 'list', 'packages']).recvall().decode('utf-8')
return [line.split('package:', 1)[-1] for line in packages.splitlines()]
@context.quietfunc
+6
View File
@@ -159,6 +159,11 @@ def STDERR(v):
"""Sends logging to ``stderr`` by default, instead of ``stdout``"""
context.log_console = sys.stderr
def LOCAL_LIBCDB(v):
"""Sets path to local libc-database via ``context.local_libcdb``, e.g.
``LOCAL_LIBCDB='/path/to/libc-databse'``"""
context.local_libcdb = v
hooks = {
'LOG_LEVEL': LOG_LEVEL,
'LOG_FILE': LOG_FILE,
@@ -170,6 +175,7 @@ hooks = {
'NOASLR': NOASLR,
'NOPTRACE': NOPTRACE,
'STDERR': STDERR,
'LOCAL_LIBCDB': LOCAL_LIBCDB,
}
def initialize():
+141 -9
View File
@@ -59,6 +59,9 @@ from pwnlib import shellcraft
from pwnlib.context import LocalContext
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.util.hashes import sha1sumhex
from pwnlib.util.packing import _encode
from pwnlib.version import __version__
log = getLogger(__name__)
@@ -167,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):
@@ -175,6 +180,12 @@ def which_binutils(util, check_version=False):
"""
arch = context.arch
# Handle endianness-specific naming for mips/mips64
arch = {
('mips', 'little'): 'mipsel',
('mips64', 'little'): 'mips64el',
}.get((arch, context.endianness), arch)
# Fix up pwntools vs Debian triplet naming, and account
# for 'thumb' being its own pwntools architecture.
arches = [arch] + {
@@ -182,11 +193,14 @@ def which_binutils(util, check_version=False):
'i386': ['x86_64', 'amd64'],
'i686': ['x86_64', 'amd64'],
'amd64': ['x86_64', 'i386'],
'mips64': ['mips'],
'mips': ['mipsel'],
'mipsel': ['mips'],
'mips64': ['mips', 'mipsel'],
'mips64el': ['mipsel', 'mips'],
'powerpc64': ['powerpc'],
'sparc64': ['sparc'],
'riscv32': ['riscv32', 'riscv64', 'riscv'],
'riscv64': ['riscv64', 'riscv32', 'riscv'],
'riscv32': ['riscv64', 'riscv'],
'riscv64': ['riscv32', 'riscv'],
}.get(arch, [])
# If one of the candidate architectures matches the native
@@ -206,6 +220,9 @@ def which_binutils(util, check_version=False):
if platform.system() == 'Darwin':
utils = ['g'+util, util]
if platform.system() == 'Windows':
utils = [util + '.exe']
for arch in arches:
for gutil in utils:
# e.g. objdump
@@ -220,7 +237,7 @@ def which_binutils(util, check_version=False):
'%s-%s' % (arch, gutil)]
for pattern in patterns:
for dir in environ['PATH'].split(':'):
for dir in environ['PATH'].split(os.pathsep):
for res in sorted(glob(path.join(dir, pattern))):
if check_version:
ver = check_binutils_version(res)
@@ -457,18 +474,24 @@ def cpp(shellcode):
>>> cpp("SYS_setresuid", os = "freebsd")
'311\n'
"""
if platform.system() == 'Windows':
cpp = which_binutils('cpp')
else:
cpp = 'cpp'
code = _include_header() + shellcode
cmd = [
'cpp',
cpp,
'-Wno-unused-command-line-argument',
'-C',
'-nostdinc',
'-undef',
'-P',
'-I' + _incdir,
'/dev/stdin'
]
return _run(cmd, code).strip('\n').rstrip() + '\n'
@LocalContext
def make_elf_from_assembly(assembly,
vma=None,
@@ -651,6 +674,68 @@ def make_elf(data,
return retval
@LocalContext
def make_macho_from_assembly(shellcode):
return make_macho(shellcode, is_shellcode=True)
@LocalContext
def make_macho(data, is_shellcode=False):
prefix = []
if context.arch == 'amd64':
prefix = [
'.intel_syntax noprefix',
]
prefix.extend([
'.text',
'.global _start',
'_start:',
'.p2align 2',
])
code = ''
code += '\n'.join(prefix) + '\n'
if is_shellcode:
code += cpp(data)
else:
code += '.string "%s"' % ''.join('\\x%02x' % c for c in bytearray(data))
log.debug('Assembling\n%s' % code)
tmpdir = tempfile.mkdtemp(prefix = 'pwn-asm-')
step1 = path.join(tmpdir, 'step1')
step2 = path.join(tmpdir, 'step2')
step3 = path.join(tmpdir, 'step3')
with open(step1, 'w') as fd:
fd.write(code)
assembler = [
'/usr/bin/as',
]
asflags = [
'-mmacosx-version-min=11.0',
'-o', step2, step1,
]
_run(assembler + asflags)
linker = [
'/usr/bin/ld',
]
ldflags = [
'-macos_version_min', '11.0',
'-l', 'System',
'-e', '_start',
'-L', '/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib',
'-o', step3, step2,
]
_run(linker + ldflags)
os.chmod(step3, 0o755)
return step3
@LocalContext
def asm(shellcode, vma = 0, extract = True, shared = False):
r"""asm(code, vma = 0, extract = True, shared = False, ...) -> str
@@ -688,8 +773,23 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
b'0@*\x00'
>>> asm("la %r0, 42", arch = 's390', bits=64)
b'A\x00\x00*'
The output is cached:
>>> start = time.time()
>>> asm("lea rax, [rip+0]", arch = 'amd64', cache_dir = None) # force uncached time
b'H\x8d\x05\x00\x00\x00\x00'
>>> uncached_time = time.time() - start
>>> asm("lea rax, [rip+0]", arch = 'amd64') # cache it
b'H\x8d\x05\x00\x00\x00\x00'
>>> start = time.time()
>>> asm("lea rax, [rip+0]", arch = 'amd64')
b'H\x8d\x05\x00\x00\x00\x00'
>>> cached_time = time.time() - start
>>> uncached_time > cached_time
True
"""
result = ''
result = b''
assembler = _assembler()
linker = _linker()
@@ -700,6 +800,30 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
log.debug('Assembling\n%s' % code)
cache_file = None
if context.cache_dir:
cache_dir = os.path.join(context.cache_dir, 'asm-cache')
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
# Include the context in the hash in addition to the shellcode
hash_params = '{}_{}_{}_{}'.format(vma, extract, shared, __version__)
fingerprint_params = _encode(code) + _encode(hash_params) + _encode(' '.join(assembler)) + _encode(' '.join(linker)) + _encode(' '.join(objcopy))
asm_hash = sha1sumhex(fingerprint_params)
cache_file = os.path.join(cache_dir, asm_hash)
if os.path.exists(cache_file):
log.debug('Using cached assembly output from %r', cache_file)
if extract:
with open(cache_file, 'rb') as f:
return f.read()
# Create a temporary copy of the cached file to avoid modification.
tmpdir = tempfile.mkdtemp(prefix = 'pwn-asm-')
atexit.register(shutil.rmtree, tmpdir)
step3 = os.path.join(tmpdir, 'step3')
shutil.copy(cache_file, step3)
return step3
tmpdir = tempfile.mkdtemp(prefix = 'pwn-asm-')
step1 = path.join(tmpdir, 'step1')
step2 = path.join(tmpdir, 'step2')
@@ -747,6 +871,8 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
shutil.copy(step2, step3)
if not extract:
if cache_file is not None:
shutil.copy(step3, cache_file)
return step3
_run(objcopy + [step3, step4])
@@ -760,6 +886,10 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
else:
atexit.register(lambda: shutil.rmtree(tmpdir))
if cache_file is not None and result != b'':
with open(cache_file, 'wb') as f:
f.write(result)
return result
@LocalContext
@@ -792,8 +922,8 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
0: b8 17 00 00 00 mov eax, 0x17
>>> print(disasm(unhex('48c7c017000000'), arch = 'amd64'))
0: 48 c7 c0 17 00 00 00 mov rax, 0x17
>>> print(disasm(unhex('04001fe552009000'), arch = 'arm'))
0: e51f0004 ldr r0, [pc, #-4] ; 0x4
>>> print(disasm(unhex('04001fe552009000'), arch = 'arm')) # doctest: +ELLIPSIS
0: e51f0004 ldr r0, [pc, #-4] ...
4: 00900052 addseq r0, r0, r2, asr r0
>>> print(disasm(unhex('4ff00500'), arch = 'thumb', bits=32))
0: f04f 0005 mov.w r0, #5
@@ -860,6 +990,8 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
lines = []
# Note: those patterns are also used in pwnlib/commandline/disasm.py
pattern = '^( *[0-9a-f]+: *)', '((?:[0-9a-f]+ )+ *)', '(.*)'
if not byte:
pattern = pattern[::2]
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
__all__ = [
'asm',
'checksec',
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -138,4 +137,4 @@ def main(args):
args.output.write(b'\n')
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+5 -3
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -36,7 +35,10 @@ def main(args):
return
for f in files:
e = ELF(f.name)
try:
e = ELF(f.name)
except Exception as e:
print("{name}: {error}".format(name=f.name, error=e))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
common.main(__file__, main)
+20 -3
View File
@@ -25,8 +25,25 @@ parser = argparse.ArgumentParser(description='Pwntools Command-line Interface',
prog='pwn')
parser_commands = parser.add_subparsers(dest='command')
def main(file=sys.argv[0]):
import pwnlib.commandline.main
def main(file=sys.argv[0], command_main=None):
name = os.path.splitext(os.path.basename(file))[0]
if command_main is None:
import importlib
command_main = importlib.import_module('pwnlib.commandline.%s' % name).main
sys.argv.insert(1, name)
pwnlib.commandline.main.main()
entrypoint({name: command_main})
def deprecated_main():
file=sys.argv[0]
name = os.path.splitext(os.path.basename(file))[0]
import warnings
warnings.warn("The '%s' command is deprecated and will be removed in a future version. Please use 'pwn %s' instead." % (name, name), DeprecationWarning, stacklevel=2)
main(file)
def entrypoint(commands):
if len(sys.argv) < 2:
parser.print_usage()
sys.exit()
args = parser.parse_args()
with context.local(log_console = sys.stderr):
commands[args.command](args)
Executable → Regular
+7 -4
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -92,9 +91,13 @@ def main(args):
if not matcher.search(k):
continue
# Check if the value has proper type
val = getattr(mod, k)
if not isinstance(val, pwnlib.constants.constant.Constant):
continue
# Check the constant
if constant is not None:
val = getattr(mod, k)
if args.mask_mode:
if constant & val != val:
continue
@@ -103,7 +106,7 @@ def main(args):
continue
# Append it
out.append((getattr(mod, k), k))
out.append((val, k))
maxlen = max(len(k), maxlen)
# Output all matching constants
@@ -130,4 +133,4 @@ def main(args):
print('(%s) == %s' % (' | '.join(k for v, k in good), args.constant))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -108,4 +107,4 @@ def main(args):
out.write(b'\n')
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -103,4 +102,4 @@ def main(args):
gdb.debug(target, gdbscript=gdbscript, sysroot=args.sysroot).interactive()
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -25,4 +24,4 @@ def main(args):
ELF(e.path)
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+28 -12
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import re
import string
import sys
@@ -77,21 +77,37 @@ def main(args):
from pygments.formatters import TerminalFormatter
from pwnlib.lexer import PwntoolsLexer
offsets = disasm(dat, vma=safeeval.const(args.address), instructions=False, byte=False)
bytes = disasm(dat, vma=safeeval.const(args.address), instructions=False, offset=False)
instrs = disasm(dat, vma=safeeval.const(args.address), byte=False, offset=False)
# instrs = highlight(instrs, PwntoolsLexer(), TerminalFormatter())
dis = disasm(dat, vma=safeeval.const(args.address))
# Note: those patterns are copied from disasm function
pattern = '^( *[0-9a-f]+: *)((?:[0-9a-f]+ )+ *)(.*)'
lines = []
for line in dis.splitlines():
match = re.search(pattern, line)
if not match:
# Append as one element tuple
lines.append((line,))
continue
groups = match.groups()
o, b, i = groups
lines.append((o, b, i))
highlight_bytes = lambda t: ''.join(map(lambda x: x.replace('00', text.red('00')).replace('0a', text.red('0a')), group(2, t)))
for o,b,i in zip(*map(str.splitlines, (offsets, bytes, instrs))):
b = ' '.join(highlight_bytes(bb) for bb in b.split(' '))
i = highlight(i.strip(), PwntoolsLexer(), TerminalFormatter()).strip()
i = i.replace(',',', ')
print(o,b,i)
for line in lines:
if len(line) == 3:
o, b, i = line
b = ' '.join(highlight_bytes(bb) for bb in b.split(' '))
i = highlight(i.strip(), PwntoolsLexer(), TerminalFormatter()).strip()
i = i.replace(',',', ')
print(o,b,i)
else:
print(line[0])
return
print(disasm(dat, vma=safeeval.const(args.address)))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -60,4 +59,4 @@ def main(a):
print(diff(x, y))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -35,4 +34,4 @@ def main(a):
getattr(sys.stdout, 'buffer', sys.stdout).write(elf.get_data())
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -1
View File
@@ -46,4 +46,4 @@ def main(args):
print(os.strerror(value))
if __name__ == '__main__':
common.main(__file__)
common.main(__file__, main)
+28 -4
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -7,6 +6,7 @@ import sys
from pwnlib.commandline import common
from pwnlib.util.fiddling import enhex
from pwnlib.util.lists import group
parser = common.parser_commands.add_parser(
'hex',
@@ -16,14 +16,38 @@ parser = common.parser_commands.add_parser(
parser.add_argument('data', nargs='*',
help='Data to convert into hex')
parser.add_argument(
'-p', '--prefix',
metavar = 'prefix',
type = str,
default = '',
help = 'Insert a prefix before each byte',
)
parser.add_argument(
'-s', '--separator',
metavar = 'separator',
type = str,
default = '',
help = 'Add a separator between each byte',
)
def format_hex(hex_string, prefix, separator):
return separator.join([prefix + x for x in group(2, hex_string)])
def main(args):
if not args.data:
print(enhex(getattr(sys.stdin, 'buffer', sys.stdin).read()))
encoded = enhex(getattr(sys.stdin, 'buffer', sys.stdin).read())
else:
data = ' '.join(args.data)
if not hasattr(data, 'decode'):
data = data.encode('utf-8', 'surrogateescape')
print(enhex(data))
encoded = enhex(data)
if args.prefix or args.separator:
encoded = format_hex(encoded, args.prefix, args.separator)
print(encoded)
if __name__ == '__main__':
common.main(__file__)
common.main(__file__, main)
+113 -69
View File
@@ -37,19 +37,12 @@ lookup_parser.add_argument(
)
lookup_parser.add_argument(
'--download-libc',
'-d', '--download-libc',
action = 'store_true',
default = False,
help = 'Attempt to download the matching libc.so'
)
lookup_parser.add_argument(
'--unstrip',
action = 'store_true',
default = True,
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
lookup_parser.add_argument(
'--no-unstrip',
action = 'store_false',
@@ -57,6 +50,14 @@ lookup_parser.add_argument(
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
lookup_parser.add_argument(
'--offline-only',
action = 'store_true',
default = False,
dest = 'offline_only',
help = 'Attempt to searching with offline only mode'
)
hash_parser = libc_commands.add_parser(
'hash',
help = 'Display information of a libc version given an unique hash',
@@ -80,19 +81,12 @@ hash_parser.add_argument(
)
hash_parser.add_argument(
'--download-libc',
'-d', '--download-libc',
action = 'store_true',
default = False,
help = 'Attempt to download the matching libc.so'
)
hash_parser.add_argument(
'--unstrip',
action = 'store_true',
default = True,
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
hash_parser.add_argument(
'--no-unstrip',
action = 'store_false',
@@ -100,6 +94,14 @@ hash_parser.add_argument(
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
)
hash_parser.add_argument(
'--offline-only',
action = 'store_true',
default = False,
dest = 'offline_only',
help = 'Attempt to searching with offline only mode'
)
file_parser = libc_commands.add_parser(
'file',
help = 'Dump information about a libc binary',
@@ -130,25 +132,34 @@ file_parser.add_argument(
file_parser.add_argument(
'--unstrip',
action = 'store_true',
default = False,
dest = 'unstrip',
help = 'Attempt to unstrip the libc binary inplace with debug symbols from a debuginfod server'
)
fetch_parser = libc_commands.add_parser(
'fetch',
help = 'Fetch libc database',
description = 'Fetch libc database. If no argument passed, it will init and upgrade libc-database repository',
)
fetch_parser.add_argument(
'path',
nargs = '?',
default = context.local_libcdb,
help = 'Set libc-database path, If it is empty, the default path will be `context.local_libcdb` (%s)' % context.local_libcdb
)
fetch_parser.add_argument(
'-u', '--update',
metavar = 'update',
nargs = '+',
choices = ['all', 'ubuntu', 'debian', 'rpm', 'centos', 'arch', 'alpine', 'kali', 'parrotsec', 'launchpad'],
help = 'Fetch the desired libc categories'
)
common_symbols = ['dup2', 'printf', 'puts', 'read', 'system', 'write']
def find_libc(params):
import requests
url = "https://libc.rip/api/find"
result = requests.post(url, json=params, timeout=20)
log.debug('Request: %s', params)
log.debug('Result: %s', result.json())
if result.status_code != 200 or len(result.json()) == 0:
log.failure("Could not find libc for %s on libc.rip", params)
return []
return result.json()
def print_libc(libc):
def print_libc_info(libc):
log.info('%s', text.red(libc['id']))
log.indented('\t%-20s %s', text.green('BuildID:'), libc['buildid'])
log.indented('\t%-20s %s', text.green('MD5:'), libc['md5'])
@@ -158,14 +169,39 @@ def print_libc(libc):
for symbol in libc['symbols'].items():
log.indented('\t%25s = %s', symbol[0], symbol[1])
def handle_remote_libc(args, libc):
print_libc(libc)
if args.download_libc:
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
if path:
if args.unstrip:
libcdb.unstrip_libc(path)
shutil.copy(path, './{}.so'.format(libc['id']))
def print_libc_elf(exe):
from hashlib import md5, sha1, sha256
log.info('%s', text.red(os.path.basename(exe.path)))
libc_version = get_libc_version(exe)
if libc_version:
log.indented('%-20s %s', text.green('Version:'), libc_version)
if exe.buildid:
log.indented('%-20s %s', text.green('BuildID:'), enhex(exe.buildid))
log.indented('%-20s %s', text.green('MD5:'), md5(exe.data).hexdigest())
log.indented('%-20s %s', text.green('SHA1:'), sha1(exe.data).hexdigest())
log.indented('%-20s %s', text.green('SHA256:'), sha256(exe.data).hexdigest())
# Always dump the basic list of common symbols
log.indented('%s', text.green('Symbols:'))
synthetic_symbols = collect_synthetic_symbols(exe)
symbols = common_symbols + (args.symbols or []) + synthetic_symbols
symbols.sort()
for symbol in symbols:
if symbol not in exe.symbols:
log.indented('%25s = %s', symbol, text.red('not found'))
else:
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))
def get_libc_version(exe):
res = re.search(br'libc[ -](\d+\.\d+)', exe.data)
if res:
return res.group(1).decode()
return None
def translate_offset(offs, args, exe):
if args.offset:
@@ -176,8 +212,12 @@ def translate_offset(offs, args, exe):
return offs
def collect_synthetic_symbols(exe):
available_symbols = ['str_bin_sh']
exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
available_symbols = []
try:
exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
available_symbols.append('str_bin_sh')
except StopIteration:
pass
libc_start_main_return = exe.libc_start_main_return
if libc_start_main_return > 0:
@@ -196,52 +236,56 @@ def main(args):
if len(pairs) % 2 != 0:
log.failure('Uneven number of arguments. Please provide "symbol offset" pairs')
return
symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
matched_libcs = find_libc({'symbols': symbols})
matched_libcs = libcdb.search_by_symbol_offsets(symbols, offline_only=args.offline_only, return_raw=True)
for libc in matched_libcs:
handle_remote_libc(args, libc)
print_libc_info(libc)
if args.download_libc:
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
if path:
shutil.copy(path, './{}.so'.format(libc['id']))
elif args.libc_command == 'hash':
inverted_map = {v: k for k, v in libcdb.MAP_TYPES.items()}
hash_type = inverted_map.get(args.hash_type, args.hash_type)
for hash_value in args.hash_value:
matched_libcs = find_libc({args.hash_type: hash_value})
for libc in matched_libcs:
handle_remote_libc(args, libc)
path = libcdb.search_by_hash(hash_value, hash_type, unstrip=args.unstrip, offline_only=args.offline_only)
exe = ELF(path, checksec=False)
print_libc_elf(exe)
if args.download_libc:
# if we cannot get actual libc version then copy with cache name
shutil.copy(path, './libc-{}.so'.format(get_libc_version(exe) or Path(path).stem))
elif args.libc_command == 'file':
from hashlib import md5, sha1, sha256
for file in args.files:
if not os.path.exists(file) or not os.path.isfile(file):
log.failure('File does not exist %s', args.file)
continue
if args.unstrip:
libcdb.unstrip_libc(file)
exe = ELF(file, checksec=False)
log.info('%s', text.red(os.path.basename(file)))
print_libc_elf(ELF(file, checksec=False))
libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data)
if libc_version:
log.indented('%-20s %s', text.green('Version:'), libc_version.group(1).decode())
elif args.libc_command == 'fetch':
if exe.buildid:
log.indented('%-20s %s', text.green('BuildID:'), enhex(exe.buildid))
log.indented('%-20s %s', text.green('MD5:'), md5(exe.data).hexdigest())
log.indented('%-20s %s', text.green('SHA1:'), sha1(exe.data).hexdigest())
log.indented('%-20s %s', text.green('SHA256:'), sha256(exe.data).hexdigest())
if args.update:
subprocess.check_call(['./get'] + args.update, cwd=args.path)
# Always dump the basic list of common symbols
log.indented('%s', text.green('Symbols:'))
synthetic_symbols = collect_synthetic_symbols(exe)
else:
if not Path(args.path).exists():
if yesno("Would you like to initialize the libc-database repository? "
"If the path already exists, this prompt will not display, and automatically upgrade repository."):
log.waitfor("init libc-database repository")
subprocess.check_call(['git', 'clone', 'https://github.com/niklasb/libc-database/', args.path])
else:
log.waitfor("upgrade libc-database repository")
subprocess.check_call(['git', 'pull'], cwd=args.path)
symbols = common_symbols + (args.symbols or []) + synthetic_symbols
symbols.sort()
for symbol in symbols:
if symbol not in exe.symbols:
log.indented('%25s = %s', symbol, text.red('not found'))
else:
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+2 -10
View File
@@ -1,7 +1,5 @@
from __future__ import absolute_import
import sys
from pwnlib.commandline import asm
from pwnlib.commandline import checksec
from pwnlib.commandline import common
@@ -23,8 +21,7 @@ from pwnlib.commandline import template
from pwnlib.commandline import unhex
from pwnlib.commandline import update
from pwnlib.commandline import version
from pwnlib.commandline.common import parser
from pwnlib.context import context
from pwnlib.commandline.common import parser as parser
commands = {
'asm': asm.main,
@@ -50,12 +47,7 @@ commands = {
}
def main():
if len(sys.argv) < 2:
parser.print_usage()
sys.exit()
args = parser.parse_args()
with context.local(log_console = sys.stderr):
commands[args.command](args)
common.entrypoint(commands)
if __name__ == '__main__':
main()
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -110,4 +109,4 @@ def main(args):
pass
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -1
View File
@@ -53,4 +53,4 @@ def main(args):
args.output.write(result)
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -1
View File
@@ -110,4 +110,4 @@ def main(args):
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+112 -81
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -72,17 +71,9 @@ p.add_argument(
p.add_argument(
'shellcode',
nargs = '?',
help = 'The shellcode you want',
type = str
)
p.add_argument(
'args',
nargs = '*',
metavar = 'arg',
default = (),
help = 'Argument to the chosen shellcode',
help = 'The shellcodes you want. shellcode [args ...] [+ shellcode [args ...]]',
type = str
)
p.add_argument(
@@ -92,6 +83,12 @@ p.add_argument(
action='store_true'
)
p.add_argument(
'--delim',
help='Set the delimiter between multilple shellcodes',
default='+'
)
p.add_argument(
'-b',
'--before',
@@ -173,24 +170,50 @@ p.add_argument(
help='Generated ELF is a shared library'
)
def get_template(name):
func = shellcraft
for attr in name.split('.'):
func = getattr(func, attr)
return func
def get_template(shellcodes):
funcs = []
for shellcode in shellcodes:
func = shellcraft
cur_name = shellcode[0]
args = []
if len(shellcode) > 1:
args = shellcode[1:]
for attr in cur_name.split('.'):
func = getattr(func, attr)
funcs.append((cur_name, func, args))
return funcs
def is_not_a_syscall_template(name):
template_src = shellcraft._get_source(name)
return '/syscalls' not in template_src
def main(args):
delim = '+'
if args.delim:
delim = args.delim.strip()
shellcodes = []
if args.shellcode:
current = []
for s in args.shellcode:
if s.strip() == delim:
shellcodes.append(current)
current = []
else:
current.append(s)
if len(current) > 0:
shellcodes.append(current)
if args.list:
templates = shellcraft.templates
if args.shellcode:
templates = filter(lambda a: args.shellcode in a, templates)
template_array = []
for s in shellcodes:
template_array.extend(list(filter(lambda a: s[0] in a, templates)))
templates = template_array
elif not args.syscalls:
templates = filter(is_not_a_syscall_template, templates)
templates = list(filter(is_not_a_syscall_template, templates))
print('\n'.join(templates))
exit()
@@ -200,84 +223,92 @@ def main(args):
exit()
try:
func = get_template(args.shellcode)
funcs = get_template(shellcodes)
except AttributeError:
log.error("Unknown shellcraft template %r. Use --list to see available shellcodes." % args.shellcode)
if args.show:
# remove doctests
doc = []
in_doctest = False
block_indent = None
caption = None
lines = func.__doc__.splitlines()
i = 0
while i < len(lines):
line = lines[i]
if line.lstrip().startswith('>>>'):
# this line starts a doctest
in_doctest = True
block_indent = None
if caption:
# delete back up to the caption
doc = doc[:caption - i]
caption = None
elif line == '':
# skip blank lines
pass
elif in_doctest:
# indentation marks the end of a doctest
indent = len(line) - len(line.lstrip())
if block_indent is None:
if not line.lstrip().startswith('...'):
block_indent = indent
elif indent < block_indent:
in_doctest = False
for (name, func, _args) in funcs:
# remove doctests
doc = []
in_doctest = False
block_indent = None
caption = None
lines = func.__doc__.splitlines()
i = 0
if len(funcs) > 1:
print('%s:' % name)
while i < len(lines):
line = lines[i]
if line.lstrip().startswith('>>>'):
# this line starts a doctest
in_doctest = True
block_indent = None
# re-evalutate this line
continue
elif line.endswith(':'):
# save index of caption
caption = i
else:
# this is not blank space and we're not in a doctest, so the
# previous caption (if any) was not for a doctest
caption = None
if caption:
# delete back up to the caption
doc = doc[:caption - i]
caption = None
elif line == '':
# skip blank lines
pass
elif in_doctest:
# indentation marks the end of a doctest
indent = len(line) - len(line.lstrip())
if block_indent is None:
if not line.lstrip().startswith('...'):
block_indent = indent
elif indent < block_indent:
in_doctest = False
block_indent = None
# re-evalutate this line
continue
elif line.endswith(':'):
# save index of caption
caption = i
else:
# this is not blank space and we're not in a doctest, so the
# previous caption (if any) was not for a doctest
caption = None
if not in_doctest:
doc.append(line)
i += 1
print('\n'.join(doc).rstrip())
if not in_doctest:
doc.append(line)
i += 1
print('\n'.join(doc).rstrip())
if len(funcs) > 1:
print('')
exit()
defargs = len(six.get_function_defaults(func) or ())
reqargs = six.get_function_code(func).co_argcount - defargs
if len(args.args) < reqargs:
if defargs > 0:
log.critical('%s takes at least %d arguments' % (args.shellcode, reqargs))
sys.exit(1)
else:
log.critical('%s takes exactly %d arguments' % (args.shellcode, reqargs))
sys.exit(1)
code_array = []
for (name, func, func_args) in funcs:
defargs = len(six.get_function_defaults(func) or ())
reqargs = six.get_function_code(func).co_argcount - defargs
# Captain uglyness saves the day!
for i, val in enumerate(args.args):
try:
args.args[i] = util.safeeval.expr(val)
except ValueError:
pass
if len(func_args) < reqargs:
if defargs > 0:
log.critical('%s takes at least %d arguments' % (name, reqargs))
sys.exit(1)
else:
log.critical('%s takes exactly %d arguments' % (name, reqargs))
sys.exit(1)
# And he strikes again!
list(map(common.context_arg, args.shellcode.split('.')))
code = func(*args.args)
# Captain uglyness saves the day!
for i, val in enumerate(func_args):
try:
func_args[i] = util.safeeval.expr(val)
except ValueError:
pass
# And he strikes again!
list(map(common.context_arg, name.split('.')))
code_array.append(func(*func_args))
code = "".join(code_array)
if args.before:
code = shellcraft.trap() + code
if args.after:
code = code + shellcraft.trap()
if args.format in ['a', 'asm', 'assembly']:
if args.color:
from pygments import highlight
@@ -320,7 +351,7 @@ def main(args):
else:
args.format = 'raw'
arch = args.shellcode.split('.')[0]
arch = name.split('.')[0]
if args.debug:
if not args.avoid:
@@ -353,4 +384,4 @@ def main(args):
args.out.write(code)
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
Executable → Regular
+177 -16
View File
@@ -1,30 +1,183 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from pwn import *
from pwnlib.commandline import common
from pwnlib.util.misc import which, parse_ldd_output, write
from mako.lookup import TemplateLookup
from sys import stderr
from mako.lookup import TemplateLookup, Template
parser = common.parser_commands.add_parser(
'template',
help = 'Generate an exploit template',
description = 'Generate an exploit template'
description = 'Generate an exploit template. If no arguments are given, '
'the current directory is searched for an executable binary and '
'libc. If only one binary is found, it is assumed to be the '
'challenge binary.'
)
parser.add_argument('exe', nargs='?', help='Target binary')
# change path to hardcoded one when building the documentation
printable_data_path = "pwnlib/data" if 'sphinx' in sys.modules else pwnlib.data.path
parser.add_argument('exe', nargs='?', help='Target binary. If not given, the current directory is searched for an executable binary.')
parser.add_argument('--host', help='Remote host / SSH server')
parser.add_argument('--port', help='Remote port / SSH port', type=int)
parser.add_argument('--user', help='SSH Username')
parser.add_argument('--pass', '--password', help='SSH Password', dest='password')
parser.add_argument('--libc', help='Path to libc binary to use. If not given, the current directory is searched for a libc binary.')
parser.add_argument('--path', help='Remote path of file on SSH server')
parser.add_argument('--quiet', help='Less verbose template comments', action='store_true')
parser.add_argument('--color', help='Print the output in color', choices=['never', 'always', 'auto'], default='auto')
parser.add_argument('--template', help='Path to a custom template. Tries to use \'~/.config/pwntools/templates/pwnup.mako\', if it exists. '
'Check \'%s\' for the default template shipped with pwntools.' %
os.path.join(printable_data_path, "templates", "pwnup.mako"))
parser.add_argument('--no-auto', help='Do not automatically detect missing binaries', action='store_false', dest='auto')
def get_docker_image_libraries():
"""Tries to retrieve challenge libraries from a Docker image built from the Dockerfile in the current working directory.
The libraries are retrieved by parsing the output of running ldd on /bin/sh.
Supports regular Docker images as well as jail images.
"""
with log.progress("Extracting challenge libraries from Docker image") as progress:
if not which("docker"):
progress.failure("docker command not found")
return None, None
# maps jail image name to the root directory of the child image
jail_image_to_chroot_dir = {
"pwn.red/jail": "/srv",
}
dockerfile = open("Dockerfile", "r").read()
jail = None
chroot_dir = "/"
for jail_image in jail_image_to_chroot_dir:
if re.search(r"^FROM %s" % jail_image, dockerfile, re.MULTILINE):
jail = jail_image
chroot_dir = jail_image_to_chroot_dir[jail_image]
break
try:
progress.status("Building image")
image_sha = subprocess.check_output(["docker", "build", "-q", "."], stderr=subprocess.PIPE, shell=False).decode().strip()
progress.status("Retrieving library paths")
ldd_command = ["-c", "chroot %s /bin/sh -c 'ldd /bin/sh'" % chroot_dir]
ldd_output = subprocess.check_output([
"docker",
"run",
"--rm",
"--entrypoint",
"/bin/sh",
] + (["--privileged"] if jail else []) + [
image_sha,
] + ldd_command,
stderr=subprocess.PIPE,
shell=False
).decode()
libc, ld = None, None
libc_basename, ld_basename = None, None
for lib_path in parse_ldd_output(ldd_output):
if "libc." in lib_path:
libc = lib_path
libc_basename = os.path.basename(lib_path)
if "ld-" in lib_path:
ld = lib_path
ld_basename = os.path.basename(lib_path)
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)]
contents = subprocess.check_output([
"docker",
"run",
"--rm",
"--entrypoint",
"/bin/sh",
] + (["--privileged"] if jail else []) + [
image_sha
] + cat_command,
stderr=subprocess.PIPE,
shell=False
)
write(basename, contents)
except subprocess.CalledProcessError as e:
print(e.stderr.decode())
log.error("docker failed with status: %d" % e.returncode)
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
has_dockerfile = False
other_files = []
for filename in os.listdir("."):
if not os.path.isfile(filename):
continue
if not libc and ('libc-' in filename or 'libc.' in filename):
libc = filename
elif not ld and 'ld-' in filename:
ld = filename
elif filename == "Dockerfile":
has_dockerfile = True
else:
if os.access(filename, os.X_OK):
other_files.append(filename)
if not exe:
if len(other_files) == 1:
exe = other_files[0]
elif len(other_files) > 1:
log.warning("Failed to find challenge binary. There are multiple binaries in the current directory: %s", other_files)
# 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:
log.success("Found challenge binary %r", exe)
if libc != args.libc:
log.success("Found libc binary %r", libc)
return exe, libc
def main(args):
lookup = TemplateLookup(
directories = [os.path.join(pwnlib.data.path, 'templates')],
directories = [
os.path.expanduser('~/.config/pwntools/templates/'),
os.path.join(pwnlib.data.path, 'templates')
],
module_directory = None
)
@@ -34,25 +187,32 @@ def main(args):
if not (args.path or args.exe):
log.error("Must specify --path or a exe")
s = ssh(args.user, args.host, args.port or 22, args.password or None)
try:
remote_file = args.path or args.exe
s.download(remote_file)
except Exception:
log.warning("Could not download file %r, opening a shell", remote_file)
s.interactive()
return
with ssh(args.user, args.host, args.port or 22, args.password or None) as s:
try:
remote_file = args.path or args.exe
s.download(remote_file)
except Exception:
log.warning("Could not download file %r, opening a shell", remote_file)
s.interactive()
return
if not args.exe:
args.exe = os.path.basename(args.path)
template = lookup.get_template('pwnup.mako')
if args.auto and (args.exe is None or args.libc is None):
args.exe, args.libc = detect_missing_binaries(args)
if args.template:
template = Template(filename=args.template) # Failing on invalid file is ok
else:
template = lookup.get_template('pwnup.mako')
output = template.render(args.exe,
args.host,
args.port,
args.user,
args.password,
args.libc,
args.path,
args.quiet)
@@ -74,4 +234,5 @@ def main(args):
except OSError: pass
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python2
from __future__ import absolute_import
from __future__ import division
@@ -31,4 +30,4 @@ def main(args):
raise
if __name__ == '__main__':
common.main(__file__)
common.main(__file__, main)
+1 -1
View File
@@ -30,4 +30,4 @@ def main(a):
subprocess.check_call(result, shell=False)
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
+1 -1
View File
@@ -29,4 +29,4 @@ def main(a):
log.info("Pwntools v%s" % version)
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
pwnlib.commandline.common.main(__file__, main)
View File
Whitespace-only changes.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+153 -18
View File
@@ -302,7 +302,7 @@ class ContextType(object):
>>> context.os == 'linux'
True
>>> context.arch = 'arm'
>>> vars(context) == {'arch': 'arm', 'bits': 32, 'endian': 'little', 'os': 'linux'}
>>> vars(context) == {'arch': 'arm', 'bits': 32, 'endian': 'little', 'os': 'linux', 'newline': b'\n'}
True
>>> context.endian
'little'
@@ -359,13 +359,16 @@ class ContextType(object):
'encoding': 'auto',
'endian': 'little',
'gdbinit': "",
'gdb_binary': "",
'kernel': None,
'local_libcdb': "/var/lib/libc-database",
'log_level': logging.INFO,
'log_file': _devnull(),
'log_console': sys.stdout,
'randomize': False,
'rename_corefiles': True,
'newline': b'\n',
'throw_eof_on_incomplete_line': None,
'noptrace': False,
'os': 'linux',
'proxy': None,
@@ -375,8 +378,19 @@ class ContextType(object):
'timeout': Timeout.maximum,
}
#: Valid values for :meth:`pwnlib.context.ContextType.os`
oses = sorted(('linux','freebsd','windows','cgc','android','baremetal'))
unix_like = {'newline': b'\n'}
windows_like = {'newline': b'\r\n'}
#: Keys are valid values for :meth:`pwnlib.context.ContextType.os`
oses = _longest({
'linux': unix_like,
'freebsd': unix_like,
'windows': windows_like,
'cgc': unix_like,
'android': unix_like,
'baremetal': unix_like,
'darwin': unix_like,
})
big_32 = {'endian': 'big', 'bits': 32}
big_64 = {'endian': 'big', 'bits': 64}
@@ -445,14 +459,14 @@ class ContextType(object):
def copy(self):
"""copy() -> dict
r"""copy() -> dict
Returns a copy of the current context as a dictionary.
Examples:
>>> context.clear()
>>> context.os = 'linux'
>>> vars(context) == {'os': 'linux'}
>>> vars(context) == {'os': 'linux', 'newline': b'\n'}
True
"""
return self._tls.copy()
@@ -581,9 +595,9 @@ class ContextType(object):
... log.debug("DEBUG")
... log.info("INFO")
... log.warn("WARN")
[DEBUG] DEBUG
[*] INFO
[!] WARN
[...] DEBUG
[...] INFO
[...] WARN
"""
level = 'error'
if context.log_level <= logging.DEBUG:
@@ -651,7 +665,7 @@ class ContextType(object):
information is printed.
>>> with context.verbose: func()
[DEBUG] Hello
[...] Hello
"""
return self.local(log_level='debug')
@@ -787,7 +801,7 @@ class ContextType(object):
try:
defaults = self.architectures[arch]
except KeyError:
raise AttributeError('AttributeError: arch must be one of %r' % sorted(self.architectures))
raise AttributeError('AttributeError: arch (%r) must be one of %r' % (arch, sorted(self.architectures)))
for k,v in defaults.items():
if k not in self._tls:
@@ -830,6 +844,7 @@ class ContextType(object):
The default value is ``32``, but changes according to :attr:`arch`.
Examples:
>>> context.clear()
>>> context.bits == 32
True
@@ -1020,6 +1035,8 @@ class ContextType(object):
"""
if isinstance(value, (bytes, six.text_type)):
# check if mode was specified as "[value],[mode]"
from pwnlib.util.packing import _need_text
value = _need_text(value)
if ',' not in value:
value += ',a'
filename, mode = value.rsplit(',', 1)
@@ -1070,31 +1087,105 @@ class ContextType(object):
stream = open(stream, 'wt')
return stream
@_validator
def local_libcdb(self, path):
"""
Sets path to local libc-database, get more information for libc-database:
https://github.com/niklasb/libc-database
Works in :attr:`pwnlib.libcdb` when searching by local database provider.
The default value is ``/var/lib/libc-database``.
Sets `context.local_libcdb` to empty string or `None` will turn off local libc-database integration.
Examples:
>>> context.local_libcdb = pwnlib.data.elf.path
>>> context.local_libcdb = 'foobar'
Traceback (most recent call last):
...
AttributeError: 'foobar' does not exist, please download libc-database first
"""
if not os.path.isdir(path):
raise AttributeError("'%s' does not exist, please download libc-database first" % path)
return path
@property
def mask(self):
return (1 << self.bits) - 1
@_validator
def os(self, os):
"""
r"""
Operating system of the target machine.
The default value is ``linux``.
Allowed values are listed in :attr:`pwnlib.context.ContextType.oses`.
Side Effects:
If an os is specified some attributes will be set on the context
if a user has not already set a value.
The following property may be modified:
- :attr:`newline`
Raises:
AttributeError: An invalid os was specified
Examples:
>>> context.os = 'linux'
>>> context.clear()
>>> context.os == 'linux' # Default os
True
>>> context.os = 'freebsd'
>>> context.os == 'freebsd'
True
>>> context.os = 'foobar' #doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: os must be one of ['android', 'baremetal', 'cgc', 'freebsd', 'linux', 'windows']
>>> context.clear()
>>> context.newline == b'\n' # Default value
True
>>> context.os = 'windows'
>>> context.newline == b'\r\n' # New value
True
Note that expressly setting :attr:`newline` means that we use
that value instead of the default
>>> context.clear()
>>> context.newline = b'\n'
>>> context.os = 'windows'
>>> context.newline == b'\n'
True
Setting the os can override the default for :attr:`newline`
>>> context.clear()
>>> context.os = 'windows'
>>> vars(context) == {'os': 'windows', 'newline': b'\r\n'}
True
"""
os = os.lower()
if os not in self.oses:
raise AttributeError("os must be one of %r" % self.oses)
try:
defaults = self.oses[os]
except KeyError:
raise AttributeError("os must be one of %r" % sorted(self.oses))
for k,v in defaults.items():
if k not in self._tls:
self._tls[k] = v
return os
@@ -1287,9 +1378,9 @@ class ContextType(object):
def cache_dir_base(self, new_base):
"""Base directory to use for caching content.
Changing this to a different value will clear the `cache_dir` path
Changing this to a different value will clear the :attr:`cache_dir` path
stored in TLS since a new path will need to be generated to respect the
new `cache_dir_base` value.
new :attr:`cache_dir_base` value.
"""
if new_base != self.cache_dir_base:
@@ -1304,6 +1395,9 @@ class ContextType(object):
Note:
May be either a path string, or :const:`None`.
Set to :const:`None` to disable caching.
Set to :const:`True` to generate the default cache directory path
based on :attr:`cache_dir_base` again.
Example:
@@ -1311,12 +1405,18 @@ class ContextType(object):
>>> cache_dir is not None
True
>>> os.chmod(cache_dir, 0o000)
>>> del context._tls['cache_dir']
>>> context.cache_dir = True
>>> context.cache_dir is None
True
>>> os.chmod(cache_dir, 0o755)
>>> cache_dir == context.cache_dir
True
>>> context.cache_dir = None
>>> context.cache_dir is None
True
>>> context.cache_dir = True
>>> context.cache_dir is not None
True
"""
try:
# If the TLS already has a cache directory path, we return it
@@ -1361,7 +1461,9 @@ class ContextType(object):
@cache_dir.setter
def cache_dir(self, v):
if os.access(v, os.W_OK):
if v is True:
del self._tls["cache_dir"]
elif v is None or os.access(v, os.W_OK):
# Stash this in TLS for later reuse
self._tls["cache_dir"] = v
@@ -1401,6 +1503,25 @@ class ContextType(object):
# circular imports
from pwnlib.util.packing import _need_bytes
return _need_bytes(v)
@_validator
def throw_eof_on_incomplete_line(self, v):
"""Whether to raise an :class:`EOFError` if an EOF is received before a newline in ``tube.recvline``.
Controls if an :class:`EOFError` is treated as newline in ``tube.recvline`` and similar functions
and whether a warning should be logged about it.
Possible values are:
- ``True``: Raise an :class:`EOFError` if an EOF is received before a newline.
- ``False``: Return the data received so far if an EOF is received
before a newline without logging a warning.
- ``None``: Return the data received so far if an EOF is received
before a newline and log a warning.
Default value is ``None``.
"""
return v if v is None else bool(v)
@_validator
@@ -1419,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.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,304 @@
#define __NR_io_setup 0
#define __NR_io_destroy 1
#define __NR_io_submit 2
#define __NR_io_cancel 3
#define __NR_io_getevents 4
#define __NR_setxattr 5
#define __NR_lsetxattr 6
#define __NR_fsetxattr 7
#define __NR_getxattr 8
#define __NR_lgetxattr 9
#define __NR_fgetxattr 10
#define __NR_listxattr 11
#define __NR_llistxattr 12
#define __NR_flistxattr 13
#define __NR_removexattr 14
#define __NR_lremovexattr 15
#define __NR_fremovexattr 16
#define __NR_getcwd 17
#define __NR_lookup_dcookie 18
#define __NR_eventfd2 19
#define __NR_epoll_create1 20
#define __NR_epoll_ctl 21
#define __NR_epoll_pwait 22
#define __NR_dup 23
#define __NR_dup3 24
#define __NR_fcntl 25
#define __NR_inotify_init1 26
#define __NR_inotify_add_watch 27
#define __NR_inotify_rm_watch 28
#define __NR_ioctl 29
#define __NR_ioprio_set 30
#define __NR_ioprio_get 31
#define __NR_flock 32
#define __NR_mknodat 33
#define __NR_mkdirat 34
#define __NR_unlinkat 35
#define __NR_symlinkat 36
#define __NR_linkat 37
#define __NR_umount2 39
#define __NR_mount 40
#define __NR_pivot_root 41
#define __NR_nfsservctl 42
#define __NR_statfs 43
#define __NR_fstatfs 44
#define __NR_truncate 45
#define __NR_ftruncate 46
#define __NR_fallocate 47
#define __NR_faccessat 48
#define __NR_chdir 49
#define __NR_fchdir 50
#define __NR_chroot 51
#define __NR_fchmod 52
#define __NR_fchmodat 53
#define __NR_fchownat 54
#define __NR_fchown 55
#define __NR_openat 56
#define __NR_close 57
#define __NR_vhangup 58
#define __NR_pipe2 59
#define __NR_quotactl 60
#define __NR_getdents64 61
#define __NR_lseek 62
#define __NR_read 63
#define __NR_write 64
#define __NR_readv 65
#define __NR_writev 66
#define __NR_pread64 67
#define __NR_pwrite64 68
#define __NR_preadv 69
#define __NR_pwritev 70
#define __NR_sendfile 71
#define __NR_pselect6 72
#define __NR_ppoll 73
#define __NR_signalfd4 74
#define __NR_vmsplice 75
#define __NR_splice 76
#define __NR_tee 77
#define __NR_readlinkat 78
#define __NR_newfstatat 79
#define __NR_fstat 80
#define __NR_sync 81
#define __NR_fsync 82
#define __NR_fdatasync 83
#define __NR_sync_file_range 84
#define __NR_timerfd_create 85
#define __NR_timerfd_settime 86
#define __NR_timerfd_gettime 87
#define __NR_utimensat 88
#define __NR_acct 89
#define __NR_capget 90
#define __NR_capset 91
#define __NR_personality 92
#define __NR_exit 93
#define __NR_exit_group 94
#define __NR_waitid 95
#define __NR_set_tid_address 96
#define __NR_unshare 97
#define __NR_futex 98
#define __NR_set_robust_list 99
#define __NR_get_robust_list 100
#define __NR_nanosleep 101
#define __NR_getitimer 102
#define __NR_setitimer 103
#define __NR_kexec_load 104
#define __NR_init_module 105
#define __NR_delete_module 106
#define __NR_timer_create 107
#define __NR_timer_gettime 108
#define __NR_timer_getoverrun 109
#define __NR_timer_settime 110
#define __NR_timer_delete 111
#define __NR_clock_settime 112
#define __NR_clock_gettime 113
#define __NR_clock_getres 114
#define __NR_clock_nanosleep 115
#define __NR_syslog 116
#define __NR_ptrace 117
#define __NR_sched_setparam 118
#define __NR_sched_setscheduler 119
#define __NR_sched_getscheduler 120
#define __NR_sched_getparam 121
#define __NR_sched_setaffinity 122
#define __NR_sched_getaffinity 123
#define __NR_sched_yield 124
#define __NR_sched_get_priority_max 125
#define __NR_sched_get_priority_min 126
#define __NR_sched_rr_get_interval 127
#define __NR_restart_syscall 128
#define __NR_kill 129
#define __NR_tkill 130
#define __NR_tgkill 131
#define __NR_sigaltstack 132
#define __NR_rt_sigsuspend 133
#define __NR_rt_sigaction 134
#define __NR_rt_sigprocmask 135
#define __NR_rt_sigpending 136
#define __NR_rt_sigtimedwait 137
#define __NR_rt_sigqueueinfo 138
#define __NR_rt_sigreturn 139
#define __NR_setpriority 140
#define __NR_getpriority 141
#define __NR_reboot 142
#define __NR_setregid 143
#define __NR_setgid 144
#define __NR_setreuid 145
#define __NR_setuid 146
#define __NR_setresuid 147
#define __NR_getresuid 148
#define __NR_setresgid 149
#define __NR_getresgid 150
#define __NR_setfsuid 151
#define __NR_setfsgid 152
#define __NR_times 153
#define __NR_setpgid 154
#define __NR_getpgid 155
#define __NR_getsid 156
#define __NR_setsid 157
#define __NR_getgroups 158
#define __NR_setgroups 159
#define __NR_uname 160
#define __NR_sethostname 161
#define __NR_setdomainname 162
#define __NR_getrlimit 163
#define __NR_setrlimit 164
#define __NR_getrusage 165
#define __NR_umask 166
#define __NR_prctl 167
#define __NR_getcpu 168
#define __NR_gettimeofday 169
#define __NR_settimeofday 170
#define __NR_adjtimex 171
#define __NR_getpid 172
#define __NR_getppid 173
#define __NR_getuid 174
#define __NR_geteuid 175
#define __NR_getgid 176
#define __NR_getegid 177
#define __NR_gettid 178
#define __NR_sysinfo 179
#define __NR_mq_open 180
#define __NR_mq_unlink 181
#define __NR_mq_timedsend 182
#define __NR_mq_timedreceive 183
#define __NR_mq_notify 184
#define __NR_mq_getsetattr 185
#define __NR_msgget 186
#define __NR_msgctl 187
#define __NR_msgrcv 188
#define __NR_msgsnd 189
#define __NR_semget 190
#define __NR_semctl 191
#define __NR_semtimedop 192
#define __NR_semop 193
#define __NR_shmget 194
#define __NR_shmctl 195
#define __NR_shmat 196
#define __NR_shmdt 197
#define __NR_socket 198
#define __NR_socketpair 199
#define __NR_bind 200
#define __NR_listen 201
#define __NR_accept 202
#define __NR_connect 203
#define __NR_getsockname 204
#define __NR_getpeername 205
#define __NR_sendto 206
#define __NR_recvfrom 207
#define __NR_setsockopt 208
#define __NR_getsockopt 209
#define __NR_shutdown 210
#define __NR_sendmsg 211
#define __NR_recvmsg 212
#define __NR_readahead 213
#define __NR_brk 214
#define __NR_munmap 215
#define __NR_mremap 216
#define __NR_add_key 217
#define __NR_request_key 218
#define __NR_keyctl 219
#define __NR_clone 220
#define __NR_execve 221
#define __NR_mmap 222
#define __NR_fadvise64 223
#define __NR_swapon 224
#define __NR_swapoff 225
#define __NR_mprotect 226
#define __NR_msync 227
#define __NR_mlock 228
#define __NR_munlock 229
#define __NR_mlockall 230
#define __NR_munlockall 231
#define __NR_mincore 232
#define __NR_madvise 233
#define __NR_remap_file_pages 234
#define __NR_mbind 235
#define __NR_get_mempolicy 236
#define __NR_set_mempolicy 237
#define __NR_migrate_pages 238
#define __NR_move_pages 239
#define __NR_rt_tgsigqueueinfo 240
#define __NR_perf_event_open 241
#define __NR_accept4 242
#define __NR_recvmmsg 243
#define __NR_arch_specific_syscall 244
#define __NR_wait4 260
#define __NR_prlimit64 261
#define __NR_fanotify_init 262
#define __NR_fanotify_mark 263
#define __NR_name_to_handle_at 264
#define __NR_open_by_handle_at 265
#define __NR_clock_adjtime 266
#define __NR_syncfs 267
#define __NR_setns 268
#define __NR_sendmmsg 269
#define __NR_process_vm_readv 270
#define __NR_process_vm_writev 271
#define __NR_kcmp 272
#define __NR_finit_module 273
#define __NR_sched_setattr 274
#define __NR_sched_getattr 275
#define __NR_renameat2 276
#define __NR_seccomp 277
#define __NR_getrandom 278
#define __NR_memfd_create 279
#define __NR_bpf 280
#define __NR_execveat 281
#define __NR_userfaultfd 282
#define __NR_membarrier 283
#define __NR_mlock2 284
#define __NR_copy_file_range 285
#define __NR_preadv2 286
#define __NR_pwritev2 287
#define __NR_pkey_mprotect 288
#define __NR_pkey_alloc 289
#define __NR_pkey_free 290
#define __NR_statx 291
#define __NR_io_pgetevents 292
#define __NR_rseq 293
#define __NR_kexec_file_load 294
#define __NR_pidfd_send_signal 424
#define __NR_io_uring_setup 425
#define __NR_io_uring_enter 426
#define __NR_io_uring_register 427
#define __NR_open_tree 428
#define __NR_move_mount 429
#define __NR_fsopen 430
#define __NR_fsconfig 431
#define __NR_fsmount 432
#define __NR_fspick 433
#define __NR_pidfd_open 434
#define __NR_clone3 435
#define __NR_close_range 436
#define __NR_openat2 437
#define __NR_pidfd_getfd 438
#define __NR_faccessat2 439
#define __NR_process_madvise 440
#define __NR_epoll_pwait2 441
#define __NR_mount_setattr 442
#define __NR_landlock_create_ruleset 444
#define __NR_landlock_add_rule 445
#define __NR_landlock_restrict_self 446
#define __NR_sysriscv __NR_arch_specific_syscall
#define __NR_riscv_flush_icache (__NR_sysriscv + 15)
@@ -0,0 +1,4 @@
// https://git.musl-libc.org/cgit/musl/plain/arch/riscv64/bits/syscall.h.in
#define __riscv64__
#include <riscv64/syscalls.h>
#include <common.h>
@@ -44,6 +44,7 @@
#define SYS_clone2 __NR_clone2
#define SYS_clone3 __NR_clone3
#define SYS_close __NR_close
#define SYS_close_range __NR_close_range
#define SYS_connect __NR_connect
#define SYS_copy_file_range __NR_copy_file_range
#define SYS_creat __NR_creat
@@ -58,6 +59,7 @@
#define SYS_epoll_ctl __NR_epoll_ctl
#define SYS_epoll_ctl_old __NR_epoll_ctl_old
#define SYS_epoll_pwait __NR_epoll_pwait
#define SYS_epoll_pwait2 __NR_epoll_pwait2
#define SYS_epoll_wait __NR_epoll_wait
#define SYS_epoll_wait_old __NR_epoll_wait_old
#define SYS_eventfd __NR_eventfd
@@ -69,6 +71,7 @@
#define SYS_exit __NR_exit
#define SYS_exit_group __NR_exit_group
#define SYS_faccessat __NR_faccessat
#define SYS_faccessat2 __NR_faccessat2
#define SYS_fadvise64 __NR_fadvise64
#define SYS_fadvise64_64 __NR_fadvise64_64
#define SYS_fallocate __NR_fallocate
@@ -191,6 +194,9 @@
#define SYS_kexec_load __NR_kexec_load
#define SYS_keyctl __NR_keyctl
#define SYS_kill __NR_kill
#define SYS_landlock_add_rule __NR_landlock_add_rule
#define SYS_landlock_create_ruleset __NR_landlock_create_ruleset
#define SYS_landlock_restrict_self __NR_landlock_restrict_self
#define SYS_lchown __NR_lchown
#define SYS_lchown32 __NR_lchown32
#define SYS_lgetxattr __NR_lgetxattr
@@ -228,6 +234,7 @@
#define SYS_mmap2 __NR_mmap2
#define SYS_modify_ldt __NR_modify_ldt
#define SYS_mount __NR_mount
#define SYS_mount_setattr __NR_mount_setattr
#define SYS_move_mount __NR_move_mount
#define SYS_move_pages __NR_move_pages
#define SYS_mprotect __NR_mprotect
@@ -402,6 +409,7 @@
#define SYS_preadv __NR_preadv
#define SYS_preadv2 __NR_preadv2
#define SYS_prlimit64 __NR_prlimit64
#define SYS_process_madvise __NR_process_madvise
#define SYS_process_vm_readv __NR_process_vm_readv
#define SYS_process_vm_writev __NR_process_vm_writev
#define SYS_prof __NR_prof
@@ -440,6 +448,7 @@
#define SYS_reserved221 __NR_reserved221
#define SYS_reserved82 __NR_reserved82
#define SYS_restart_syscall __NR_restart_syscall
#define SYS_riscv_flush_icache __NR_riscv_flush_icache
#define SYS_rmdir __NR_rmdir
#define SYS_rseq __NR_rseq
#define SYS_rtas __NR_rtas
@@ -572,6 +581,7 @@
#define SYS_sys_kexec_load __NR_sys_kexec_load
#define SYS_syslog __NR_syslog
#define SYS_sysmips __NR_sysmips
#define SYS_sysriscv __NR_sysriscv
#define SYS_sys_setaltroot __NR_sys_setaltroot
#define SYS_tee __NR_tee
#define SYS_tgkill __NR_tgkill
File diff suppressed because it is too large. Load diff
+3
View File
@@ -7,4 +7,7 @@ all: generate.py functions.py
functions.py:
wget https://raw.githubusercontent.com/zachriggle/functions/master/functions.py
generate_darwin:
python3 generate_darwin.py "$(ROOT)"
.phony: all
+4 -4
View File
@@ -72,7 +72,7 @@ CALL = """
target = regs[index]
register_arguments[target] = arg
elif arg is not None:
stack_arguments[index] = arg
stack_arguments[name] = arg
# The argument is not a register. It is a string value, and we
# are expecting a string value
@@ -100,7 +100,7 @@ CALL = """
target = regs[index]
register_arguments[target] = arg
elif arg is not None:
stack_arguments[target] = arg
stack_arguments[name] = arg
# Some syscalls have different names on various architectures.
# Determine which syscall number to use for the current architecture.
@@ -108,7 +108,7 @@ CALL = """
if hasattr(pwnlib.constants, syscall):
break
else:
raise Exception("Could not locate any syscalls: %r" % syscalls)
raise Exception("Could not locate any syscalls: %r" % {syscalls!r})
%>
/* {name}(${{', '.join(syscall_repr)}}) */
%for name, arg in string_arguments.items():
@@ -249,7 +249,7 @@ def generate_one(target):
# Mako is unable to use *vararg and *kwarg, so we just stub in
# a whole bunch of additional arguments.
if argname == 'vararg':
for j in range(5):
for j in range(6):
argname = 'vararg_%i' % j
argument_names.append(argname)
argument_names_.append(argname)
+73
View File
@@ -0,0 +1,73 @@
# ./pwnlib/data/includes/darwin/aarch64.h
# ./pwnlib/constants/darwin/aarch64.py
# https://github.com/nullgemm/instant_macos_sdk (old sdk here, please use real macos device)
# https://github.com/joseluisq/macosx-sdks (old sdk here, please use real macos device)
# /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/
from pathlib import Path
import re
import sys
# In the future, you should change the version of `MacOSX15.sdk`
sdk_path = Path('/Library/Developer/CommandLineTools/SDKs/MacOSX15.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/')
if not sdk_path.exists():
print('missing MacOSX sdk')
exit(1)
project_path = Path(sys.argv[1])
regex = re.compile(r'^#define\s+([a-zA-Z0-9_-]+)\s+([0-9]+|0x[0-9a-fA-F]+)(?:\s|$)', re.DOTALL)
out_data = {}
for file in sdk_path.iterdir():
if not file.is_file():
continue
print(file.name)
for line in file.read_text(errors='ignore').split('\n'):
matched = regex.search(line)
if not matched:
continue
key, value = matched.groups()
if value.startswith('0') and not value.startswith('0x') and len(value) > 1:
value = '0o'+value
print(key, value)
out_data[key] = value
outbuf1_aarch64 = ''
outbuf1_aarch64 += "from pwnlib.constants.constant import Constant\n"
outbuf1_amd64 = ''
outbuf1_amd64 += "from pwnlib.constants.constant import Constant\n"
outbuf2_aarch64 = ''
outbuf2_amd64 = ''
# https://www.idryman.org/blog/2014/12/02/writing-64-bit-assembly-on-mac-os-x/
# on amd64 syscall offsets from 0x2000000 + syscall number
for key, value in out_data.items():
value_octal = value
if value_octal.startswith('0o'):
value_octal = value_octal.replace('0o', '0')
outbuf1_aarch64 += "{} = Constant('{}',{})\n".format(key, key, value)
outbuf2_aarch64 += "#define {} {}\n".format(key, value_octal)
if key.startswith('SYS_'):
value = f'{value} + 0x2000000'
value_octal = f'{value_octal} + 0x2000000'
outbuf1_amd64 += "{} = Constant('{}',{})\n".format(key, key, value)
outbuf2_amd64 += "#define {} {}\n".format(key, value_octal)
pp = project_path
(pp / Path('./pwnlib/constants/darwin/aarch64.py')).write_bytes(outbuf1_aarch64.encode())
(pp / Path('./pwnlib/data/includes/darwin/aarch64.h')).write_bytes(outbuf2_aarch64.encode())
(pp / Path('./pwnlib/constants/darwin/amd64.py')).write_bytes(outbuf1_amd64.encode())
(pp / Path('./pwnlib/data/includes/darwin/amd64.h')).write_bytes(outbuf2_amd64.encode())
+28 -2
View File
@@ -1,4 +1,4 @@
<%page args="binary, host=None, port=None, user=None, password=None, remote_path=None, quiet=False"/>\
<%page args="binary, host=None, port=None, user=None, password=None, libc=None, remote_path=None, quiet=False"/>\
<%
import os
import sys
@@ -31,6 +31,7 @@ elif host and not port:
remote_path = remote_path or exe
password = password or 'secret1234'
binary_repr = repr(binary)
libc_repr = repr(libc)
%>\
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
@@ -43,7 +44,7 @@ from pwn import *
%if not quiet:
# Set up pwntools for the correct architecture
%endif
%if ctx.binary:
%if ctx.binary or not host:
exe = context.binary = ELF(args.EXE or ${binary_repr})
<% binary_repr = 'exe.path' %>
%else:
@@ -83,6 +84,31 @@ if not args.LOCAL:
shell.set_working_directory(symlink=True)
%endif
%if libc:
%if not quiet:
# Use the specified remote libc version unless explicitly told to use the
# local system version with the `LOCAL_LIBC` argument.
# ./exploit.py LOCAL LOCAL_LIBC
%endif
if args.LOCAL_LIBC:
libc = exe.libc
%if host:
elif args.LOCAL:
%else:
else:
%endif
library_path = libcdb.download_libraries(${libc_repr})
if library_path:
exe = context.binary = ELF.patch_custom_libraries(${binary_repr}, library_path)
libc = exe.libc
else:
libc = ELF(${libc_repr})
%if host:
else:
libc = ELF(${libc_repr})
%endif
%endif
%if host:
def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
+137 -49
View File
@@ -165,6 +165,18 @@ class DynELF(object):
self._waitfor = None
self._bases = {}
self._dynamic = None
self.elf = None
if elf:
path = elf
if isinstance(elf, ELF):
path = elf.path
# Load a fresh copy of the ELF
with context.local(log_level='error'):
w = self.waitfor("Loading from %r" % path)
self.elf = ELF(path)
w.success("[LOADED]")
if not (pointer or (elf and elf.address)):
log.error("Must specify either a pointer into a module and/or an ELF file with a valid base address")
@@ -177,12 +189,15 @@ class DynELF(object):
if not elf:
log.warn_once("No ELF provided. Leaking is much faster if you have a copy of the ELF being leaked.")
self.elf = elf
self.leak = leak
self.libbase = self._find_base(pointer or elf.address)
if elf:
self._find_linkmap_assisted(elf)
self._elftype = self.elf.elftype
self._elfclass = self.elf.elfclass
self.elf.address = self.libbase
self._dynamic = self.elf.get_section_by_name('.dynamic').header.sh_addr
self._dynamic = self._make_absolute_ptr(self._dynamic)
@classmethod
def for_one_lib_only(cls, leak, ptr):
@@ -241,49 +256,6 @@ class DynELF(object):
self._dynamic = self._find_dynamic_phdr()
return self._dynamic
def _find_linkmap_assisted(self, path):
"""Uses an ELF file to assist in finding the link_map.
"""
if isinstance(path, ELF):
path = path.path
# Load a fresh copy of the ELF
with context.local(log_level='error'):
elf = ELF(path)
elf.address = self.libbase
w = self.waitfor("Loading from %r" % elf.path)
# Save our real leaker
real_leak = self.leak
# Create a fake leaker which just leaks out of the 'loaded' ELF
# However, we may load things which are outside of the ELF (e.g.
# the linkmap or GOT) so we need to fall back on the real leak.
@MemLeak
def fake_leak(address):
try:
return elf.read(address, 4)
except ValueError:
return real_leak.b(address)
# Save off our real leaker, use the fake leaker
self.leak = fake_leak
# Get useful pointers for resolving the linkmap faster
w.status("Searching for DT_PLTGOT")
pltgot = self._find_dt(constants.DT_PLTGOT)
w.status("Searching for DT_DEBUG")
debug = self._find_dt(constants.DT_DEBUG)
# Restore the real leaker
self.leak = real_leak
# Find the linkmap using the helper pointers
self._find_linkmap(pltgot, debug)
self.success('Done')
def _find_base(self, ptr):
page_size = 0x1000
page_mask = ~(page_size - 1)
@@ -380,6 +352,27 @@ class DynELF(object):
return dynamic
def _find_dt_optimized(self, name):
"""
Find an entry in the DYNAMIC array through an ELF
Arguments:
name(str): Name of the tag to find ('DT_DEBUG', 'DT_PLTGOT', ...)
Returns:
Pointer to the data described by the specified entry.
"""
if not self.elf:
return None
ptr = self.elf.dynamic_value_by_tag(name)
if ptr:
ptr = self._make_absolute_ptr(ptr)
self.success("Found %s at %#x" % (name, ptr))
return ptr
return None
def _find_dt(self, tag):
"""
Find an entry in the DYNAMIC array.
@@ -390,11 +383,16 @@ class DynELF(object):
Returns:
Pointer to the data described by the specified entry.
"""
leak = self.leak
base = self.libbase
dynamic = self.dynamic
leak = self.leak
name = next(k for k,v in ENUM_D_TAG.items() if v == tag)
# Read directly from the ELF if possible
ptr = self._find_dt_optimized(name)
if ptr:
return ptr
Dyn = {32: elf.Elf32_Dyn, 64: elf.Elf64_Dyn} [self.elfclass]
# Found the _DYNAMIC program header, now find PLTGOT entry in it
@@ -407,10 +405,10 @@ class DynELF(object):
self.failure("Could not find tag %s" % name)
return None
self.status("Found %s at %#x" % (name, dynamic))
ptr = leak.field(dynamic, Dyn.d_ptr)
ptr = self._make_absolute_ptr(ptr)
self.status("Found %s at %#x" % (name, ptr))
return ptr
@@ -599,6 +597,10 @@ class DynELF(object):
Return a dictionary mapping library path to its base address.
'''
if not self._bases:
if self.link_map is None:
self.failure("Cannot determine bases without linkmap")
return {}
leak = self.leak
LinkMap = {32: elf.Elf32_Link_Map, 64: elf.Elf64_Link_Map}[self.elfclass]
@@ -650,7 +652,7 @@ class DynELF(object):
break
if name:
self.status('Skipping %s' % name)
self.status('Skipping %r' % name)
cur = leak.field(cur, LinkMap.l_next)
else:
@@ -666,6 +668,62 @@ class DynELF(object):
lib._waitfor = self._waitfor
return lib
def _rel_lookup(self, symb, strtab=None, symtab=None, jmprel=None):
"""Performs slower symbol lookup using DT_JMPREL(.rela.plt)"""
leak = self.leak
elf_obj = self.elf
symb_name = symb.decode()
# If elf is available look for the symbol in it
if elf_obj and symb_name in elf_obj.symbols:
self.success("Symbol '%s' found in ELF!" % symb_name)
return elf_obj.symbols[symb_name]
log.warning("Looking up symbol through DT_JMPREL. This might be slower...")
strtab = strtab or self._find_dt(constants.DT_STRTAB)
symtab = symtab or self._find_dt(constants.DT_SYMTAB)
jmprel = jmprel or self._find_dt(constants.DT_JMPREL) # .rela.plt
strtab = self._make_absolute_ptr(strtab)
symtab = self._make_absolute_ptr(symtab)
jmprel = self._make_absolute_ptr(jmprel)
w = self.waitfor("Looking for %s in .rel.plt" % symb)
# We look for the symbol by iterating through each Elf64_Rel entry.
# For each Elf64_Rel, get the Elf64_Sym for that entry
# Then compare the Elf64_Sym.st_name with the symbol name
Rel = {32: elf.Elf32_Rel, 64: elf.Elf64_Rel}[self.elfclass]
Sym = {32: elf.Elf32_Sym, 64: elf.Elf64_Sym}[self.elfclass]
rel_addr = jmprel
rel_entry = None
while True:
rel_entry = leak.struct(rel_addr, Rel)
# We ran out of entries in DT_JMPREL
if rel_entry.r_offset == 0:
return None
sym_idx = rel_entry.r_info >> 32 # might be different for 32-bit
sym_entry_address = symtab + ( sym_idx * sizeof(Sym) )
sym_str_off = leak.field(sym_entry_address, Sym.st_name)
symb_str = leak.s(strtab+sym_str_off)
if symb_str == symb:
w.success("Found matching Elf64_Rel entry!")
break
rel_addr += sizeof(Rel)
symbol_address = self._make_absolute_ptr(rel_entry.r_offset)
return symbol_address
def _lookup(self, symb):
"""Performs the actual symbol lookup within one ELF file."""
leak = self.leak
@@ -698,9 +756,39 @@ class DynELF(object):
#
# Perform the hash lookup
#
# Save off our real leaker in case we use the fake leaker
real_leak = self.leak
if self.elf:
# Create a fake leaker which just leaks out of the 'loaded' ELF
# However, we may load things which are outside of the ELF (e.g.
# the linkmap or GOT) so we need to fall back on the real leak.
@MemLeak
def fake_leak(address):
try:
return self.elf.read(address, 4)
except ValueError:
return real_leak.b(address)
# Use fake leaker since ELF is available
self.leak = fake_leak
routine = {'sysv': self._resolve_symbol_sysv,
'gnu': self._resolve_symbol_gnu}[hshtype]
return routine(self.libbase, symb, hshtab, strtab, symtab)
resolved_addr = routine(self.libbase, symb, hshtab, strtab, symtab)
if resolved_addr:
# Restore the original leaker
self.leak = real_leak
return resolved_addr
# if symbol not found in GNU_Hash, try looking in JMPREL
resolved_addr = self._rel_lookup(symb, strtab, symtab)
# Restore the original leaker
self.leak = real_leak
return resolved_addr
def _resolve_symbol_sysv(self, libbase, symb, hshtab, strtab, symtab):
"""
+37 -77
View File
@@ -93,7 +93,6 @@ from pwnlib.util.fiddling import enhex
from pwnlib.util.fiddling import unhex
from pwnlib.util.misc import read
from pwnlib.util.misc import write
from pwnlib.util.packing import _decode
from pwnlib.util.packing import pack
from pwnlib.util.packing import unpack_many
@@ -106,44 +105,11 @@ prstatus_types = {
'aarch64': elf_prstatus_aarch64
}
prpsinfo_types = {
32: elf_prpsinfo_32,
64: elf_prpsinfo_64,
}
siginfo_types = {
32: elf_siginfo_32,
64: elf_siginfo_64
}
# Slightly modified copy of the pyelftools version of the same function,
# until they fix this issue:
# https://github.com/eliben/pyelftools/issues/93
def iter_notes(self):
""" Iterates the list of notes in the segment.
"""
offset = self['p_offset']
end = self['p_offset'] + self['p_filesz']
while offset < end:
note = struct_parse(
self.elffile.structs.Elf_Nhdr,
self.stream,
stream_pos=offset)
note['n_offset'] = offset
offset += self.elffile.structs.Elf_Nhdr.sizeof()
self.stream.seek(offset)
# n_namesz is 4-byte aligned.
disk_namesz = roundup(note['n_namesz'], 2)
with context.local(encoding='latin-1'):
note['n_name'] = _decode(
CString('').parse(self.stream.read(disk_namesz)))
offset += disk_namesz
desc_data = _decode(self.stream.read(note['n_descsz']))
note['n_desc'] = desc_data
offset += roundup(note['n_descsz'], 2)
note['n_size'] = offset - note['n_offset']
yield note
class Mapping(object):
"""Encapsulates information about a memory mapping in a :class:`Corefile`.
@@ -272,8 +238,8 @@ class Corefile(ELF):
Registers can be accessed directly, e.g. via ``core_obj.eax`` and enumerated
via :data:`Corefile.registers`.
Memory can be accessed directly via :meth:`.read` or :meth:`.write`, and also
via :meth:`.pack` or :meth:`.unpack` or even :meth:`.string`.
Memory can be accessed directly via :meth:`pwnlib.elf.elf.ELF.read` or :meth:`pwnlib.elf.elf.ELF.write`, and also
via :meth:`pwnlib.elf.elf.ELF.pack` or :meth:`pwnlib.elf.elf.ELF.unpack` or even :meth:`.string`.
Arguments:
core: Path to the core file. Alternately, may be a :class:`.process` instance,
@@ -410,8 +376,8 @@ class Corefile(ELF):
>>> core.exe.data[0:4]
b'\x7fELF'
It also supports all of the features of :class:`ELF`, so you can :meth:`.read`
or :meth:`.write` or even the helpers like :meth:`.pack` or :meth:`.unpack`.
It also supports all of the features of :class:`ELF`, so you can :meth:`pwnlib.elf.elf.ELF.read`
or :meth:`pwnlib.elf.elf.ELF.write` or even the helpers like :meth:`pwnlib.elf.elf.ELF.pack` or :meth:`pwnlib.elf.elf.ELF.unpack`.
Don't forget to call :meth:`.ELF.save` to save the changes to disk.
@@ -531,7 +497,6 @@ class Corefile(ELF):
>>> io = elf.process()
>>> io.wait(2)
>>> core = io.corefile
[!] End of the stack is corrupted, skipping stack parsing (got: 41414141)
>>> core.argc, core.argv, core.env
(0, [], {})
>>> core.stack.data.endswith(b'AAAA')
@@ -590,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:
@@ -606,7 +574,6 @@ class Corefile(ELF):
log.warn_once("%s does not use a supported corefile architecture, registers are unavailable" % self.file.name)
prstatus_type = prstatus_types.get(self.arch)
prpsinfo_type = prpsinfo_types.get(self.bits)
siginfo_type = siginfo_types.get(self.bits)
with log.waitfor("Parsing corefile...") as w:
@@ -617,45 +584,38 @@ class Corefile(ELF):
continue
# Note that older versions of pyelftools (<=0.24) are missing enum values
# for NT_PRSTATUS, NT_PRPSINFO, NT_AUXV, etc.
# For this reason, we have to check if note.n_type is any of several values.
for note in iter_notes(segment):
if not isinstance(note.n_desc, bytes):
note['n_desc'] = note.n_desc.encode('latin1')
for note in segment.iter_notes():
# Try to find NT_PRSTATUS.
if prstatus_type and \
note.n_descsz == ctypes.sizeof(prstatus_type) and \
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRSTATUS'):
if note.n_type == 'NT_PRSTATUS':
self.NT_PRSTATUS = note
self.prstatus = prstatus_type.from_buffer_copy(note.n_desc)
# Try to find NT_PRPSINFO
if prpsinfo_type and \
note.n_descsz == ctypes.sizeof(prpsinfo_type) and \
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRPSINFO'):
if note.n_type == 'NT_PRPSINFO':
self.NT_PRPSINFO = note
self.prpsinfo = prpsinfo_type.from_buffer_copy(note.n_desc)
self.prpsinfo = note.n_desc
# Try to find NT_SIGINFO so we can see the fault
if note.n_type in (0x53494749, 'NT_SIGINFO'):
if note.n_type == 'NT_SIGINFO':
self.NT_SIGINFO = note
self.siginfo = siginfo_type.from_buffer_copy(note.n_desc)
# Try to find the list of mapped files
if note.n_type in (constants.NT_FILE, 'NT_FILE'):
if note.n_type == 'NT_FILE':
with context.local(bytes=self.bytes):
self._parse_nt_file(note)
# Try to find the auxiliary vector, which will tell us
# where the top of the stack is.
if note.n_type in (constants.NT_AUXV, 'NT_AUXV'):
if note.n_type == 'NT_AUXV':
self.NT_AUXV = note
with context.local(bytes=self.bytes):
self._parse_auxv(note)
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:
@@ -667,7 +627,7 @@ class Corefile(ELF):
log.warn('Could not find the stack!')
self.stack = None
with context.local(bytes=self.bytes, log_level='warn'):
with context.local(bytes=self.bytes):
try:
self._parse_stack()
except ValueError:
@@ -685,31 +645,16 @@ class Corefile(ELF):
self._describe_core()
def _parse_nt_file(self, note):
t = tube()
t.unrecv(note.n_desc)
count = t.unpack()
page_size = t.unpack()
starts = []
addresses = {}
for i in range(count):
start = t.unpack()
end = t.unpack()
offset = t.unpack()
starts.append((start, offset))
for i in range(count):
filename = t.recvuntil(b'\x00', drop=True)
for vma, filename in zip(note.n_desc.Elf_Nt_File_Entry, note.n_desc.filename):
if not isinstance(filename, str):
filename = filename.decode('utf-8')
(start, offset) = starts[i]
filename = filename.decode('utf-8', 'surrogateescape')
for mapping in self.mappings:
if mapping.start == start:
if mapping.start == vma.vm_start:
mapping.name = filename
mapping.page_offset = offset
mapping.page_offset = vma.page_offset
self.mappings = sorted(self.mappings, key=lambda m: m.start)
@@ -1389,6 +1334,10 @@ class CorefileFinder(object):
except Exception:
pass
# Convert bytes-like object to string
if isinstance(data, bytes):
data = data.decode('utf-8')
return data
def systemd_coredump_corefile(self):
@@ -1566,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(
+9 -23
View File
@@ -399,6 +399,15 @@ class Elf64_Sym(ctypes.Structure):
("st_value", Elf64_Addr),
("st_size", Elf64_Xword),]
class Elf64_Rel(ctypes.Structure):
_fields_ = [("r_offset", Elf64_Addr),
("r_info", Elf64_Xword),
("r_addend", Elf64_Sxword),]
class Elf32_Rel(ctypes.Structure):
_fields_ = [("r_offset", Elf32_Addr),
("r_info", Elf32_Word),]
class Elf32_Link_Map(ctypes.Structure):
_fields_ = [("l_addr", Elf32_Addr),
("l_name", Elf32_Addr),
@@ -631,29 +640,6 @@ class Elf64_auxv_t(ctypes.Structure):
_fields_ = [('a_type', ctypes.c_uint64),
('a_val', ctypes.c_uint64),]
def generate_prpsinfo(long):
return [
('pr_state', byte),
('pr_sname', char),
('pr_zomb', byte),
('pr_nice', byte),
('pr_flag', long),
('pr_uid', ctypes.c_ushort),
('pr_gid', ctypes.c_ushort),
('pr_pid', ctypes.c_int),
('pr_ppid', ctypes.c_int),
('pr_pgrp', ctypes.c_int),
('pr_sid', ctypes.c_int),
('pr_fname', char * 16),
('pr_psargs', char * 80)
]
class elf_prpsinfo_32(ctypes.Structure):
_fields_ = generate_prpsinfo(Elf32_Addr)
class elf_prpsinfo_64(ctypes.Structure):
_fields_ = generate_prpsinfo(Elf64_Addr)
def generate_siginfo(int_t, long_t):
class siginfo_t(ctypes.Structure):
_fields_ = [('si_signo', int_t),
+364 -105
View File
@@ -47,14 +47,16 @@ import tempfile
from six import BytesIO
from collections import namedtuple
from collections import namedtuple, defaultdict
from elftools.elf.constants import P_FLAGS
from elftools.elf.constants import SHN_INDICES
from elftools.elf.descriptions import describe_e_type
from elftools.elf.dynamic import DynamicSection
from elftools.elf.elffile import ELFFile
from elftools.elf.enums import ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS
from elftools.elf.gnuversions import GNUVerDefSection
from elftools.elf.relocation import RelocationSection
from elftools.elf.relocation import RelocationSection, RelrRelocationSection
from elftools.elf.sections import SymbolTableSection
from elftools.elf.segments import InterpSegment
@@ -82,7 +84,7 @@ from pwnlib.tubes.process import process
from pwnlib.util import misc
from pwnlib.util import packing
from pwnlib.util.fiddling import unhex
from pwnlib.util.misc import align, align_down
from pwnlib.util.misc import align, align_down, which
from pwnlib.util.sh_string import sh_string
log = getLogger(__name__)
@@ -357,12 +359,21 @@ class ELF(ELFFile):
self._populate_functions()
self._populate_kernel_version()
self._print_checksec = checksec
if checksec:
self._describe()
self._libs = None
self._maps = None
def close(self):
"""close() -> None
Close the ELF file and release all resources associated with it.
"""
super(ELF, self).close()
self.file.close()
@staticmethod
@LocalContext
def from_assembly(assembly, *a, **kw):
@@ -447,7 +458,7 @@ class ELF(ELFFile):
def _describe(self, *a, **kw):
log.info_once(
'%s\n%-10s%s-%s-%s\n%s',
'%s\n%-12s%s-%s-%s\n%s',
repr(self.path),
'Arch:',
self.arch,
@@ -459,6 +470,7 @@ class ELF(ELFFile):
def get_machine_arch(self):
return {
('EM_X86_64', 64): 'amd64',
('EM_X86_64', 32): 'amd64', # x32 ABI
('EM_386', 32): 'i386',
('EM_486', 32): 'i386',
('EM_ARM', 32): 'arm',
@@ -510,6 +522,29 @@ class ELF(ELFFile):
if t == seg.header.p_type or t in str(seg.header.p_type):
yield seg
def iter_notes(self):
"""
Yields:
All the notes in the PT_NOTE segments. Each result is a dictionary-
like object with ``n_name``, ``n_type``, and ``n_desc`` fields, amongst
others.
"""
for seg in self.iter_segments_by_type('PT_NOTE'):
for note in seg.iter_notes():
yield note
def iter_properties(self):
"""
Yields:
All the GNU properties in the PT_NOTE segments. Each result is a dictionary-
like object with ``pr_type``, ``pr_datasz``, and ``pr_data`` fields.
"""
for note in self.iter_notes():
if note.n_type != 'NT_GNU_PROPERTY_TYPE_0':
continue
for prop in note.n_desc:
yield prop
def get_segment_for_address(self, address, size=1):
"""get_segment_for_address(address, size=1) -> Segment
@@ -680,29 +715,57 @@ 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:
return ELF(lib)
if ('/libc.' in lib or '/libc-' in lib) and lib != self.path:
return ELF(lib, self._print_checksec)
def _populate_libraries(self):
"""
@@ -816,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)
@@ -883,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.
@@ -917,15 +986,25 @@ class ELF(ELFFile):
self.symbols['got.' + symbol] = address
def _populate_got(self):
"""Loads the symbols for all relocations"""
"""Loads the symbols for all relocations.
>>> libc = ELF(which('bash')).libc
>>> assert 'strchrnul' in libc.got
>>> assert 'memcpy' in libc.got
>>> assert libc.got.strchrnul != libc.got.memcpy
"""
# Statically linked implies no relocations, since there is no linker
# Could always be self-relocating like Android's linker *shrug*
if self.statically_linked:
return
revsymbols = defaultdict(list)
for name, addr in self.symbols.items():
revsymbols[addr].append(name)
for section in self.sections:
# We are only interested in relocations
if not isinstance(section, RelocationSection):
if not isinstance(section, (RelocationSection, RelrRelocationSection)):
continue
# Only get relocations which link to another section (for symbols)
@@ -937,7 +1016,13 @@ class ELF(ELFFile):
for rel in section.iter_relocations():
sym_idx = rel.entry.r_info_sym
if not sym_idx:
if not sym_idx and rel.is_RELA():
# TODO: actually resolve relocations
relocated = rel.entry.r_addend # sufficient for now
symnames = revsymbols[relocated]
for symname in symnames:
self.got[symname] = rel.entry.r_offset
continue
symbol = symbols.get_symbol(sym_idx)
@@ -1191,7 +1276,7 @@ class ELF(ELFFile):
segments = self.executable_segments
else:
segments = self.segments
needle = packing._need_bytes(needle)
for seg in segments:
addr = seg.header.p_vaddr
memsz = seg.header.p_memsz
@@ -1305,6 +1390,7 @@ class ELF(ELFFile):
or :const:`None`.
Examples:
>>> bash = ELF(which('bash'))
>>> bash.vaddr_to_offset(bash.address)
0
@@ -1455,6 +1541,7 @@ class ELF(ELFFile):
that it stays in the same segment.
Examples:
>>> bash = ELF(which('bash'))
>>> bash.read(bash.address+1, 3)
b'ELF'
@@ -1554,7 +1641,7 @@ class ELF(ELFFile):
dt = None
dynamic = self.get_section_by_name('.dynamic')
if not dynamic:
if not dynamic or not isinstance(dynamic, DynamicSection):
return None
try:
@@ -1737,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
@@ -1961,6 +2053,16 @@ class ELF(ELFFile):
""":class:`bool`: Whether the current binary is packed with UPX."""
return b'UPX!' in self.get_data()[:0xFF]
@property
def stripped(self):
""":class:`bool`: Whether the current binary has been stripped of symbols"""
return not any(section['sh_type'] == 'SHT_SYMTAB' for section in self.iter_sections())
@property
def debuginfo(self):
""":class:`bool`: Whether the current binary has debug information"""
return self.get_section_by_name('.debug_info') is not None
@property
def pie(self):
""":class:`bool`: Whether the current binary is position-independent."""
@@ -2004,26 +2106,26 @@ class ELF(ELFFile):
# Kernel version?
if self.version and self.version != (0,):
res.append('Version:'.ljust(10) + '.'.join(map(str, self.version)))
res.append('Version:'.ljust(12) + '.'.join(map(str, self.version)))
if self.build:
res.append('Build:'.ljust(10) + self.build)
res.append('Build:'.ljust(12) + self.build)
res.extend([
"RELRO:".ljust(10) + {
"RELRO:".ljust(12) + {
'Full': green("Full RELRO"),
'Partial': yellow("Partial RELRO"),
None: red("No RELRO")
}[self.relro],
"Stack:".ljust(10) + {
"Stack:".ljust(12) + {
True: green("Canary found"),
False: red("No canary found")
}[self.canary],
"NX:".ljust(10) + {
"NX:".ljust(12) + {
True: green("NX enabled"),
False: red("NX disabled"),
None: yellow("NX unknown - GNU_STACK missing"),
}[self.nx],
"PIE:".ljust(10) + {
"PIE:".ljust(12) + {
True: green("PIE enabled"),
False: red("No PIE (%#x)" % self.address)
}[self.pie],
@@ -2031,35 +2133,47 @@ class ELF(ELFFile):
# Execstack may be a thing, even with NX enabled, because of glibc
if self.execstack and self.nx is not False:
res.append("Stack:".ljust(10) + red("Executable"))
res.append("Stack:".ljust(12) + red("Executable"))
# Are there any RWX areas in the binary?
#
# This will occur if NX is disabled and *any* area is
# RW, or can expressly occur.
if self.rwx_segments or (not self.nx and self.writable_segments):
res += [ "RWX:".ljust(10) + red("Has RWX segments") ]
res += [ "RWX:".ljust(12) + red("Has RWX segments") ]
if self.rpath:
res += [ "RPATH:".ljust(10) + red(repr(self.rpath)) ]
res += [ "RPATH:".ljust(12) + red(repr(self.rpath)) ]
if self.runpath:
res += [ "RUNPATH:".ljust(10) + red(repr(self.runpath)) ]
res += [ "RUNPATH:".ljust(12) + red(repr(self.runpath)) ]
if self.packed:
res.append('Packer:'.ljust(10) + red("Packed with UPX"))
res.append('Packer:'.ljust(12) + red("Packed with UPX"))
if self.fortify:
res.append("FORTIFY:".ljust(10) + green("Enabled"))
res.append("FORTIFY:".ljust(12) + green("Enabled"))
if self.asan:
res.append("ASAN:".ljust(10) + green("Enabled"))
res.append("ASAN:".ljust(12) + green("Enabled"))
if self.msan:
res.append("MSAN:".ljust(10) + green("Enabled"))
res.append("MSAN:".ljust(12) + green("Enabled"))
if self.ubsan:
res.append("UBSAN:".ljust(10) + green("Enabled"))
res.append("UBSAN:".ljust(12) + green("Enabled"))
if self.shadowstack:
res.append("SHSTK:".ljust(12) + green("Enabled"))
if self.ibt:
res.append("IBT:".ljust(12) + green("Enabled"))
if not self.stripped:
res.append("Stripped:".ljust(12) + red("No"))
if self.debuginfo:
res.append("Debuginfo:".ljust(12) + red("Yes"))
# Check for Linux configuration, it must contain more than
# just the version.
@@ -2117,6 +2231,31 @@ class ELF(ELFFile):
""":class:`bool`: Whether the current binary was built with
Undefined Behavior Sanitizer (``UBSAN``)."""
return any(s.startswith('__ubsan_') for s in self.symbols)
@property
def shadowstack(self):
""":class:`bool`: Whether the current binary was built with
Shadow Stack (``SHSTK``)"""
if self.arch not in ['i386', 'amd64']:
return False
for prop in self.iter_properties():
if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
continue
return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_SHSTK'] > 0
return False
@property
def ibt(self):
""":class:`bool`: Whether the current binary was built with
Indirect Branch Tracking (``IBT``)"""
if self.arch not in ['i386', 'amd64']:
return False
for prop in self.iter_properties():
if prop.pr_type != 'GNU_PROPERTY_X86_FEATURE_1_AND':
continue
return prop.pr_data & ENUM_GNU_PROPERTY_X86_FEATURE_1_FLAGS['GNU_PROPERTY_X86_FEATURE_1_IBT'] > 0
return False
def _update_args(self, kw):
kw.setdefault('arch', self.arch)
@@ -2247,3 +2386,123 @@ class ELF(ELFFile):
return
log.error("Could not find PT_GNU_STACK, stack should already be executable")
@staticmethod
def set_runpath(exepath, runpath):
r"""set_runpath(exepath, runpath) -> ELF
Patches the RUNPATH of the ELF to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
The dynamic loader will look for any needed shared libraries in the given path first,
before trying the system library paths. This is useful to run a binary with a different
libc binary.
Arguments:
exepath(str): Path to the binary to patch.
runpath(str): Path containing the needed libraries.
Returns:
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
Example:
>>> tmpdir = tempfile.mkdtemp()
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.set_runpath(ls_path, './libs')
>>> e.runpath == b'./libs'
True
"""
if not which('patchelf'):
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
return None
try:
subprocess.check_output(['patchelf', '--set-rpath', runpath, exepath], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
log.failure('Patching RUNPATH failed (%d): %r', e.returncode, e.stdout)
return ELF(exepath, checksec=False)
@staticmethod
def set_interpreter(exepath, interpreter_path):
r"""set_interpreter(exepath, interpreter_path) -> ELF
Patches the interpreter of the ELF to the given binary using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
When running the binary, the new interpreter will be used to load the ELF.
Arguments:
exepath(str): Path to the binary to patch.
interpreter_path(str): Path to the ld.so dynamic loader.
Returns:
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
Example:
>>> tmpdir = tempfile.mkdtemp()
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
>>> e.linker == b'/tmp/correct_ld.so'
True
"""
# patch the interpreter
if not which('patchelf'):
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
return None
try:
subprocess.check_output(['patchelf', '--set-interpreter', interpreter_path, exepath], stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
log.failure('Patching interpreter failed (%d): %r', e.returncode, e.stdout)
return ELF(exepath, checksec=False)
@staticmethod
def patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc'):
r"""patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc') -> ELF
Looks for the interpreter binary in the given path and patches the binary to use
it if available. Also patches the RUNPATH to the given path using the `patchelf utility <https://github.com/NixOS/patchelf>`_.
Arguments:
exe_path(str): Path to the binary to patch.
custom_library_path(str): Path to a folder containing the libraries.
create_copy(bool): Create a copy of the binary and apply the patches to the copy.
suffix(str): Suffix to append to the filename when creating the copy to patch.
Returns:
A new ELF instance is returned after patching the binary with the external ``patchelf`` tool.
Example:
>>> tmpdir = tempfile.mkdtemp()
>>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
>>> write(linker_path, b'loader')
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
>>> e.runpath.decode() == tmpdir
True
>>> e.linker.decode() == linker_path
True
"""
if not which('patchelf'):
log.error('"patchelf" tool not installed. See https://github.com/NixOS/patchelf')
return None
# Create a copy of the ELF to patch instead of the original file.
if create_copy:
import shutil
patched_path = exe_path + suffix
shutil.copy2(exe_path, patched_path)
exe_path = patched_path
# Set interpreter in ELF to the one in the library path.
interpreter_name = [filename for filename in os.listdir(custom_library_path) if filename.startswith('ld-')]
if interpreter_name:
interpreter_path = os.path.realpath(os.path.join(custom_library_path, interpreter_name[0]))
ELF.set_interpreter(exe_path, interpreter_path)
else:
log.warn("Couldn't find ld.so in library path. Interpreter not set.")
# Set RUNPATH to library path in order to find other libraries.
return ELF.set_runpath(exe_path, custom_library_path)
+1 -1
View File
@@ -22,6 +22,6 @@ CAT_PROC_MAPS_EXIT = {
'726f093c2f702935f0ffa9af7365093c632f2935f4ffa9af2f6d093c6c662935f8ffa9af8cff193c9e8f393727482003fcffa9aff0ffbd272020a003ffff0528ffbf192427302003a50f02340c01010122e8a603fcffa2affcffa48f2028a003a30f02340c010101feff1924272020032028a003fcffa2affcffa68fa40f02340c010101'
'ffff0428a10f02340c010101',
'aarch64':
'ee058ed24eeeadf26eecc5f26eaeecf28fcd8cd2efa5adf22f0ccef26f0ee0f2ee3fbfa980f39fd2e0ffbff2e0ffdff2e0fffff2e1030091e2031faa080780d2010000d4020088d2ff6322cbe1030091e80780d2010000d4e20300aa200080d2e1030091080880d2010000d4'
'ee058ed24eeeadf26eecc5f26eaeecf28fcd8cd2efa5adf22f0ccef26f0ee0f2ee3fbfa980f39fd2e0ffbff2e0ffdff2e0fffff2e1633f8be2031faa080780d2010000d4222080d22e2088d2c20102caff6322cbe1633f8be80780d2010000d402041f8b4f2080d26c2080d28f010fcae0030faae1633f8b080880d2010000d4'
'e0031faaa80b80d2010000d4',
}
+33 -2
View File
@@ -53,9 +53,40 @@ def emulate_plt_instructions(elf, got, address, data, targets):
return rv
def __ensure_memory_to_run_unicorn():
"""
Check if there is enough memory to run Unicorn Engine.
Unicorn Engine requires 1GB of memory to run, if there isn't enough memory it calls exit(1).
This is a bug in Unicorn Engine, see: https://github.com/unicorn-engine/unicorn/issues/1766
"""
try:
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
)
mm.close()
except OSError:
raise OSError("Cannot allocate 1GB memory to run Unicorn Engine")
except ImportError:
# Can only mmap files on Windows, would need to use VirtualAlloc.
pass
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
# architecutre.
arch = {
@@ -145,8 +176,8 @@ def emulate_plt_instructions_inner(uc, elf, got, pc, data):
return False
hooks = [
uc.hook_add(U.UC_HOOK_MEM_READ | U.UC_HOOK_MEM_READ_UNMAPPED,
hook_mem, stopped_addr),
uc.hook_add(U.UC_HOOK_MEM_READ, hook_mem, stopped_addr),
uc.hook_add(U.UC_HOOK_MEM_READ_UNMAPPED, hook_mem, stopped_addr),
]
# callback for tracing instructions
+4 -4
View File
@@ -132,7 +132,7 @@ class AsciiShellcodeEncoder(Encoder):
Examples:
>>> context.update(arch='i386', os='linux')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> encoders.i386.ascii_shellcode.encode._get_allocator(300, vocab)
bytearray(b'TX-!!!!-!_``-t~~~P\\%!!!!%@@@@')
"""
@@ -178,7 +178,7 @@ class AsciiShellcodeEncoder(Encoder):
Examples:
>>> context.update(arch='i386', os='linux')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> a, b = encoders.i386.ascii_shellcode.encode._find_negatives(vocab)
>>> a & b
0
@@ -212,7 +212,7 @@ class AsciiShellcodeEncoder(Encoder):
>>> context.update(arch='i386', os='linux')
>>> sc = bytearray(b'ABCDEFGHIGKLMNOPQRSTUVXYZ')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> encoders.i386.ascii_shellcode.encode._get_subtractions(sc, vocab)
bytearray(b'-(!!!-~NNNP-!=;:-f~~~-~~~~P-!!!!-edee-~~~~P-!!!!-eddd-~~~~P-!!!!-egdd-~~~~P-!!!!-eadd-~~~~P-!!!!-eddd-~~~~P')
"""
@@ -255,7 +255,7 @@ class AsciiShellcodeEncoder(Encoder):
Examples:
>>> context.update(arch='i386', os='linux')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~')
>>> print(encoders.i386.ascii_shellcode.encode._calc_subtractions(bytearray(b'\x10'*4), bytearray(b'\x11'*4), vocab))
[bytearray(b'!!!!'), bytearray(b'`___'), bytearray(b'~~~~')]
>>> print(encoders.i386.ascii_shellcode.encode._calc_subtractions(bytearray(b'\x11\x12\x13\x14'), bytearray(b'\x15\x16\x17\x18'), vocab))
+2 -2
View File
@@ -41,7 +41,7 @@ decoders = {
b'\xff\xff\x08\x21', # addi t0,t0,-1
b'\xff\xff\x10\x05', # bltzal t0,14 <next>
b'\x82\x82\x08\x28', # slti t0,zero,-32126
b'\xe2\xff\xfd\x23', # addi sp,ra,-30
b'\xe0\xff\xfd\x23', # addi sp,ra,-32
b'\x27\x58\x60\x01', # nor t3,t3,zero
b'\x21\xc8\xeb\x03', # addu t9,ra,t3
b'\x82\x82\x17\x28', # slti s7,zero,-32126
@@ -72,7 +72,7 @@ decoders = {
b'\x21\x08\xff\xff', # addi t0,t0,-1
b'\x05\x10\xff\xff', # bltzal t0,14 <next>
b'\x28\x08\x82\x82', # slti t0,zero,-32126
b'\x23\xfd\xff\xe2', # addi sp,ra,-30
b'\x23\xfd\xff\xe0', # addi sp,ra,-32
b'\x01\x60\x58\x27', # nor t3,t3,zero
b'\x03\xeb\xc8\x21', # addu t9,ra,t3
b'\x28\x17\x82\x82', # slti s7,zero,-32126
+15 -3
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_=[]
@@ -176,7 +188,7 @@ class FileStructure(object):
def __repr__(self):
structure=[]
for i in self.vars_:
structure.append(" %s: %s" % (i,hex(getattr(self, i))))
structure.append(" %s: %#x" % (i, getattr(self, i)))
return "{"+ "\n".join(structure)+"}"
def __len__(self):
@@ -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:
+43 -9
View File
@@ -129,6 +129,7 @@ def normalize_writes(writes):
such that all values are raw bytes and consecutive writes are merged to a single key.
Examples:
>>> context.clear(endian="little", bits=32)
>>> normalize_writes({0x0: [p32(0xdeadbeef)], 0x4: p32(0xf00dface), 0x10: 0x41414141})
[(0, b'\xef\xbe\xad\xde\xce\xfa\r\xf0'), (16, b'AAAA')]
@@ -215,6 +216,7 @@ class AtomWrite(object):
given the current format string write counter (how many bytes have been written until now).
Examples:
>>> hex(pwnlib.fmtstr.AtomWrite(0x0, 0x2, 0x2345).compute_padding(0x1111))
'0x1234'
>>> hex(pwnlib.fmtstr.AtomWrite(0x0, 0x2, 0xaa00).compute_padding(0xaabb))
@@ -246,6 +248,7 @@ class AtomWrite(object):
Combine adjacent writes into a single write.
Example:
>>> context.clear(endian = "little")
>>> pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0x1, 0xff).union(pwnlib.fmtstr.AtomWrite(0x1, 0x1, 0x2, 0x77))
AtomWrite(start=0, size=2, integer=0x201, mask=0x77ff)
@@ -285,11 +288,13 @@ def make_atoms_simple(address, data, badbytes=frozenset()):
This function is simple and does not try to minimize the number of atoms. For example, if there are no
bad bytes, it simply returns one atom for each byte:
>>> pwnlib.fmtstr.make_atoms_simple(0x0, b"abc", set())
[AtomWrite(start=0, size=1, integer=0x61, mask=0xff), AtomWrite(start=1, size=1, integer=0x62, mask=0xff), AtomWrite(start=2, size=1, integer=0x63, mask=0xff)]
If there are bad bytes, it will try to bypass by skipping addresses containing bad bytes, otherwise a
RuntimeError will be raised:
>>> pwnlib.fmtstr.make_atoms_simple(0x61, b'abc', b'\x62')
[AtomWrite(start=97, size=2, integer=0x6261, mask=0xffff), AtomWrite(start=99, size=1, integer=0x63, mask=0xff)]
>>> pwnlib.fmtstr.make_atoms_simple(0x61, b'a'*0x10, b'\x62\x63\x64\x65\x66\x67\x68')
@@ -304,14 +309,15 @@ def make_atoms_simple(address, data, badbytes=frozenset()):
i = 0
out = []
end = address + len(data)
while i < len(data):
candidate = AtomWrite(address + i, 1, data[i])
while i + candidate.size < len(data) and any(x in badbytes for x in pack(candidate.end)):
while candidate.end < end and any(x in badbytes for x in pack(candidate.end)):
candidate = candidate.union(AtomWrite(candidate.end, 1, data[i + candidate.size]))
sz = min([s for s in SPECIFIER if s >= candidate.size] + [float("inf")])
if i + sz > len(data):
raise RuntimeError("impossible to avoid badbytes starting after offset %d (address %x)" % (i, i + address))
if candidate.start + sz > end:
raise RuntimeError("impossible to avoid badbytes starting after offset %d (address %#x)" % (i, i + address))
i += candidate.size
candidate = candidate.union(AtomWrite(candidate.end, sz - candidate.size, 0, 0))
out.append(candidate)
@@ -324,6 +330,7 @@ def merge_atoms_writesize(atoms, maxsize):
This function simply merges adjacent atoms as long as the merged atom's size is not larger than ``maxsize``.
Examples:
>>> from pwnlib.fmtstr import *
>>> merge_atoms_writesize([AtomWrite(0, 1, 1), AtomWrite(1, 1, 1), AtomWrite(2, 1, 2)], 2)
[AtomWrite(start=0, size=2, integer=0x101, mask=0xffff), AtomWrite(start=2, size=1, integer=0x2, mask=0xff)]
@@ -363,6 +370,7 @@ def find_min_hamming_in_range_step(prev, step, carry, strict):
A tuple (score, value, mask) where score equals the number of matching bytes between the returned value and target.
Examples:
>>> initial = {(0,0): (0,0,0), (0,1): None, (1,0): None, (1,1): None}
>>> pwnlib.fmtstr.find_min_hamming_in_range_step(initial, (0, 0xFF, 0x1), 0, 0)
(1, 1, 255)
@@ -418,6 +426,7 @@ def find_min_hamming_in_range(maxbytes, lower, upper, target):
target(int): the target value that should be approximated
Examples:
>>> pp = lambda svm: (svm[0], hex(svm[1]), hex(svm[2]))
>>> pp(pwnlib.fmtstr.find_min_hamming_in_range(1, 0x0, 0x100, 0xaa))
(1, '0xaa', '0xff')
@@ -469,6 +478,7 @@ def merge_atoms_overlapping(atoms, sz, szmax, numbwritten, overflows):
overflows(int): how many extra overflows (of size sz) to tolerate to reduce the number of atoms
Examples:
>>> from pwnlib.fmtstr import *
>>> merge_atoms_overlapping([AtomWrite(0, 1, 1), AtomWrite(1, 1, 1)], 2, 8, 0, 1)
[AtomWrite(start=0, size=2, integer=0x101, mask=0xffff)]
@@ -498,7 +508,7 @@ def merge_atoms_overlapping(atoms, sz, szmax, numbwritten, overflows):
# the best write is the one which sets the largest number of target
# bytes correctly
candidate = AtomWrite(atom.start, 0, 0)
best = (0, None)
best = (atom.size, idx, atom)
for nextidx, nextatom in enumerate(atoms[idx:], idx):
# if there is no atom immediately following the current candidate
# that we haven't written yet, stop
@@ -527,6 +537,8 @@ def merge_atoms_overlapping(atoms, sz, szmax, numbwritten, overflows):
_, nextidx, best_candidate = best
numbwritten_here += best_candidate.compute_padding(numbwritten_here)
if numbwritten_here > maxwritten:
maxwritten = numbwritten_here
offset = 0
# for all atoms that we merged, check if all bytes are written already to update `done``
@@ -554,6 +566,7 @@ def overlapping_atoms(atoms):
Finds pairs of atoms that write to the same address.
Basic examples:
>>> from pwnlib.fmtstr import *
>>> list(overlapping_atoms([AtomWrite(0, 2, 0), AtomWrite(2, 10, 1)])) # no overlaps
[]
@@ -561,6 +574,7 @@ def overlapping_atoms(atoms):
[(AtomWrite(start=0, size=2, integer=0x0, mask=0xffff), AtomWrite(start=1, size=2, integer=0x1, mask=0xffff))]
When there are transitive overlaps, only the largest overlap is returned. For example:
>>> list(overlapping_atoms([AtomWrite(0, 3, 0), AtomWrite(1, 4, 1), AtomWrite(2, 4, 1)]))
[(AtomWrite(start=0, size=3, integer=0x0, mask=0xffffff), AtomWrite(start=1, size=4, integer=0x1, mask=0xffffffff)), (AtomWrite(start=1, size=4, integer=0x1, mask=0xffffffff), AtomWrite(start=2, size=4, integer=0x1, mask=0xffffffff))]
@@ -626,6 +640,7 @@ def sort_atoms(atoms, numbwritten):
numbwritten(int): the value at which the counter starts
Examples:
>>> from pwnlib.fmtstr import *
>>> sort_atoms([AtomWrite(0, 1, 0xff), AtomWrite(1, 1, 0xfe)], 0) # the example described above
[AtomWrite(start=1, size=1, integer=0xfe, mask=0xff), AtomWrite(start=0, size=1, integer=0xff, mask=0xff)]
@@ -691,6 +706,7 @@ def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4, no_dol
no_dollars(bool) : flag to generete the payload with or w/o $ notation
Examples:
>>> pwnlib.fmtstr.make_payload_dollar(1, [pwnlib.fmtstr.AtomWrite(0x0, 0x1, 0xff)])
(b'%255c%1$hhn', b'\x00\x00\x00\x00')
'''
@@ -799,7 +815,7 @@ def make_atoms(writes, sz, szmax, numbwritten, overflows, strategy, badbytes):
all_atoms += atoms
return all_atoms
def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset()):
def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset(), no_dollars=False):
"""
Build a format string like fmtstr_payload but return the string and data separately.
"""
@@ -813,7 +829,7 @@ def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_ma
szmax = WRITE_SIZE[write_size_max]
atoms = make_atoms(writes, sz, szmax, numbwritten, overflows, strategy, badbytes)
return make_payload_dollar(offset, atoms, numbwritten)
return make_payload_dollar(offset, atoms, numbwritten, no_dollars=no_dollars)
def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset(), offset_bytes=0, no_dollars=False):
r"""fmtstr_payload(offset, writes, numbwritten=0, write_size='byte') -> str
@@ -825,6 +841,12 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
The overflows argument is a format-string-length to output-amount tradeoff:
Larger values for ``overflows`` produce shorter format strings that generate more output at runtime.
The writes argument is a dictionary with address/value pairs like ``{addr: value, addr2: value2}``.
If the value is an ``int`` datatype, it will be automatically casted into a bytestring with the length of a ``long`` (8 bytes in 64-bit, 4 bytes in 32-bit).
If a specific number of bytes is intended to be written (such as only a single byte, single short, or single int and not an entire long),
then provide a bytestring like ``b'\x37\x13'`` or ``p16(0x1337)``.
Note that the ``write_size`` argument does not determine **total** bytes written, only the size of each consecutive write.
Arguments:
offset(int): the first formatter's offset you control
writes(dict): dict with addr, value ``{addr: value, addr2: value2}``
@@ -837,13 +859,18 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
The payload in order to do needed writes
Examples:
>>> context.clear(arch = 'amd64')
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
b'%322419390c%4$llnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00'
>>> fmtstr_payload(1, {0x0: p32(0x1337babe)}, write_size='int')
b'%322419390c%3$na\x00\x00\x00\x00\x00\x00\x00\x00'
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='short')
b'%47806c%5$lln%22649c%6$hnaaaabaa\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00'
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')
b'%190c%7$lln%85c%8$hhn%36c%9$hhn%131c%10$hhnaaaab\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
>>> fmtstr_payload(6, {0x8: 0x55d15d2004a0}, badbytes=b'\n')
b'%1184c%14$lln%49c%15$hhn%6963c%16$hn%81c%17$hhn%8c%18$hhnaaaabaa\x08\x00\x00\x00\x00\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00\x00\t\x00\x00\x00\x00\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x00'
>>> context.clear(arch = 'i386')
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='int')
b'%322419390c%5$na\x00\x00\x00\x00'
@@ -853,6 +880,8 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
b'%19c%12$hhn%36c%13$hhn%131c%14$hhn%4c%15$hhn\x03\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
>>> fmtstr_payload(1, {0x0: 0x00000001}, write_size='byte')
b'c%3$naaa\x00\x00\x00\x00'
>>> fmtstr_payload(1, {0x0: b'\x01'}, write_size='byte')
b'c%3$hhna\x00\x00\x00\x00'
>>> fmtstr_payload(1, {0x0: b"\xff\xff\x04\x11\x00\x00\x00\x00"}, write_size='short')
b'%327679c%7$lln%18c%8$hhn\x00\x00\x00\x00\x03\x00\x00\x00'
>>> fmtstr_payload(10, {0x404048 : 0xbadc0ffe, 0x40403c : 0xdeadbeef}, no_dollars=True)
@@ -900,11 +929,12 @@ class FmtStr(object):
"""
def __init__(self, execute_fmt, offset=None, padlen=0, numbwritten=0):
def __init__(self, execute_fmt, offset=None, padlen=0, numbwritten=0, badbytes=frozenset()):
self.execute_fmt = execute_fmt
self.offset = offset
self.padlen = padlen
self.numbwritten = numbwritten
self.badbytes = badbytes
if self.offset is None:
self.offset, self.padlen = self.find_offset()
@@ -968,7 +998,7 @@ class FmtStr(object):
"""
fmtstr = randoms(self.padlen).encode()
fmtstr += fmtstr_payload(self.offset, self.writes, numbwritten=self.padlen + self.numbwritten, write_size='byte')
fmtstr += fmtstr_payload(self.offset, self.writes, numbwritten=self.padlen + self.numbwritten, badbytes=self.badbytes, write_size='byte')
self.execute_fmt(fmtstr)
self.writes = {}
@@ -979,7 +1009,7 @@ class FmtStr(object):
Arguments:
addr(int): the address where you want to write
data(int): the data that you want to write ``addr``
data(int or bytes): the data that you want to write ``addr``
Returns:
None
@@ -993,6 +1023,10 @@ class FmtStr(object):
>>> f.write(0x08040506, 0x1337babe)
>>> f.execute_writes()
b'%19c%16$hhn%36c%17$hhn%131c%18$hhn%4c%19$hhn\t\x05\x04\x08\x08\x05\x04\x08\x07\x05\x04\x08\x06\x05\x04\x08'
>>> f2 = FmtStr(send_fmt_payload, offset=5)
>>> f2.write(0x08040506, p16(0x1337))
>>> f2.execute_writes()
b'%19c%11$hhn%36c%12$hhnaa\x07\x05\x04\x08\x06\x05\x04\x08'
"""
self.writes[addr] = data
+270 -87
View File
@@ -141,13 +141,10 @@ Member Documentation
from __future__ import absolute_import
from __future__ import division
from contextlib import contextmanager
import os
import platform
import psutil
import random
import re
import shlex
import six
import six.moves
import socket
@@ -249,7 +246,43 @@ def debug_shellcode(data, gdbscript=None, vma=None, api=False):
return debug(tmp_elf, gdbscript=gdbscript, arch=context.arch, api=api)
def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
def _execve_script(argv, executable, env, ssh):
"""_execve_script(argv, executable, env, ssh) -> str
Returns the filename of a python script that calls
execve the specified program with the specified arguments.
This script is suitable to call with gdbservers ``--wrapper`` option,
so we have more control over the environment of the debugged process.
Arguments:
argv(list): List of arguments to pass to the program
executable(bytes): Path to the program to run
env(dict): Environment variables to pass to the program
ssh(ssh): SSH connection to use if we are debugging a remote process
Returns:
The filename of the created script.
"""
# Make sure args are bytes not bytearray.
argv = [bytes(arg) for arg in argv]
executable = packing._encode(executable)
if ssh:
# ssh.process with run=false creates the script for us
return ssh.process(argv, executable=executable, env=env, run=False)
script = misc._create_execve_script(argv=argv, executable=executable, env=env, log=log)
script = script.strip()
# Create a temporary file to hold the script
tmp = tempfile.NamedTemporaryFile(mode="w+t",prefix='pwnlib-execve-', suffix='.py', delete=False)
tmp.write(script)
# Make script executable
os.fchmod(tmp.fileno(), 0o755)
log.debug("Created execve wrapper script %s:\n%s", tmp.name, script)
return tmp.name
def _gdbserver_args(pid=None, path=None, port=0, gdbserver_args=None, args=None, which=None, env=None, python_wrapper_script=None):
"""_gdbserver_args(pid=None, path=None, args=None, which=None, env=None) -> list
Sets up a listening gdbserver, to either connect to the specified
@@ -258,12 +291,21 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
Arguments:
pid(int): Process ID to attach to
path(str): Process to launch
port(int): Port to use for gdbserver, default: random
gdbserver_args(list): List of additional arguments to pass to gdbserver
args(list): List of arguments to provide on the debugger command line
which(callaable): Function to find the path of a binary.
env(dict): Environment variables to pass to the program
python_wrapper_script(str): Path to a python script to use with ``--wrapper``
Returns:
A list of arguments to invoke gdbserver.
"""
if gdbserver_args is None:
gdbserver_args = list()
elif not isinstance(gdbserver_args, (list, tuple)):
gdbserver_args = [gdbserver_args]
if [pid, path, args].count(None) != 2:
log.error("Must specify exactly one of pid, path, or args")
@@ -287,7 +329,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
orig_args = args
gdbserver_args = [gdbserver, '--multi']
gdbserver_args = [gdbserver, '--multi'] + gdbserver_args
if context.aslr:
gdbserver_args += ['--no-disable-randomization']
else:
@@ -296,16 +338,22 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
if pid:
gdbserver_args += ['--once', '--attach']
env_args = []
if env is not None:
env_args = []
for key in tuple(env):
# Special case for LD_ environment variables, so gdbserver
# starts with the native libraries
if key.startswith(b'LD_'): # LD_PRELOAD / LD_LIBRARY_PATH etc.
env_args.append(b'%s=%s' % (key, env.pop(key)))
else:
env_args.append(b'%s=%s' % (key, env[key]))
if python_wrapper_script is not None:
gdbserver_args += ['--wrapper', python_wrapper_script, '--']
elif env is not None:
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
gdbserver_args += ['localhost:0']
gdbserver_args += ['localhost:%d' % port]
gdbserver_args += args
return gdbserver_args
@@ -315,7 +363,14 @@ def _gdbserver_port(gdbserver, ssh):
# Process /bin/bash created; pid = 14366
# Listening on port 34816
process_created = gdbserver.recvline()
process_created = gdbserver.recvline(timeout=3)
if not process_created:
log.error(
'No output from gdbserver after 3 seconds. Try setting the SHELL=/bin/sh '
'environment variable or using the env={} argument if you are affected by '
'https://sourceware.org/bugzilla/show_bug.cgi?id=26116'
)
if process_created.startswith(b'ERROR:'):
raise ValueError(
@@ -363,7 +418,7 @@ def _get_runner(ssh=None):
else: return tubes.process.process
@LocalContext
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=False, **kwargs):
def debug(args, gdbscript=None, gdb_args=None, exe=None, ssh=None, env=None, port=0, gdbserver_args=None, sysroot=None, api=False, **kwargs):
r"""
Launch a GDB server with the specified command line,
and launches GDB to attach to it.
@@ -371,9 +426,12 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
Arguments:
args(list): Arguments to the process, similar to :class:`.process`.
gdbscript(str): GDB script to run.
gdb_args(list): List of additional arguments to pass to GDB.
exe(str): Path to the executable on disk
env(dict): Environment to start the binary in
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
port(int): Gdb port to use, default: random
gdbserver_args(list): List of additional arguments to pass to gdbserver
sysroot(str): Set an alternate system root. The system root is used to
load absolute shared library symbol files. This is useful to instruct
gdb to load a local version of binaries/libraries instead of downloading
@@ -458,33 +516,27 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
>>> io.interactive() # doctest: +SKIP
>>> io.close()
Start a new process with modified argv[0]
Using GDB Python API:
.. doctest
:skipif: six.PY2
Debug a new process
>>> io = gdb.debug(['echo', 'foo'], api=True)
Stop at 'write'
>>> bp = io.gdb.Breakpoint('write', temporary=True)
>>> io.gdb.continue_and_wait()
Dump 'count'
>>> count = io.gdb.parse_and_eval('$rdx')
>>> long = io.gdb.lookup_type('long')
>>> int(count.cast(long))
4
Resume the program
>>> io.gdb.continue_nowait()
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh")
>>> io.sendline(b"echo $0")
>>> io.recvline()
b'foo\n'
b'\xde\xad\xbe\xef\n'
>>> io.close()
Demonstrate that LD_PRELOAD is respected
>>> io = process(["grep", "libc.so.6", "/proc/self/maps"])
>>> real_libc_path = io.recvline().split()[-1]
>>> io.close()
>>> import shutil
>>> local_path = shutil.copy(real_libc_path, "./local-libc.so") # make a copy of libc to demonstrate that it is loaded
>>> io = gdb.debug(["grep", "local-libc.so", "/proc/self/maps"], gdbscript="continue", env={"LD_PRELOAD": "./local-libc.so"})
>>> io.recvline().split()[-1] # doctest: +ELLIPSIS
b'.../local-libc.so'
>>> io.close()
>>> os.remove("./local-libc.so") # cleanup
Using SSH:
@@ -507,8 +559,71 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
>>> io.sendline(b"echo hello")
Interact with the process
>>> io.interactive() # doctest: +SKIP
>>> io.close()
Using a modified args[0] on a remote process
>>> io = gdb.debug(args=[b'\xde\xad\xbe\xef'], gdbscript='continue', exe="/bin/sh", ssh=shell)
>>> io.sendline(b"echo $0")
>>> io.recvline()
b'$ \xde\xad\xbe\xef\n'
>>> io.close()
Using an empty args[0] on a remote process
>>> io = gdb.debug(args=[], gdbscript='continue', exe="/bin/sh", ssh=shell)
>>> io.sendline(b"echo $0")
>>> io.recvline()
b'$ \n'
>>> io.close()
Using GDB Python API:
.. doctest::
:skipif: is_python2
Debug a new process
>>> io = gdb.debug(['echo', 'foo'], api=True)
or using ssh
>>> shell = ssh('travis', 'example.pwnme', password='demopass')
>>> ssh_io = gdb.debug(['/bin/echo', 'foo'], ssh=shell, api=True)
Stop at 'write'
>>> bp = io.gdb.Breakpoint('write', temporary=True)
>>> io.gdb.continue_and_wait()
>>> ssh_bp = ssh_io.gdb.Breakpoint('write', temporary=True)
>>> ssh_io.gdb.continue_and_wait()
Dump 'count'
>>> count = io.gdb.parse_and_eval('$rdx')
>>> long = io.gdb.lookup_type('long')
>>> int(count.cast(long))
4
>>> count = ssh_io.gdb.parse_and_eval('$rdx')
>>> long = ssh_io.gdb.lookup_type('long')
>>> int(count.cast(long))
4
Resume the program
>>> io.gdb.continue_nowait()
>>> io.recvline()
b'foo\n'
>>> io.close()
>>> ssh_io.gdb.continue_nowait()
>>> ssh_io.recvline()
b'foo\n'
>>> ssh_io.close()
>>> shell.close()
"""
if isinstance(args, six.integer_types + (tubes.process.process, tubes.ssh.ssh_channel)):
log.error("Use gdb.attach() to debug a running process")
@@ -522,21 +637,34 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
which = _get_which(ssh)
gdbscript = gdbscript or ''
if api and runner is not tubes.process.process:
raise ValueError('GDB Python API is supported only for local processes')
if api and runner is not tubes.process.process and not ssh:
raise ValueError('GDB Python API is supported only for local and ssh processes')
args, env = misc.normalize_argv_env(args, env, log)
if env:
env = {bytes(k): bytes(v) for k, v in env}
exe = which(packing._decode(exe or args[0]))
if not exe:
log.error("Could not find executable %r" % exe)
if context.noptrace:
log.warn_once("Skipping debugger since context.noptrace==True")
return runner(args, executable=exe, env=env)
if ssh or context.native or (context.os == 'android'):
args = _gdbserver_args(args=args, which=which, env=env)
if len(args) > 0 and which(packing._decode(args[0])) == packing._decode(exe):
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env)
else:
# GDBServer is limited in it's ability to manipulate argv[0]
# but can use the ``--wrapper`` option to execute commands and catches
# ``execve`` calls.
# Therefore, we use a wrapper script to execute the target binary
script = _execve_script(args, executable=exe, env=env, ssh=ssh)
args = _gdbserver_args(gdbserver_args=gdbserver_args, args=args, port=port, which=which, env=env, python_wrapper_script=script)
else:
qemu_port = random.randint(1024, 65535)
qemu_port = port if port != 0 else random.randint(1024, 65535)
qemu_user = qemu.user_path()
sysroot = sysroot or qemu.ld_prefix(env=env)
if not qemu_user:
@@ -557,10 +685,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
if not which(args[0]):
log.error("%s is not installed" % args[0])
if not ssh:
exe = exe or which(orig_args[0])
if not (exe and os.path.exists(exe)):
log.error("%s does not exist" % exe)
# Start gdbserver/qemu
# (Note: We override ASLR here for the gdbserver process itself.)
@@ -569,9 +693,11 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
# Set the .executable on the process object.
gdbserver.executable = exe
# Find what port we need to connect to
if ssh or context.native or (context.os == 'android'):
port = _gdbserver_port(gdbserver, ssh)
gdb_port = _gdbserver_port(gdbserver, ssh)
if port != 0 and port != gdb_port:
log.error("gdbserver port (%d) doesn't equals set port (%d)" % (gdb_port, port))
port = gdb_port
else:
port = qemu_port
@@ -579,16 +705,20 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, api=
if not ssh and context.os == 'android':
host = context.adb_host
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, ssh=ssh, sysroot=sysroot, api=api)
tmp = attach((host, port), exe=exe, gdbscript=gdbscript, gdb_args=gdb_args, ssh=ssh, sysroot=sysroot, api=api)
if api:
_, gdb = tmp
gdbserver.gdb = gdb
# gdbserver outputs a message when a client connects
garbage = gdbserver.recvline(timeout=1)
# Some versions of gdbserver output an additional message
garbage2 = gdbserver.recvline_startswith(b"Remote debugging from host ", timeout=2)
message = b"Remote debugging from host "
if not garbage.startswith(message):
try:
garbage2 = gdbserver.recvline_startswith(message, timeout=2)
except EOFError:
pass
return gdbserver
@@ -615,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:
@@ -645,7 +781,10 @@ class Breakpoint:
"""
# Creates a real breakpoint and connects it with this mirror
self.conn = conn
self.server_breakpoint = conn.root.set_breakpoint(
self.server_breakpoint = self._server_set_breakpoint(*args, **kwargs)
def _server_set_breakpoint(self, *args, **kwargs):
return self.conn.root.set_breakpoint(
self, hasattr(self, 'stop'), *args, **kwargs)
def __getattr__(self, item):
@@ -663,25 +802,43 @@ class Breakpoint:
raise AttributeError()
return getattr(self.server_breakpoint, item)
def __setattr__(self, name, value):
"""Set attributes of the real breakpoint."""
if name in (
'enabled',
'silent',
'thread',
'task',
'ignore_count',
'hit_count'
'condition',
'commands',
):
return setattr(self.server_breakpoint, name, value)
return super().__setattr__(name, value)
def exposed_stop(self):
# Handle stop() call from the server.
return self.stop()
class FinishBreakpoint:
class FinishBreakpoint(Breakpoint):
"""Mirror of ``gdb.FinishBreakpoint`` class.
See https://sourceware.org/gdb/onlinedocs/gdb/Finish-Breakpoints-in-Python.html
for more information.
"""
def __init__(self, conn, *args, **kwargs):
def __init__(self, *args, **kwargs):
"""Do not create instances of this class directly.
Use ``pwnlib.gdb.Gdb.FinishBreakpoint`` instead.
"""
# Creates a real finish breakpoint and connects it with this mirror
self.conn = conn
self.server_breakpoint = conn.root.set_finish_breakpoint(
# See https://github.com/pylint-dev/pylint/issues/4228
# pylint: disable=useless-super-delegation
super().__init__(*args, **kwargs)
def _server_set_breakpoint(self, *args, **kwargs):
return self.conn.root.set_finish_breakpoint(
self, hasattr(self, 'stop'), hasattr(self, 'out_of_scope'),
*args, **kwargs)
@@ -701,10 +858,6 @@ class FinishBreakpoint:
raise AttributeError()
return getattr(self.server_breakpoint, item)
def exposed_stop(self):
# Handle stop() call from the server.
return self.stop()
def exposed_out_of_scope(self):
# Handle out_of_scope() call from the server.
return self.out_of_scope()
@@ -798,6 +951,8 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
Process name. The youngest process is selected.
:obj:`tuple`
Host, port pair of a listening ``gdbserver``
Tries to look up the target exe from the ``gdbserver`` commandline,
requires explicit ``exe`` argument if the target exe is not in the commandline.
:class:`.process`
Process to connect to
:class:`.sock`
@@ -834,8 +989,8 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
Using GDB Python API:
.. doctest
:skipif: six.PY2
.. doctest::
:skipif: is_python2
>>> io = process('bash')
@@ -882,6 +1037,30 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
>>> io.sendline(b'echo Hello from bash && exit')
>>> io.recvall()
b'Hello from bash\n'
>>> server.close()
Attach to a gdbserver / gdbstub running on the local machine
by specifying the host and port tuple it is listening on.
(gdbserver always listens on 0.0.0.0)
>>> gdbserver = process(['gdbserver', '1.2.3.4:12345', '/bin/bash'])
>>> gdbserver.recvline_contains(b'Listening on port', timeout=10)
b'Listening on port 12345'
>>> pid = gdb.attach(('0.0.0.0', 12345), gdbscript='''
... tbreak main
... commands
... call puts("Hello from gdbserver debugger!")
... continue
... end
... ''')
>>> gdbserver.recvline(timeout=10) # doctest: +ELLIPSIS
b'Remote debugging from host 127.0.0.1, ...\n'
>>> gdbserver.recvline(timeout=10)
b'Hello from gdbserver debugger!\n'
>>> gdbserver.sendline(b'echo Hello from bash && exit')
>>> gdbserver.recvline(timeout=10)
b'Hello from bash\n'
>>> gdbserver.close()
Attach to processes running on a remote machine via an SSH :class:`.ssh` process
@@ -1051,7 +1230,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
cmd += [exe]
if pid and not context.os == 'android':
cmd += [str(pid)]
cmd += ['-p', str(pid)]
if context.os == 'android' and pid:
runner = _get_runner()
@@ -1080,13 +1259,12 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
gdbscript = pre + (gdbscript or '')
if gdbscript:
tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
delete = False, mode = 'w+')
log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
with tempfile.NamedTemporaryFile(prefix = 'pwnlib-gdbscript-', suffix = '.gdb',
delete = False, mode = 'w+') as tmp:
log.debug('Wrote gdb script to %r\n%s', tmp.name, gdbscript)
gdbscript = 'shell rm %s\n%s' % (tmp.name, gdbscript)
tmp.write(gdbscript)
tmp.close()
tmp.write(gdbscript)
cmd += ['-x', tmp.name]
log.info('running in new terminal: %s', cmd)
@@ -1100,7 +1278,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
gdb_pid = misc.run_in_new_terminal(cmd, preexec_fn = preexec_fn)
if pid and context.native:
proc.wait_for_debugger(pid, gdb_pid)
gdb_pid = proc.wait_for_debugger(pid, gdb_pid)
if not api:
return gdb_pid
@@ -1124,7 +1302,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
else:
# Check to see if RPyC is installed at all in GDB
rpyc_check = [gdb_binary, '--nx', '-batch', '-ex',
'python import rpyc; import sys; sys.exit(123)']
'python import rpyc; import gdb; gdb.execute("quit 123")']
if 123 != tubes.process.process(rpyc_check).poll(block=True):
log.error('Failed to connect to GDB: rpyc is not installed')
@@ -1343,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
View File
@@ -110,5 +110,6 @@ spawn(ThreadedServer(
socket_path=socket_path,
protocol_config={
'allow_all_attrs': True,
'allow_setattr': True,
},
).start)
+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': [
+624 -127
View File
@@ -4,28 +4,70 @@ Fetch a LIBC binary based on some heuristics.
from __future__ import absolute_import
from __future__ import division
import codecs
import json
import os
import time
import six
import tempfile
import struct
import sys
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.filesystem.path import Path
from pwnlib.log import getLogger
from pwnlib.tubes.process import process
from pwnlib.util.fiddling import b64d
from pwnlib.util.fiddling import enhex
from pwnlib.util.fiddling import hexdump
from pwnlib.util.fiddling import enhex, unhex
from pwnlib.util.hashes import sha1filehex, sha256filehex, md5filehex
from pwnlib.util.misc import read
from pwnlib.util.misc import which
from pwnlib.util.misc import write
from pwnlib.util.safeeval import const
from pwnlib.util.web import wget
log = getLogger(__name__)
HASHES = ['build_id', 'sha1', 'sha256', 'md5']
def _turbofast_extract_build_id(path):
"""
Elf_External_Note:
0x00 +--------+
| namesz | <- Size of entry's owner string
0x04 +--------+
| descsz | <- Size of the note descriptor
0x08 +--------+
| type | <- Interpretation of the descriptor
0x0c +--------+
| name | <- Start of the name+desc data
... +--------
| desc |
... +--------+
"""
data = read(path, 0x1000)
# search NT_GNU_BUILD_ID and b"GNU\x00" (type+name)
idx = data.find(unhex("03000000474e5500"))
if idx == -1:
return enhex(ELF(path, checksec=False).buildid or b'')
descsz, = struct.unpack("<L", data[idx-4: idx])
return enhex(data[idx+8: idx+8+descsz])
TYPES = {
'libs_id': None,
'build_id': _turbofast_extract_build_id,
'sha1': sha1filehex,
'sha256': sha256filehex,
'md5': md5filehex,
}
# mapping for search result (same as libc.rip)
MAP_TYPES = {
'libs_id': 'id',
'build_id': 'buildid'
}
DEBUGINFOD_SERVERS = [
'https://debuginfod.ubuntu.com/',
'https://debuginfod.debian.net/',
'https://debuginfod.elfutils.org/',
]
@@ -33,15 +75,25 @@ if 'DEBUGINFOD_URLS' in os.environ:
urls = os.environ['DEBUGINFOD_URLS'].split(' ')
DEBUGINFOD_SERVERS = urls + DEBUGINFOD_SERVERS
# Allow to override url with a caching proxy in CI
LIBC_RIP_URL = os.environ.get("PWN_LIBCRIP_URL", "https://libc.rip").rstrip("/")
GITLAB_LIBCDB_URL = os.environ.get("PWN_GITLAB_LIBCDB_URL", "https://gitlab.com").rstrip("/")
# Retry failed lookups after some time
NEGATIVE_CACHE_EXPIRY = 60 * 60 * 24 * 7 # 1 week
# https://gitlab.com/libcdb/libcdb wasn't updated after 2019,
# but still is a massive database of older libc binaries.
def provider_libcdb(hex_encoded_id, hash_type):
def provider_libcdb(hex_encoded_id, search_type):
if search_type == 'libs_id':
return None
# Deferred import because it's slow
import requests
from six.moves import urllib
# Build the URL using the requested hash type
url_base = "https://gitlab.com/libcdb/libcdb/raw/master/hashes/%s/" % hash_type
url_base = "{}/libcdb/libcdb/raw/master/hashes/{}/".format(GITLAB_LIBCDB_URL, search_type)
url = urllib.parse.urljoin(url_base, hex_encoded_id)
data = b""
@@ -51,7 +103,7 @@ def provider_libcdb(hex_encoded_id, hash_type):
data = wget(url, timeout=20)
if not data:
log.warn_once("Could not fetch libc for %s %s from libcdb", hash_type, hex_encoded_id)
log.warn_once("Could not fetch libc for %s %s from libcdb", search_type, hex_encoded_id)
break
# GitLab serves up symlinks with
@@ -59,63 +111,177 @@ def provider_libcdb(hex_encoded_id, hash_type):
url = os.path.dirname(url) + '/'
url = urllib.parse.urljoin(url.encode('utf-8'), data)
except requests.RequestException as e:
log.warn_once("Failed to fetch libc for %s %s from libcdb: %s", hash_type, hex_encoded_id, e)
log.warn_once("Failed to fetch libc for %s %s from libcdb: %s", search_type, hex_encoded_id, e)
return data
# https://libc.rip/
def provider_libc_rip(hex_encoded_id, hash_type):
def query_libc_rip(params):
# Deferred import because it's slow
import requests
# Build the request for the hash type
# https://github.com/niklasb/libc-database/blob/master/searchengine/api.yml
if hash_type == 'build_id':
hash_type = 'buildid'
url = "https://libc.rip/api/find"
params = {hash_type: hex_encoded_id}
data = b""
url = "{}/api/find".format(LIBC_RIP_URL)
try:
result = requests.post(url, json=params, timeout=20)
result.raise_for_status()
libc_match = result.json()
if not libc_match:
log.warn_once("Could not find libc for %s %s on libc.rip", hash_type, hex_encoded_id)
return None
if len(libc_match) > 1:
log.debug("Received multiple matches. Choosing the first match and discarding the others.")
log.debug("%r", libc_match)
url = libc_match[0]['download_url']
log.debug("Downloading data from libc.rip: %s", url)
data = wget(url, timeout=20)
if not data:
log.warn_once("Could not fetch libc for %s %s from libc.rip", hash_type, hex_encoded_id)
if result.status_code != 200:
log.debug("Error: %s", result.text)
return None
return result.json()
except requests.RequestException as e:
log.warn_once("Failed to fetch libc for %s %s from libc.rip: %s", hash_type, hex_encoded_id, e)
log.warn_once("Failed to fetch libc info from libc.rip: %s", e)
return None
# https://libc.rip/
def provider_libc_rip(search_target, search_type):
# Build the request for the hash type
# https://github.com/niklasb/libc-database/blob/master/searchengine/api.yml
if search_type in MAP_TYPES.keys():
search_type = MAP_TYPES[search_type]
params = {search_type: search_target}
libc_match = query_libc_rip(params)
if not libc_match:
log.warn_once("Could not find libc info for %s %s on libc.rip", search_type, search_target)
return None
if len(libc_match) > 1:
log.debug("Received multiple matches. Choosing the first match and discarding the others.")
log.debug("%r", libc_match)
url = libc_match[0]['download_url']
log.debug("Downloading data from libc.rip: %s", url)
url = url.replace("https://libc.rip", LIBC_RIP_URL)
data = wget(url, timeout=20)
if not data:
log.warn_once("Could not fetch libc binary for %s %s from libc.rip", search_type, search_target)
return None
return data
PROVIDERS = [provider_libcdb, provider_libc_rip]
# Check if the local system libc matches the requested hash.
def provider_local_system(hex_encoded_id, search_type):
if search_type == 'libs_id':
return None
shell_path = os.environ.get('SHELL', None) or '/bin/sh'
if not os.path.exists(shell_path):
log.debug('Shell path %r does not exist. Skipping local system libc matching.', shell_path)
return None
local_libc = ELF(shell_path, checksec=False).libc
if not local_libc:
log.debug('Cannot lookup libc from shell %r. Skipping local system libc matching.', shell_path)
return None
if TYPES[search_type](local_libc.path) == hex_encoded_id:
return local_libc.data
return None
def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
assert hash_type in HASHES, hash_type
# Offline search https://github.com/niklasb/libc-database for hash type
def provider_local_database(search_target, search_type):
if not context.local_libcdb:
return None
localdb = Path(context.local_libcdb)
if not localdb.is_dir():
return None
# Handle the specific search type 'libs_id'
if search_type == 'libs_id':
libc_list = list(localdb.rglob("%s.so" % search_target))
if len(libc_list) == 0:
return None
return read(libc_list[0])
log.debug("Searching local libc database, %s: %s", search_type, search_target)
for libc_path in localdb.rglob("*.so"):
if search_target == TYPES[search_type](libc_path):
return read(libc_path)
return None
def query_local_database(params):
if not context.local_libcdb or not params.get("symbols"):
return None
localdb = Path(context.local_libcdb)
if not localdb.is_dir():
return None
res = []
query_syms = params["symbols"]
# Loop through each '.symbols' file in the local database
# Make sure `Path.rglod` order stable
for symbol_file in sorted(localdb.rglob("*.symbols"), key=lambda x: x.as_posix()):
libc_syms = _parse_libc_symbol(symbol_file)
matched = 0
for name, addr in query_syms.items():
if isinstance(addr, str):
addr = int(addr, 16)
# Compare last 12 bits
if libc_syms.get(name) and (libc_syms.get(name) & 0xfff) == (addr & 0xfff):
matched += 1
else:
# aborting this loop once there was a mismatch.
break
# Check if all symbols have been matched
if matched == len(query_syms):
libs_id = symbol_file.stem
libc_path = symbol_file.parent / ("%s.so" % libs_id)
libs_url = read(symbol_file.parent / ("%s.url" % libs_id)).decode().strip()
res.append(_pack_libs_info(libc_path, libs_id, libs_url, libc_syms))
return res
PROVIDERS = {
"offline": [provider_local_system, provider_local_database],
"online": [provider_libcdb, provider_libc_rip]
}
def search_by_hash(search_target, search_type='build_id', unstrip=True, offline_only=False):
"""search_by_hash(str, str, bool, bool) -> str
Arguments:
search_target(str):
Use for searching the libc. This could be a hex encoded ID (`hex_encoded_id`) or a library
name (`libs_id`). Depending on `search_type`, this can represent different types of encoded
values or names.
search_type(str):
The type of the search to be performed, it should be one of the keys in the `TYPES` dictionary.
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
If True, restricts the search to offline providers only (local database). If False, it will also
search online providers. Default is False.
Returns:
The path to the cached directory containing the downloaded libraries.
"""
assert search_type in TYPES, search_type
# Ensure that the libcdb cache directory exists
cache, cache_valid = _check_elf_cache('libcdb', hex_encoded_id, hash_type)
cache, cache_valid = _check_elf_cache('libcdb', search_target, search_type)
if cache_valid:
if unstrip:
unstrip_libc(cache)
return cache
# We searched for this buildid before, but didn't find anything.
if cache is None:
return None
providers = PROVIDERS["offline"]
if not offline_only:
providers += PROVIDERS["online"]
# Run through all available libc database providers to see if we have a match.
for provider in PROVIDERS:
data = provider(hex_encoded_id, hash_type)
for provider in providers:
data = provider(search_target, search_type)
if data and data.startswith(b'\x7FELF'):
break
if not data:
log.warn_once("Could not find libc for %s %s anywhere", hash_type, hex_encoded_id)
log.warn_once("Could not find libc for %s %s anywhere", search_type, search_target)
# Save whatever we got to the cache
write(cache, data or b'')
@@ -130,37 +296,7 @@ def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
return cache
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
# Deferred import because it's slow
import requests
from six.moves import urllib
# Check if we tried this buildid before.
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
if cache_valid:
return cache
# Try to find separate debuginfo.
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
url = urllib.parse.urljoin(base_url, url)
data = b""
log.debug("Downloading data from debuginfod: %s", url)
try:
data = wget(url, timeout=20)
except requests.RequestException as e:
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, base_url, e)
# Save whatever we got to the cache
write(cache, data or b'')
# Return ``None`` if we did not get a valid ELF file
if not data or not data.startswith(b'\x7FELF'):
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, base_url)
return None
return cache
def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
def _check_elf_cache(cache_type, search_target, search_type):
"""
Check if there already is an ELF file for this hash in the cache.
@@ -173,14 +309,14 @@ def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
True
"""
# Ensure that the cache directory exists
cache_dir = os.path.join(context.cache_dir, cache_type, hash_type)
cache_dir = os.path.join(context.cache_dir, cache_type, search_type)
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
# If we already downloaded the file, and it looks even passingly like
# a valid ELF file, return it.
cache = os.path.join(cache_dir, hex_encoded_id)
cache = os.path.join(cache_dir, search_target)
if not os.path.exists(cache):
return cache, False
@@ -189,8 +325,11 @@ def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
data = read(cache)
if not data.startswith(b'\x7FELF'):
log.info_once("Skipping unavailable ELF %s", hex_encoded_id)
return cache, False
# Retry failed lookups after some time
if time.time() > os.path.getmtime(cache) + NEGATIVE_CACHE_EXPIRY:
return cache, False
log.info_once("Skipping invalid cached ELF %s", search_target)
return None, False
log.info_once("Using cached data from %r", cache)
return cache, True
@@ -210,19 +349,20 @@ def unstrip_libc(filename):
:const:`True` if binary was unstripped, :const:`False` otherwise.
Examples:
>>> filename = search_by_build_id('69389d485a9793dbe873f0ea2c93e02efaa9aa3d', unstrip=False)
>>> 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'):
@@ -234,18 +374,51 @@ def unstrip_libc(filename):
log.warn_once('Given libc does not have a buildid. Cannot look for debuginfo to unstrip.')
return False
if libc.debuginfo:
log.debug('Given libc already contains debug information. Skipping unstrip.')
return True
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()
@@ -255,6 +428,214 @@ def unstrip_libc(filename):
return True
def _extract_tarfile(cache_dir, data_filename, tarball):
from six import BytesIO
import tarfile
# Handle zstandard compression, since tarfile only supports gz, bz2, and xz.
if data_filename.endswith('.zst') or data_filename.endswith('.zstd'):
import zstandard
dctx = zstandard.ZstdDecompressor()
decompressed_tar = BytesIO()
dctx.copy_stream(tarball, decompressed_tar)
decompressed_tar.seek(0)
tarball.close()
tarball = decompressed_tar
if six.PY2 and data_filename.endswith('.xz'):
# Python 2's tarfile doesn't support xz, so we need to decompress it first.
# Shell out to xz, since the Python 2 pylzma module is broken.
# (https://github.com/fancycode/pylzma/issues/67)
if not which('xz'):
log.error('Couldn\'t find "xz" in PATH. Please install xz first.')
import subprocess
try:
uncompressed_tarball = subprocess.check_output(['xz', '--decompress', '--stdout', tarball.name])
tarball = BytesIO(uncompressed_tarball)
except subprocess.CalledProcessError:
log.error('Failed to decompress xz archive.')
with tarfile.open(fileobj=tarball) as tar_file:
# Find the library folder in the archive (e.g. /lib/x86_64-linux-gnu/)
lib_dir = None
libc_name = None
for member in tar_file.getmembers():
if not member.isfile():
continue
libc_name = os.path.basename(member.name)
if libc_name == 'libc.so.6' or (libc_name.startswith('libc') and libc_name.endswith('.so')):
lib_dir = os.path.dirname(member.name)
break
else:
log.error('Couldn\'t find library folder containing the libc in the archive.')
# Extract everything in the library folder
for member in tar_file.getmembers():
if os.path.dirname(member.name) != lib_dir:
continue
if not member.isfile() and not member.issym():
continue
# Extract while keeping file permissions
tar_file.extract(member, cache_dir)
# Move the files up to the cache root
target_dir = os.path.join(cache_dir, lib_dir)
for file in os.listdir(target_dir):
os.rename(os.path.join(target_dir, file), os.path.join(cache_dir, file))
os.removedirs(target_dir)
return os.path.join(cache_dir, libc_name)
def _extract_debfile(cache_dir, package_filename, package):
# Extract data.tar in the .deb archive.
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.')
import atexit
import shutil
import subprocess
# Use mkdtemp instead of TemporaryDirectory because the latter is not available in Python 2.
tempdir = tempfile.mkdtemp(prefix=".pwntools-tmp")
atexit.register(shutil.rmtree, tempdir)
with tempfile.NamedTemporaryFile(mode='wb', dir=tempdir) as debfile:
debfile.write(package)
debfile.flush()
try:
files_in_deb = subprocess.check_output(['ar', 't', debfile.name]).split(b'\n')
except subprocess.CalledProcessError:
log.error('Failed to list files in .deb archive.')
[data_filename] = filter(lambda f: f.startswith(b'data.tar'), files_in_deb)
try:
subprocess.check_call(['ar', 'x', debfile.name, data_filename], cwd=tempdir)
except subprocess.CalledProcessError:
log.error('Failed to extract data.tar from .deb archive.')
with open(os.path.join(tempdir, data_filename), 'rb') as tarball:
return _extract_tarfile(cache_dir, data_filename, tarball)
else:
import unix_ar
from six import BytesIO
ar_file = unix_ar.open(BytesIO(package))
try:
data_filename = next(filter(lambda f: f.name.startswith(b'data.tar'), ar_file.infolist())).name.decode()
tarball = ar_file.open(data_filename)
return _extract_tarfile(cache_dir, data_filename, tarball)
finally:
ar_file.close()
def _extract_pkgfile(cache_dir, package_filename, package):
from six import BytesIO
return _extract_tarfile(cache_dir, package_filename, BytesIO(package))
def _find_libc_package_lib_url(libc):
# Check https://libc.rip for the libc package
libc_match = query_libc_rip({'buildid': enhex(libc.buildid)})
if libc_match is not None:
for match in libc_match:
# Allow to override url with a caching proxy in CI
ubuntu_archive_url = os.environ.get('PWN_UBUNTU_ARCHIVE_URL', 'http://archive.ubuntu.com').rstrip('/')
yield match['libs_url'].replace('http://archive.ubuntu.com', ubuntu_archive_url)
# Check launchpad.net if it's an Ubuntu libc
# GNU C Library (Ubuntu GLIBC 2.36-0ubuntu4)
import re
version = re.search(br'GNU C Library \(Ubuntu E?GLIBC ([^\)]+)\)', libc.data)
if version is not None:
libc_version = version.group(1).decode()
yield 'https://launchpad.net/ubuntu/+archive/primary/+files/libc6_{}_{}.deb'.format(libc_version, libc.arch)
def download_libraries(libc_path, unstrip=True):
"""download_libraries(str, bool) -> str
Download the matching libraries for the given libc binary and cache
them in a local directory. The libraries are looked up using `libc.rip <https://libc.rip>`_
and fetched from the official package repositories if available.
This commonly includes the ``ld-linux-x86-64.so.2`` and ``libpthread.so.0`` binaries
which can be used to execute the program locally when the given libc is
incompatible with the local dynamic loader.
Note: Only .deb and .pkg.tar.* packages are currently supported (Debian/Ubuntu, Arch).
Arguments:
libc_path(str):
The path the libc binary.
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
The path to the cached directory containing the downloaded libraries.
Example:
>>> libc_path = ELF(which('ls'), checksec=False).libc.path
>>> lib_path = download_libraries(libc_path)
>>> lib_path is not None
True
>>> os.path.exists(os.path.join(lib_path, 'libc.so.6'))
True
>>> os.path.exists(os.path.join(lib_path, 'ld-linux-x86-64.so.2'))
True
"""
libc = ELF(libc_path, checksec=False)
if not libc.buildid:
log.warn_once('Given libc does not have a buildid.')
return None
# Handle caching and don't redownload if it already exists.
cache_dir = os.path.join(context.cache_dir, 'libcdb_libs')
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
cache_dir = os.path.join(cache_dir, enhex(libc.buildid))
if os.path.exists(cache_dir):
return cache_dir
for package_url in _find_libc_package_lib_url(libc):
extension_handlers = {
'.deb': _extract_debfile,
'.pkg.tar.xz': _extract_pkgfile,
'.pkg.tar.zst': _extract_pkgfile,
}
package_filename = os.path.basename(package_url)
for extension, handler in extension_handlers.items():
if package_filename.endswith(extension):
break
else:
log.failure('Cannot handle %s (%s)', package_filename, package_url)
continue
# Download the package
package = wget(package_url, timeout=20)
if not package:
continue
# Create target cache directory to extract files into
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
try:
# Extract the archive
libc_path = handler(cache_dir, package_filename, package)
except Exception as e:
os.removedirs(cache_dir)
log.failure('Failed to extract %s: %s', package_filename, e)
continue
# Unstrip the libc binary
try:
if unstrip:
unstrip_libc(libc_path)
except Exception:
pass
return cache_dir
log.warn_once('Failed to find matching libraries for provided libc.')
return None
def _handle_multiple_matching_libcs(matching_libcs):
from pwnlib.term import text
from pwnlib.ui import options
@@ -272,7 +653,7 @@ def _handle_multiple_matching_libcs(matching_libcs):
selected_index = options("Select the libc version to use:", [libc['id'] for libc in matching_libcs])
return matching_libcs[selected_index]
def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as_list=False):
def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, offline_only=False, search_type='build_id', return_as_list=False, return_raw=False):
"""
Lookup possible matching libc versions based on leaked function addresses.
@@ -291,9 +672,16 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as
The libc to select if there are multiple matches (starting at 1).
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
When pass `offline_only=True`, restricts search mode to offline sources only,
disable online lookup. Defaults to `False`, and enable both offline and online providers.
search_type(str):
An option to select searched hash.
return_as_list(bool):
Return a list of build ids of all matching libc versions
instead of a path to a downloaded file.
return_raw(bool):
Return raw list of matched libc.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -301,6 +689,7 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as
is returned instead.
Examples:
>>> filename = search_by_symbol_offsets({'puts': 0x420, 'printf': 0xc90}, select_index=1)
>>> libc = ELF(filename)
>>> libc.sym.system == 0x52290
@@ -311,43 +700,91 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, return_as
>>> for buildid in matched_libcs: # doctest +SKIP
... libc = ELF(search_by_build_id(buildid)) # doctest +SKIP
"""
import requests
assert search_type in TYPES, search_type
for symbol, address in symbols.items():
if isinstance(address, int):
symbols[symbol] = hex(address)
try:
params = {'symbols': symbols}
url = "https://libc.rip/api/find"
log.debug('Request: %s', params)
result = requests.post(url, json=params, timeout=20)
result.raise_for_status()
matching_libcs = result.json()
log.debug('Result: %s', matching_libcs)
if len(matching_libcs) == 0:
log.warn_once("No matching libc for symbols %r on libc.rip", symbols)
return None
params = {'symbols': symbols}
log.debug('Request: %s', params)
if return_as_list:
return [libc['buildid'] for libc in matching_libcs]
offline_matching = query_local_database(params)
online_matching = query_libc_rip(params) if not offline_only else None
if len(matching_libcs) == 1:
return search_by_build_id(matching_libcs[0]['buildid'], unstrip=unstrip)
if offline_matching is None:
offline_matching = []
if online_matching is None:
online_matching = []
if select_index is not None:
if select_index > 0 and select_index <= len(matching_libcs):
return search_by_build_id(matching_libcs[select_index - 1]['buildid'], unstrip=unstrip)
else:
log.error('Invalid selected libc index. %d is not in the range of 1-%d.', select_index, len(matching_libcs))
return None
# Aggregate and deduplicate matches from both sources
matching_libcs = {}
for libc in offline_matching + online_matching:
if libc['id'] not in matching_libcs:
matching_libcs[libc['id']] = libc
selected_libc = _handle_multiple_matching_libcs(matching_libcs)
return search_by_build_id(selected_libc['buildid'], unstrip=unstrip)
except requests.RequestException as e:
log.warn_once("Failed to lookup libc for symbols %r from libc.rip: %s", symbols, e)
log.debug('Offline search result: %s', offline_matching)
if not offline_only:
log.debug('Online search result: %s', online_matching)
# Check if no matches are found
if len(matching_libcs) == 0:
log.warn_once("No matching libc for symbols %r", symbols)
return None
def search_by_build_id(hex_encoded_id, unstrip=True):
matching_list = list(matching_libcs.values())
if return_as_list:
return [libc['buildid'] for libc in matching_list]
if return_raw:
return matching_list
mapped_type = MAP_TYPES.get(search_type, search_type)
# If there's only one match, return it directly
if len(matching_list) == 1:
return search_by_hash(matching_list[0][mapped_type], search_type=search_type, unstrip=unstrip, offline_only=offline_only)
# If a specific index is provided, validate it and return the selected libc
if select_index is not None:
if select_index > 0 and select_index <= len(matching_list):
return search_by_hash(matching_list[select_index - 1][mapped_type], search_type=search_type, unstrip=unstrip, offline_only=offline_only)
else:
log.error('Invalid selected libc index. %d is not in the range of 1-%d.', select_index, len(matching_list))
return None
# Handle multiple matches interactively if no index is specified
selected_libc = _handle_multiple_matching_libcs(matching_list)
return search_by_hash(selected_libc[mapped_type], search_type=search_type, unstrip=unstrip, offline_only=offline_only)
def search_by_libs_id(libs_id, unstrip=True, offline_only=False):
"""
Given a Libs ID, attempt to download a matching libc from libcdb.
Arguments:
libs_id(str):
Libs ID (e.g. ``'libc6_...'``) of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
When pass ``offline_only=True``, restricts search mode to offline sources only,
disable online lookup. Defaults to :const:`False`, and enable both offline and online providers.
Returns:
Path to the downloaded library on disk, or :const:`None`.
Examples:
>>> None == search_by_libs_id('XX')
True
>>> filename = search_by_libs_id('libc6_2.31-3_amd64')
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(libs_id, 'libs_id', unstrip, offline_only)
def search_by_build_id(hex_encoded_id, unstrip=True, offline_only=False):
"""
Given a hex-encoded Build ID, attempt to download a matching libc from libcdb.
@@ -356,11 +793,15 @@ def search_by_build_id(hex_encoded_id, unstrip=True):
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
When pass `offline_only=True`, restricts search mode to offline sources only,
disable online lookup. Defaults to `False`, and enable both offline and online providers.
Returns:
Path to the downloaded library on disk, or :const:`None`.
Examples:
>>> filename = search_by_build_id('fe136e485814fee2268cf19e5c124ed0f73f4400')
>>> hex(ELF(filename).symbols.read)
'0xda260'
@@ -370,9 +811,9 @@ def search_by_build_id(hex_encoded_id, unstrip=True):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'build_id', unstrip)
return search_by_hash(hex_encoded_id, 'build_id', unstrip, offline_only)
def search_by_md5(hex_encoded_id, unstrip=True):
def search_by_md5(hex_encoded_id, unstrip=True, offline_only=False):
"""
Given a hex-encoded md5sum, attempt to download a matching libc from libcdb.
@@ -381,11 +822,15 @@ def search_by_md5(hex_encoded_id, unstrip=True):
Hex-encoded md5sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
When pass `offline_only=True`, restricts search mode to offline sources only,
disable online lookup. Defaults to `False`, and enable both offline and online providers.
Returns:
Path to the downloaded library on disk, or :const:`None`.
Examples:
>>> filename = search_by_md5('7a71dafb87606f360043dcd638e411bd')
>>> hex(ELF(filename).symbols.read)
'0xda260'
@@ -395,9 +840,9 @@ def search_by_md5(hex_encoded_id, unstrip=True):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'md5', unstrip)
return search_by_hash(hex_encoded_id, 'md5', unstrip, offline_only)
def search_by_sha1(hex_encoded_id, unstrip=True):
def search_by_sha1(hex_encoded_id, unstrip=True, offline_only=False):
"""
Given a hex-encoded sha1, attempt to download a matching libc from libcdb.
@@ -406,11 +851,15 @@ def search_by_sha1(hex_encoded_id, unstrip=True):
Hex-encoded sha1sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
When pass `offline_only=True`, restricts search mode to offline sources only,
disable online lookup. Defaults to `False`, and enable both offline and online providers.
Returns:
Path to the downloaded library on disk, or :const:`None`.
Examples:
>>> filename = search_by_sha1('34471e355a5e71400b9d65e78d2cd6ce7fc49de5')
>>> hex(ELF(filename).symbols.read)
'0xda260'
@@ -420,10 +869,9 @@ def search_by_sha1(hex_encoded_id, unstrip=True):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'sha1', unstrip)
return search_by_hash(hex_encoded_id, 'sha1', unstrip, offline_only)
def search_by_sha256(hex_encoded_id, unstrip=True):
def search_by_sha256(hex_encoded_id, unstrip=True, offline_only=False):
"""
Given a hex-encoded sha256, attempt to download a matching libc from libcdb.
@@ -432,11 +880,15 @@ def search_by_sha256(hex_encoded_id, unstrip=True):
Hex-encoded sha256sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
offline_only(bool):
When pass `offline_only=True`, restricts search mode to offline sources only,
disable online lookup. Defaults to `False`, and enable both offline and online providers.
Returns:
Path to the downloaded library on disk, or :const:`None`.
Examples:
>>> filename = search_by_sha256('5e877a8272da934812d2d1f9ee94f73c77c790cbc5d8251f5322389fc9667f21')
>>> hex(ELF(filename).symbols.read)
'0xda260'
@@ -446,9 +898,54 @@ def search_by_sha256(hex_encoded_id, unstrip=True):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'sha256', unstrip)
return search_by_hash(hex_encoded_id, 'sha256', unstrip, offline_only)
def _parse_libc_symbol(path):
"""
Parse symbols file to `dict`, the format is same as https://github.com/niklasb/libc-database/
"""
syms = {}
with open(path, "r") as fd:
for x in fd:
name, addr = x.split(" ")
syms[name] = int(addr, 16)
return syms
def _pack_libs_info(path, libs_id, libs_url, syms):
"""
The JSON format is the same as libc.rip, and the "download_url" field is by default an empty string,
as it's not required in offline mode.
"""
info = {}
info["id"] = libs_id
info["libs_url"] = libs_url
info["download_url"] = ""
for search_type, hash_func in TYPES.items():
# pass libs_id
if search_type == 'libs_id':
continue
# replace search_type
if search_type in MAP_TYPES.keys():
search_type = MAP_TYPES[search_type]
info[search_type] = hash_func(path)
default_symbol_list = [
"__libc_start_main_ret", "dup2", "printf", "puts", "read", "system", "str_bin_sh"
]
info["symbols"] = {}
for name in default_symbol_list:
info["symbols"][name] = hex(syms[name])
return info
def get_build_id_offsets():
@@ -503,4 +1000,4 @@ def get_build_id_offsets():
}.get(context.arch, [])
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'unstrip_libc', 'search_by_symbol_offsets']
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'search_by_libs_id', 'unstrip_libc', 'search_by_symbol_offsets', 'download_libraries']
+1 -1
View File
@@ -560,7 +560,7 @@ class Handler(logging.StreamHandler):
# we enrich the `Progress` object to keep track of the spinner
if not hasattr(progress, '_spinner_handle'):
spinner_handle = term.output('')
spinner_handle = term.output('[x] ')
msg_handle = term.output(msg)
stop = threading.Event()
def spin():
+94
View File
@@ -0,0 +1,94 @@
"""
Compatibility layer with python 2, allowing us to write normal code.
Beware, some monkey-patching is done.
"""
import os
import shutil
import sys
try:
import fcntl
import termios
except ImportError:
pass
from collections import namedtuple
from struct import Struct
def py2_monkey_patch(module):
def decorator(f):
if sys.version_info < (3,):
f.__module__ = module.__name__
setattr(module, f.__name__, f)
return decorator
# python3 -c 'import shutil,inspect; print(inspect.getsource(shutil.get_terminal_size))'
@py2_monkey_patch(shutil)
def get_terminal_size(fallback=(80, 24)):
"""Get the size of the terminal window.
For each of the two dimensions, the environment variable, COLUMNS
and LINES respectively, is checked. If the variable is defined and
the value is a positive integer, it is used.
When COLUMNS or LINES is not defined, which is the common case,
the terminal connected to sys.__stdout__ is queried
by invoking os.get_terminal_size.
If the terminal size cannot be successfully queried, either because
the system doesn't support querying, or because we are not
connected to a terminal, the value given in fallback parameter
is used. Fallback defaults to (80, 24) which is the default
size used by many terminal emulators.
The value returned is a named tuple of type os.terminal_size.
"""
# columns, lines are the working values
try:
columns = int(os.environ['COLUMNS'])
except (KeyError, ValueError):
columns = 0
try:
lines = int(os.environ['LINES'])
except (KeyError, ValueError):
lines = 0
# only query if necessary
if columns <= 0 or lines <= 0:
try:
size = os.get_terminal_size(sys.__stdout__.fileno())
except (AttributeError, ValueError, IOError):
# stdout is None, closed, detached, or not a terminal, or
# os.get_terminal_size() is unsupported
size = os.terminal_size(fallback)
if columns <= 0:
columns = size.columns
if lines <= 0:
lines = size.lines
return os.terminal_size((columns, lines))
@py2_monkey_patch(os)
class terminal_size(tuple):
@property
def columns(self):
return self[0]
@property
def lines(self):
return self[1]
def __repr__(self):
return 'os.terminal_size(columns=%r, lines=%r)' % self
terminal_size = namedtuple('terminal_size', 'columns lines')
termsize = Struct('HHHH')
@py2_monkey_patch(os)
def get_terminal_size(fd): # pylint: disable=function-redefined
arr = b'\0' * termsize.size
arr = fcntl.ioctl(fd, termios.TIOCGWINSZ, arr)
lines, columns, xpixel, ypixel = termsize.unpack(arr)
return os.terminal_size((columns, lines))
+1 -1
View File
@@ -139,7 +139,7 @@ def user_path():
def ld_prefix(path=None, env=None):
"""Returns the linker prefix for the selected qemu-user binary
>>> pwnlib.qemu.ld_prefix(arch='arm')
>>> pwnlib.qemu.ld_prefix(arch='arm') # doctest: +SKIP
'/etc/qemu-binfmt/arm'
"""
if context.os == 'baremetal':
Loaded 100 of 701 files, more files were not shown because too many files have changed in this diff. Show more