mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-08 01:58:16 -10:00
Compare commits
70
Commits
4.15.0beta1
...
stable
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8bc6f6cd2b | ||
|
|
455c8c02c3 | ||
|
|
dbc618de0d | ||
|
|
34b8345d1d | ||
|
|
771c7f81ac | ||
|
|
8d69dc6142 | ||
|
|
3911a6a12e | ||
|
|
a5f3873194 | ||
|
|
481704bfd8 | ||
|
|
5196a1a0f0 | ||
|
|
be705326a7 | ||
|
|
c7e6ef37c8 | ||
|
|
210faac75b | ||
|
|
8e0e4c6c90 | ||
|
|
bbac6660b2 | ||
|
|
2a986f12c1 | ||
|
|
b60ed9928d | ||
|
|
b1391984d1 | ||
|
|
dd747e1062 | ||
|
|
78fccc055f | ||
|
|
4f50b45586 | ||
|
|
a1c7d07db8 | ||
|
|
6691e8476a | ||
|
|
a8bcdf6f8d | ||
|
|
bf5e4df220 | ||
|
|
68b5bbf16b | ||
|
|
9a7e197754 | ||
|
|
96c3ede156 | ||
|
|
f2eba7c8fd | ||
|
|
1c7d917432 | ||
|
|
1553670f9d | ||
|
|
8f67b7b7b5 | ||
|
|
459dfd7e35 | ||
|
|
d8d47977ac | ||
|
|
3daf28b6be | ||
|
|
f4b3e6e1c4 | ||
|
|
2b39684912 | ||
|
|
7e5e79d00f | ||
|
|
e551b4ae56 | ||
|
|
43ef1b494c | ||
|
|
be92aa1b4c | ||
|
|
07f3caadc4 | ||
|
|
3ad86ec5ad | ||
|
|
b5076d9262 | ||
|
|
77b4f06b07 | ||
|
|
4968499d11 | ||
|
|
f38991bc02 | ||
|
|
d0e6ee4b37 | ||
|
|
48ea09b27f | ||
|
|
9f22a2f202 | ||
|
|
7413a3da31 | ||
|
|
95aac6e367 | ||
|
|
9ed93a5281 | ||
|
|
3579fb1384 | ||
|
|
485bf0a0b7 | ||
|
|
b133b76b4e | ||
|
|
ffc46d277a | ||
|
|
dd3a5f6974 | ||
|
|
9a275b2db0 | ||
|
|
1c28222fe6 | ||
|
|
b93c5ace10 | ||
|
|
da9797c1c2 | ||
|
|
6dfe8b6f72 | ||
|
|
425221fb30 | ||
|
|
004a6dab88 | ||
|
|
6098f28bfa | ||
|
|
1205b1aaa3 | ||
|
|
ce6d2ca847 | ||
|
|
9e0ca3a415 | ||
|
|
b60b2e897f |
No files matched your search
@@ -5,7 +5,7 @@ jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
python_version: ['3.10', '3.12']
|
||||
python_version: ['3.10', '3.12', '3.13', '3.14', '3.15']
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- python_version: '2.7'
|
||||
@@ -22,8 +22,9 @@ jobs:
|
||||
- 3001:3001 # https://libc.rip/ proxy cache
|
||||
- 3002:3002 # http://archive.ubuntu.com/ proxy cache
|
||||
- 3003:3003 # https://gitlab.com/ proxy cache
|
||||
- 3004:3004 # https://debuginfod.ubuntu.com/ proxy cache
|
||||
env:
|
||||
DEBUGINFOD_URLS: http://localhost:3000/
|
||||
DEBUGINFOD_URLS: "http://localhost:3000/ http://localhost:3004/"
|
||||
PWN_LIBCRIP_URL: http://localhost:3001/
|
||||
PWN_UBUNTU_ARCHIVE_URL: http://localhost:3002/
|
||||
PWN_GITLAB_LIBCDB_URL: http://localhost:3003/
|
||||
@@ -120,6 +121,7 @@ jobs:
|
||||
binutils-aarch64-linux-gnu \
|
||||
binutils-arm-linux-gnueabihf \
|
||||
binutils-mips-linux-gnu \
|
||||
binutils-mipsel-linux-gnu \
|
||||
binutils-msp430 \
|
||||
binutils-powerpc-linux-gnu \
|
||||
binutils-s390x-linux-gnu \
|
||||
@@ -148,6 +150,8 @@ jobs:
|
||||
pip install --upgrade wheel build
|
||||
pip install --upgrade flake8 appdirs
|
||||
pip install --upgrade --editable .
|
||||
# Pin to known working version until 2.1.5 is out. https://github.com/unicorn-engine/unicorn/issues/2134
|
||||
pip install 'unicorn==2.1.2'
|
||||
|
||||
- name: Sanity checks
|
||||
run: PWNLIB_NOTERM=1 python -bb -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
@@ -283,7 +287,7 @@ jobs:
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade --editable .
|
||||
|
||||
|
||||
- name: Sanity checks
|
||||
run: |
|
||||
python -bb -c 'from pwn import *'
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: PyLint
|
||||
run: |
|
||||
set -x
|
||||
pip install pylint
|
||||
pip install 'pylint<4'
|
||||
pip install --upgrade -e .
|
||||
run_pylint() { pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- | sed 's/line [0-9]\+/line XXXX/g'; }
|
||||
run_pylint > current.txt
|
||||
|
||||
+109
-13
@@ -10,8 +10,8 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
| Version | Branch | Release Date |
|
||||
| ---------------- | -------- | ---------------------- |
|
||||
| [5.0.0](#500-dev) | `dev` |
|
||||
| [4.15.0](#4150-beta) | `beta` |
|
||||
| [4.14.0](#4141-stable) | `stable` | Mar 24, 2025
|
||||
| [4.15.0](#4150-stable) | `stable` | Oct 12, 2025
|
||||
| [4.14.1](#4141) | | Mar 24, 2025
|
||||
| [4.14.0](#4140) | | Jan 15, 2025
|
||||
| [4.13.1](#4131) | | Sep 29, 2024
|
||||
| [4.13.0](#4130) | | Aug 12, 2024
|
||||
@@ -75,35 +75,105 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
|
||||
## 5.0.0 (`dev`)
|
||||
|
||||
- [#2419][2419] riscv: avoid compressed instructions (if you need compressed, use .option rvc)
|
||||
- [#2551][2551] Detect when kitty is being used as terminal
|
||||
- [#2519][2519] Drop Python 2.7 support / Require Python 3.10
|
||||
- [#2507][2507] Add `+LINUX` and `+WINDOWS` doctest options and start proper testing on Windows
|
||||
- [#2522][2522] Support starting a kitty debugging window with the 'kitten' command
|
||||
- [#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
|
||||
- [#2527][2527] Allow setting debugger path via `context.gdb_binary`
|
||||
- [#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
|
||||
- [#2608][2608] Abort on `libcdb file libc.so --unstrip` if eu-unstrip is not installed
|
||||
- [#2611][2611] Cleanup `pwnlib.lexer` exports and imports
|
||||
- [#2615][2615] tube/process: Fix redirecting stderr to stdout on Windows
|
||||
|
||||
[2419]: https://github.com/Gallopsled/pwntools/pull/2419
|
||||
[2551]: https://github.com/Gallopsled/pwntools/pull/2551
|
||||
[2519]: https://github.com/Gallopsled/pwntools/pull/2519
|
||||
[2507]: https://github.com/Gallopsled/pwntools/pull/2507
|
||||
[2522]: https://github.com/Gallopsled/pwntools/pull/2522
|
||||
[2524]: https://github.com/Gallopsled/pwntools/pull/2524
|
||||
[2526]: https://github.com/Gallopsled/pwntools/pull/2526
|
||||
[2517]: https://github.com/Gallopsled/pwntools/pull/2517
|
||||
[2527]: https://github.com/Gallopsled/pwntools/pull/2527
|
||||
[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
|
||||
[2608]: https://github.com/Gallopsled/pwntools/pull/2608
|
||||
[2611]: https://github.com/Gallopsled/pwntools/pull/2611
|
||||
[2615]: https://github.com/Gallopsled/pwntools/pull/2615
|
||||
|
||||
## 4.15.0 (`beta`)
|
||||
## 4.15.1
|
||||
|
||||
- [#2694][2694] fix: pad bytes fields to correct field size in FileStructure
|
||||
- [#2701][2701] Fix `adb._build_date()` crash on devices with non-standard locale date strings
|
||||
- [#2721][2721] Allow running with Unicorn 2.1.[34] but throw when emulating MIPS
|
||||
- [#2773][2773] Disable debuginfod doctests in libcdb.unstrip_libc
|
||||
- [#2772][2772] fix(packing): `dd()` no longer crashes on a `bytearray` destination
|
||||
- [#2787][2787] gdb.corefile: Run gdb directly to prevent detach race
|
||||
- [#2795][2795] regsort: Fix self-assignment detection through register aliases
|
||||
- [#2524][2524] Raise EOFError during `process.recv` when stdout closes on Windows
|
||||
- [#2530][2530] Do NOT error when passing directory arguments in `checksec` commandline tool.
|
||||
- [#2506][2506] ROP: fix `ROP(ELF(exe)).leave` is `None` in some ELF
|
||||
- [#2592][2592] pwnlib.config: Fix customization of `context.timeout`
|
||||
- [#2598][2598] aarch64: Fix ABI definition
|
||||
- [#2610][2610] Fix `log.progress` ignoring `context.log_console`
|
||||
- [#2627][2627] Remove `pwnlib.util.iters.lookahead` (broken anyway, fails on Python 3.14+)
|
||||
- [#2653][2653] Add aarch64_be qemu mapping
|
||||
- [#2642][2642] Remove `pip` as explicit dependency
|
||||
- [#2646][2646] fix(libcdb-cli): return early if no matched libc found
|
||||
- [#2669][2669] asm: try native binutils before fallback architectures
|
||||
- [#2677][2677] refactor: replace unsafe eval with safeeval.const in ROP cache loading
|
||||
- [#2699][2699] Fix `tty` and `raw` arguments in `ssh.process()`
|
||||
- [#2688][2688] Close SSH client connection when authentication failed
|
||||
- [#2704][2704] ssh: Fix distro lookup on Ubuntu 24.04+
|
||||
- [#2762][2762] fix(srop): correct amd64 SigreturnFrame `uc_sigmask` offset
|
||||
- [#2767][2767] Support paginated libc.rip API requests to include all search results
|
||||
- [#2739][2739] shellcraft: migrate lazy importer to find_spec for Python 3.12+
|
||||
- [#2792][2792] Fix deprecation warning for not using `RLIM_INFINITY` in `resource.setrlimit` (Python 3.15)
|
||||
|
||||
[2694]: https://github.com/Gallopsled/pwntools/pull/2694
|
||||
[2701]: https://github.com/Gallopsled/pwntools/pull/2701
|
||||
[2721]: https://github.com/Gallopsled/pwntools/pull/2721
|
||||
[2773]: https://github.com/Gallopsled/pwntools/pull/2773
|
||||
[2772]: https://github.com/Gallopsled/pwntools/pull/2772
|
||||
[2787]: https://github.com/Gallopsled/pwntools/pull/2787
|
||||
[2795]: https://github.com/Gallopsled/pwntools/pull/2795
|
||||
[2524]: https://github.com/Gallopsled/pwntools/pull/2524
|
||||
[2530]: https://github.com/Gallopsled/pwntools/pull/2530
|
||||
[2506]: https://github.com/Gallopsled/pwntools/pull/2506
|
||||
[2592]: https://github.com/Gallopsled/pwntools/pull/2592
|
||||
[2598]: https://github.com/Gallopsled/pwntools/pull/2598
|
||||
[2610]: https://github.com/Gallopsled/pwntools/pull/2610
|
||||
[2627]: https://github.com/Gallopsled/pwntools/pull/2627
|
||||
[2653]: https://github.com/Gallopsled/pwntools/pull/2653
|
||||
[2642]: https://github.com/Gallopsled/pwntools/pull/2642
|
||||
[2646]: https://github.com/Gallopsled/pwntools/pull/2646
|
||||
[2669]: https://github.com/Gallopsled/pwntools/pull/2669
|
||||
[2677]: https://github.com/Gallopsled/pwntools/pull/2677
|
||||
[2699]: https://github.com/Gallopsled/pwntools/pull/2699
|
||||
[2688]: https://github.com/Gallopsled/pwntools/pull/2688
|
||||
[2704]: https://github.com/Gallopsled/pwntools/pull/2704
|
||||
[2762]: https://github.com/Gallopsled/pwntools/pull/2762
|
||||
[2767]: https://github.com/Gallopsled/pwntools/pull/2767
|
||||
[2739]: https://github.com/Gallopsled/pwntools/pull/2739
|
||||
[2792]: https://github.com/Gallopsled/pwntools/pull/2792
|
||||
|
||||
## 4.15.0 (`stable`)
|
||||
|
||||
- [#2508][2508] Ignore a warning when compiling with asm on nix
|
||||
- [#2471][2471] Properly close spawned kitty window
|
||||
@@ -122,6 +192,19 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
- [#2502][2502] Fix loading ELF files without valid .dynamic section
|
||||
- [#2476][2476] Deprecate 'keepends' argument in favor of 'drop' in `tube.recvline*`
|
||||
- [#2364][2364] Deprecate direct commandline scripts invocation and exclude nonsense ones
|
||||
- [#2570][2570] Fix `pwn template` to skip Docker library extraction for statically linked binaries
|
||||
- [#2545][2545] SSH: fix download/upload with -1 exit status
|
||||
- [#2567][2567] Fix mistakenly parsing of ld-linux error messages.
|
||||
- [#2576][2576] regsort: respect register aliases
|
||||
- [#2595][2595] libcdb: Add Ubuntu and Debian debuginfod servers to default list
|
||||
- [#2593][2593] Use unicorn on macOS w/ SIP enabled
|
||||
- [#2587][2587] Support longer function names in Windows `getexport` shellcode
|
||||
- [#2596][2596] Ignore `colored_traceback` error when TERM envvar is unset
|
||||
- [#2579][2579] Fix poll error in `process.libs()` and clean up maps parsing
|
||||
- [#2602][2602] Allow setting debugger path via context.gdb_binary
|
||||
- [#2609][2609] Fix log level of child remotes of `server` tube
|
||||
- [#2612][2612] Fix lookup of binutils for `mipsel` architecture
|
||||
- [#2624][2624] Fix regression: gdbserver can't handle command-line argument containing whitespace
|
||||
|
||||
[2508]: https://github.com/Gallopsled/pwntools/pull/2508
|
||||
[2471]: https://github.com/Gallopsled/pwntools/pull/2471
|
||||
@@ -140,8 +223,21 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
[2502]: https://github.com/Gallopsled/pwntools/pull/2502
|
||||
[2476]: https://github.com/Gallopsled/pwntools/pull/2476
|
||||
[2364]: https://github.com/Gallopsled/pwntools/pull/2364
|
||||
[2570]: https://github.com/Gallopsled/pwntools/pull/2570
|
||||
[2545]: https://github.com/Gallopsled/pwntools/pull/2545
|
||||
[2567]: https://github.com/Gallopsled/pwntools/pull/2567
|
||||
[2576]: https://github.com/Gallopsled/pwntools/pull/2576
|
||||
[2595]: https://github.com/Gallopsled/pwntools/pull/2595
|
||||
[2593]: https://github.com/Gallopsled/pwntools/pull/2593
|
||||
[2587]: https://github.com/Gallopsled/pwntools/pull/2587
|
||||
[2596]: https://github.com/Gallopsled/pwntools/pull/2596
|
||||
[2579]: https://github.com/Gallopsled/pwntools/pull/2579
|
||||
[2602]: https://github.com/Gallopsled/pwntools/pull/2602
|
||||
[2609]: https://github.com/Gallopsled/pwntools/pull/2609
|
||||
[2612]: https://github.com/Gallopsled/pwntools/pull/2612
|
||||
[2624]: https://github.com/Gallopsled/pwntools/pull/2624
|
||||
|
||||
## 4.14.1 (`stable`)
|
||||
## 4.14.1
|
||||
|
||||
- [#2451][2451] Show symbols defined to value 0 (start of file)
|
||||
- [#2533][2533] Fix installation on Python 3.5 and lower
|
||||
@@ -592,7 +688,7 @@ In memoriam — [Zach Riggle][zach] — long time contributor and maintainer of
|
||||
[fecf9f]: http://github.com/Gallopsled/pwntools/commit/fecf9f
|
||||
[1454]: https://github.com/Gallopsled/pwntools/pull/1454
|
||||
|
||||
## 4.1.7 (`stable`)
|
||||
## 4.1.7
|
||||
|
||||
- [#1615][1615] Fix aarch64 pushstr and pushstr_array
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ intervaltree
|
||||
isort
|
||||
mako>=1.0.0
|
||||
paramiko>=1.15.2
|
||||
pip>=6.0.8
|
||||
pyelftools>=0.29, <0.30; python_version<'3'
|
||||
pyelftools>=0.29; python_version>='3'
|
||||
pygments>=2.0
|
||||
@@ -19,6 +18,6 @@ psutil
|
||||
requests>=2.5.1
|
||||
ropgadget>=5.3
|
||||
sphinx==1.8.6; python_version<'3'
|
||||
sphinx>=7.0.0; python_version>='3'
|
||||
sphinx>=8.1.3, <9; python_version>='3'
|
||||
sphinx_rtd_theme
|
||||
sphinxcontrib-autoprogram<=0.1.5
|
||||
@@ -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 ...'
|
||||
|
||||
@@ -32,8 +32,10 @@ RUN apt-get update \
|
||||
binutils-aarch64-linux-gnu \
|
||||
binutils-mips-linux-gnu \
|
||||
binutils-mipsel-linux-gnu \
|
||||
binutils-msp430 \
|
||||
binutils-powerpc-linux-gnu \
|
||||
binutils-powerpc64-linux-gnu \
|
||||
binutils-s390x-linux-gnu \
|
||||
binutils-sparc64-linux-gnu \
|
||||
binutils-riscv64-linux-gnu \
|
||||
tmux \
|
||||
|
||||
+9
-1
@@ -89,7 +89,15 @@ try:
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
colored_traceback.add_hook()
|
||||
try:
|
||||
colored_traceback.add_hook()
|
||||
except Exception:
|
||||
# Exception: curses.error
|
||||
# colored_traceback (curses.setupterm()) fails if TERM is unset.
|
||||
# This is not critical, so we just ignore it.
|
||||
# We cannot import `curses` for `curses.error` because it is not
|
||||
# available on all platforms (e.g. Windows).
|
||||
pass
|
||||
|
||||
# Equivalence with the default behavior of "from import *"
|
||||
# __all__ = [x for x in tuple(globals()) if not x.startswith('_')]
|
||||
+1
-1
@@ -142,7 +142,7 @@ class SigreturnABI(SyscallABI):
|
||||
linux_i386 = ABI('esp', [], 4, 0)
|
||||
linux_amd64 = ABI('rsp', ['rdi','rsi','rdx','rcx','r8','r9'], 8, 0)
|
||||
linux_arm = ABI('sp', ['r0', 'r1', 'r2', 'r3'], 8, 0)
|
||||
linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3'], 16, 0)
|
||||
linux_aarch64 = ABI('sp', ['x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7'], 16, 0)
|
||||
linux_mips = ABI('$sp', ['$a0','$a1','$a2','$a3'], 4, 0)
|
||||
linux_ppc = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 4, 0)
|
||||
linux_ppc64 = ABI('sp', ['r3', 'r4', 'r5', 'r6', 'r7', 'r8', 'r9', 'r10'], 8, 0)
|
||||
|
||||
+21
-1
@@ -1275,8 +1275,28 @@ properties = Property()
|
||||
|
||||
def _build_date():
|
||||
"""Returns the build date in the form YYYY-MM-DD as a string"""
|
||||
import datetime
|
||||
|
||||
# Use ro.build.date.utc (integer epoch seconds) which is set by the
|
||||
# AOSP build system and available on all standard Android devices.
|
||||
# This avoids ro.build.date which is locale-dependent and can contain
|
||||
# non-ASCII characters that dateutil cannot parse. See #2513.
|
||||
utc = getprop('ro.build.date.utc')
|
||||
if utc and utc.strip().isdigit():
|
||||
try:
|
||||
as_datetime = datetime.datetime.fromtimestamp(int(utc.strip()), dateutil.tz.UTC)
|
||||
return as_datetime.strftime('%Y-%b-%d')
|
||||
except (OSError, OverflowError, ValueError):
|
||||
pass
|
||||
|
||||
# Fallback for non-standard builds missing ro.build.date.utc.
|
||||
as_string = getprop('ro.build.date')
|
||||
as_datetime = dateutil.parser.parse(as_string)
|
||||
if not as_string:
|
||||
return ''
|
||||
try:
|
||||
as_datetime = dateutil.parser.parse(as_string)
|
||||
except (ValueError, OverflowError):
|
||||
return as_string
|
||||
return as_datetime.strftime('%Y-%b-%d')
|
||||
|
||||
def find_ndk_project_root(source):
|
||||
|
||||
+30
-10
@@ -170,6 +170,8 @@ def which_binutils(util, check_version=False):
|
||||
'.../bin/arm-...-as'
|
||||
>>> which_binutils('as', arch='powerpc') #doctest: +ELLIPSIS
|
||||
'.../bin/powerpc...-as'
|
||||
>>> which_binutils('as', arch='mips', endianness='little') #doctest: +ELLIPSIS
|
||||
'.../bin/mipsel...-as'
|
||||
>>> which_binutils('as', arch='msp430') #doctest: +SKIP
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
@@ -178,28 +180,44 @@ def which_binutils(util, check_version=False):
|
||||
"""
|
||||
arch = context.arch
|
||||
|
||||
# Handle endianness-specific naming for mips/mips64
|
||||
arch = {
|
||||
('mips', 'little'): 'mipsel',
|
||||
('mips64', 'little'): 'mips64el',
|
||||
}.get((arch, context.endianness), arch)
|
||||
|
||||
# Fix up pwntools vs Debian triplet naming, and account
|
||||
# for 'thumb' being its own pwntools architecture.
|
||||
arches = [arch] + {
|
||||
aliases = {
|
||||
'thumb': ['arm', 'aarch64'],
|
||||
'i386': ['x86_64', 'amd64'],
|
||||
'i686': ['x86_64', 'amd64'],
|
||||
'amd64': ['x86_64', 'i386'],
|
||||
'mips64': ['mips'],
|
||||
'powerpc64': ['powerpc'],
|
||||
'sparc64': ['sparc'],
|
||||
'riscv32': ['riscv32', 'riscv64', 'riscv'],
|
||||
'riscv64': ['riscv64', 'riscv32', 'riscv'],
|
||||
'amd64': ['x86_64'],
|
||||
}.get(arch, [])
|
||||
|
||||
# Some binutils can support multiple architectures. Try them as fallbacks.
|
||||
fallback_arches = {
|
||||
'i386': ['x86_64', 'amd64'],
|
||||
'amd64': ['i386'],
|
||||
'arm': ['aarch64'],
|
||||
'mips': ['mipsel'],
|
||||
'mipsel': ['mips'],
|
||||
'mips64': ['mips64el', 'mips', 'mipsel'],
|
||||
'mips64el': ['mips64', 'mipsel', 'mips'],
|
||||
'powerpc64': ['powerpc'],
|
||||
'sparc64': ['sparc'],
|
||||
'riscv32': ['riscv64', 'riscv'],
|
||||
'riscv64': ['riscv32', 'riscv'],
|
||||
}.get(arch, [])
|
||||
arches = [arch] + aliases + fallback_arches
|
||||
|
||||
# If one of the candidate architectures matches the native
|
||||
# architecture, use that as a last resort.
|
||||
# architecture, use that as a last resort before trying fallbacks.
|
||||
machine = platform.machine()
|
||||
machine = 'i386' if machine == 'i686' else machine
|
||||
try:
|
||||
with context.local(arch = machine):
|
||||
if context.arch in arches:
|
||||
arches.append(None)
|
||||
arches = [arch] + aliases + [None] + fallback_arches
|
||||
except AttributeError:
|
||||
log.warn_once("Your local binutils won't be used because architecture %r is not supported." % machine)
|
||||
|
||||
@@ -212,6 +230,8 @@ def which_binutils(util, check_version=False):
|
||||
if platform.system() == 'Windows':
|
||||
utils = [util + '.exe']
|
||||
|
||||
# Try the explicit tools for the target architecture first,
|
||||
# then the native one optionally, then fallbacks.
|
||||
for arch in arches:
|
||||
for gutil in utils:
|
||||
# e.g. objdump
|
||||
|
||||
@@ -15,7 +15,6 @@ parser = common.parser_commands.add_parser(
|
||||
parser.add_argument(
|
||||
'elf',
|
||||
nargs='*',
|
||||
type=argparse.FileType('rb'),
|
||||
help='Files to check'
|
||||
)
|
||||
parser.add_argument(
|
||||
@@ -23,12 +22,11 @@ parser.add_argument(
|
||||
nargs='*',
|
||||
dest='elf2',
|
||||
metavar='elf',
|
||||
type=argparse.FileType('rb'),
|
||||
help='File to check (for compatibility with checksec.sh)'
|
||||
)
|
||||
|
||||
def main(args):
|
||||
files = args.elf or args.elf2 or []
|
||||
files = args.elf or args.elf2 or []
|
||||
|
||||
if not files:
|
||||
parser.print_usage()
|
||||
@@ -36,9 +34,9 @@ def main(args):
|
||||
|
||||
for f in files:
|
||||
try:
|
||||
e = ELF(f.name)
|
||||
e = ELF(f)
|
||||
except Exception as e:
|
||||
print("{name}: {error}".format(name=f.name, error=e))
|
||||
print("{name}: {error}".format(name=f, error=e))
|
||||
|
||||
if __name__ == '__main__':
|
||||
common.main(__file__, main)
|
||||
@@ -240,6 +240,9 @@ def main(args):
|
||||
symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
|
||||
matched_libcs = libcdb.search_by_symbol_offsets(symbols, offline_only=args.offline_only, return_raw=True)
|
||||
|
||||
if not matched_libcs:
|
||||
return
|
||||
|
||||
for libc in matched_libcs:
|
||||
print_libc_info(libc)
|
||||
if args.download_libc:
|
||||
|
||||
@@ -89,7 +89,7 @@ def get_docker_image_libraries():
|
||||
if not (libc and ld):
|
||||
progress.failure("Could not find libraries")
|
||||
return None, None
|
||||
|
||||
|
||||
progress.status("Copying libraries to current directory")
|
||||
for filename, basename in zip((libc, ld), (libc_basename, ld_basename)):
|
||||
cat_command = ["-c", "chroot %s /bin/sh -c '/bin/cat %s'" % (chroot_dir, filename)]
|
||||
@@ -108,13 +108,26 @@ def get_docker_image_libraries():
|
||||
write(basename, contents)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.stderr.decode())
|
||||
log.error("docker failed with status: %d" % e.returncode)
|
||||
print(e.stderr.decode(), file=sys.stderr)
|
||||
progress.failure("docker failed with status: %d" % e.returncode)
|
||||
return None, None
|
||||
|
||||
progress.success("Retrieved libraries from Docker image")
|
||||
return libc_basename, ld_basename
|
||||
|
||||
def detect_missing_binaries(args):
|
||||
"""Automatically detects challenge binaries and libraries in the current directory.
|
||||
|
||||
This function scans the current directory for executable files, libc, and ld libraries.
|
||||
If a Dockerfile is present and no libraries are found, it attempts to extract them from
|
||||
the Docker image, but only if the binary is not statically linked.
|
||||
|
||||
Args:
|
||||
args: Argument namespace containing exe and libc attributes.
|
||||
|
||||
Returns:
|
||||
tuple: A pair of (executable_path, libc_path) where either may be None if not found.
|
||||
"""
|
||||
log.info("Automatically detecting challenge binaries...")
|
||||
# look for challenge binary, libc, and ld in current directory
|
||||
exe, libc, ld = args.exe, args.libc, None
|
||||
@@ -138,7 +151,19 @@ def detect_missing_binaries(args):
|
||||
elif len(other_files) > 1:
|
||||
log.warning("Failed to find challenge binary. There are multiple binaries in the current directory: %s", other_files)
|
||||
|
||||
if has_dockerfile and exe and not (libc or ld):
|
||||
# Check if the binary is statically linked before trying to extract libraries from Docker
|
||||
is_statically_linked = False
|
||||
if exe:
|
||||
try:
|
||||
binary = ELF(exe, checksec=False)
|
||||
is_statically_linked = binary.statically_linked
|
||||
if is_statically_linked:
|
||||
log.info("Binary is statically linked, no need for external libraries")
|
||||
except Exception as e:
|
||||
log.warning("Could not check if binary is statically linked: %s", e)
|
||||
|
||||
# Only extract libraries from Docker if the binary is not statically linked
|
||||
if has_dockerfile and exe and not (libc or ld) and not is_statically_linked:
|
||||
libc, ld = get_docker_image_libraries()
|
||||
|
||||
if exe != args.exe:
|
||||
|
||||
@@ -359,6 +359,7 @@ class ContextType(object):
|
||||
'encoding': 'auto',
|
||||
'endian': 'little',
|
||||
'gdbinit': "",
|
||||
'gdb_binary': "",
|
||||
'kernel': None,
|
||||
'local_libcdb': "/var/lib/libc-database",
|
||||
'log_level': logging.INFO,
|
||||
@@ -1539,6 +1540,20 @@ class ContextType(object):
|
||||
"""
|
||||
return str(value)
|
||||
|
||||
@_validator
|
||||
def gdb_binary(self, value):
|
||||
"""Path to the binary that is used when running GDB locally.
|
||||
|
||||
This is useful when you have multiple versions of gdb installed or the gdb binary is
|
||||
called something different.
|
||||
|
||||
If set to an empty string, pwntools will try to search for a reasonable gdb binary from
|
||||
the path.
|
||||
|
||||
Default value is ``""``.
|
||||
"""
|
||||
return str(value)
|
||||
|
||||
@_validator
|
||||
def cyclic_alphabet(self, alphabet):
|
||||
"""Cyclic alphabet.
|
||||
@@ -1733,7 +1748,7 @@ def update_context_defaults(section):
|
||||
|
||||
default = ContextType.defaults[key]
|
||||
|
||||
if isinstance(default, six.string_types + six.integer_types + (tuple, list, dict)):
|
||||
if isinstance(default, six.string_types + six.integer_types + (float, tuple, list, dict)):
|
||||
value = safeeval.expr(value)
|
||||
else:
|
||||
log.warn("Unsupported configuration option %r in section %r" % (key, 'context'))
|
||||
|
||||
+117
-78
@@ -715,29 +715,56 @@ class ELF(ELFFile):
|
||||
|
||||
@property
|
||||
def libs(self):
|
||||
"""Dictionary of ``{path: address}`` for every library loaded for this ELF."""
|
||||
"""Dictionary of ``{path: address}`` for every library loaded for this ELF.
|
||||
|
||||
.. warning::
|
||||
|
||||
Getting this attribute actually runs the executable.
|
||||
Make sure that you trust the binary you are exploiting.
|
||||
If it adds itself as ``DT_NEEDED``, has overlapping segments,
|
||||
ambiguous headers, or employs text relocations, it can run arbitrary
|
||||
code even though you are just inspecting it.
|
||||
Running is the only reliable way to ensure all the libraries are
|
||||
loaded from the correct paths, because some of them may change
|
||||
loading logic.
|
||||
|
||||
Exploitability first noticed at CWTE CTF 2025.
|
||||
"""
|
||||
if self._libs is None:
|
||||
self._populate_libraries()
|
||||
return self._libs
|
||||
|
||||
@property
|
||||
def maps(self):
|
||||
"""Dictionary of ``{name: address}`` for every mapping in this ELF's address space."""
|
||||
"""Dictionary of ``{name: address}`` for every mapping in this ELF's address space.
|
||||
|
||||
.. warning::
|
||||
|
||||
Getting this attribute actually runs the executable.
|
||||
Make sure that you trust the binary you are exploiting
|
||||
(see :attr:`.ELF.libs`).
|
||||
"""
|
||||
if self._maps is None:
|
||||
self._populate_libraries()
|
||||
return self._maps
|
||||
|
||||
@property
|
||||
def libc(self):
|
||||
""":class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``'libc[.-]``,
|
||||
""":class:`.ELF`: If this :class:`.ELF` imports any libraries which contain ``/libc[.-]``,
|
||||
and we can determine the appropriate path to it on the local
|
||||
system, returns a new :class:`.ELF` object pertaining to that library.
|
||||
Prints the `checksec` output of the library if it was printed for the original ELF too.
|
||||
|
||||
If not found, the value will be :const:`None`.
|
||||
|
||||
.. warning::
|
||||
|
||||
Getting this attribute actually runs the executable.
|
||||
Make sure that you trust the binary you are exploiting
|
||||
(see :attr:`.ELF.libs`).
|
||||
"""
|
||||
for lib in self.libs:
|
||||
if '/libc.' in lib or '/libc-' in lib:
|
||||
if ('/libc.' in lib or '/libc-' in lib) and lib != self.path:
|
||||
return ELF(lib, self._print_checksec)
|
||||
|
||||
def _populate_libraries(self):
|
||||
@@ -852,6 +879,13 @@ class ELF(ELFFile):
|
||||
log.warn_once("Injected /proc/self/maps code did not execute correctly")
|
||||
return {}
|
||||
|
||||
# Sometimes the original binary already fail to run, for example, in case glibc mismatches.
|
||||
try:
|
||||
int(data.split('-', 1)[0], 16)
|
||||
except ValueError:
|
||||
log.warn_once("Cannot execute `%s` to get /proc/self/maps", self.path)
|
||||
return {}
|
||||
|
||||
# Swap in the original ELF name
|
||||
data = data.replace(path, self.path)
|
||||
|
||||
@@ -1790,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
|
||||
@@ -2084,7 +2123,7 @@ class ELF(ELFFile):
|
||||
"NX:".ljust(12) + {
|
||||
True: green("NX enabled"),
|
||||
False: red("NX disabled"),
|
||||
None: yellow("NX unknown - GNU_STACK missing"),
|
||||
None: yellow("NX enabled on new kernels"),
|
||||
}[self.nx],
|
||||
"PIE:".ljust(12) + {
|
||||
True: green("PIE enabled"),
|
||||
|
||||
+8
-1
@@ -65,7 +65,7 @@ def __ensure_memory_to_run_unicorn():
|
||||
from mmap import mmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE
|
||||
|
||||
mm = mmap(
|
||||
-1, 1024 * 1024 * 1024, MAP_PRIVATE | MAP_ANON, PROT_WRITE | PROT_READ | PROT_EXEC
|
||||
-1, 1024 * 1024 * 1024, MAP_PRIVATE | MAP_ANON, PROT_WRITE | PROT_READ
|
||||
)
|
||||
mm.close()
|
||||
except OSError:
|
||||
@@ -78,6 +78,13 @@ def __ensure_memory_to_run_unicorn():
|
||||
def prepare_unicorn_and_context(elf, got, address, data):
|
||||
import unicorn as U
|
||||
|
||||
if elf.arch in ('mips', 'mips64'):
|
||||
# Unicorn 2.1.3 and 2.1.4 have a crashing bug when trying to emulate MIPS.
|
||||
# https://github.com/unicorn-engine/unicorn/issues/2134
|
||||
if U.UC_VERSION_MAJOR == 2 and U.UC_VERSION_MINOR == 1 and U.UC_VERSION_PATCH in (3, 4):
|
||||
raise OSError("Unicorn Engine 2.1.3 and 2.1.4 have a crashing bug when emulating MIPS, please install an earlier or later version: https://github.com/unicorn-engine/unicorn/issues/2134")
|
||||
|
||||
|
||||
__ensure_memory_to_run_unicorn()
|
||||
|
||||
# Instantiate the emulator with the correct arguments for the current
|
||||
|
||||
+14
-2
@@ -156,6 +156,18 @@ class FileStructure(object):
|
||||
_wide_data: 0xdeadbeef
|
||||
unknown2: 0x0
|
||||
vtable: 0x0}
|
||||
|
||||
Bytes fields are padded to the correct field size.
|
||||
For example, unknown2 is 56 bytes on i386, so a short value gets
|
||||
zero-padded to 56, not to context.bytes (4):
|
||||
|
||||
>>> context.clear(arch='i386')
|
||||
>>> fileStr2 = FileStructure(null=0)
|
||||
>>> fileStr2.vtable = 0x561859f0
|
||||
>>> old_len = len(bytes(fileStr2))
|
||||
>>> fileStr2.unknown2 = b'AB'
|
||||
>>> len(bytes(fileStr2)) == old_len
|
||||
True
|
||||
"""
|
||||
|
||||
vars_=[]
|
||||
@@ -186,7 +198,7 @@ class FileStructure(object):
|
||||
structure = b''
|
||||
for val in self.vars_:
|
||||
if isinstance(getattr(self, val), bytes):
|
||||
structure += getattr(self, val).ljust(context.bytes, b'\x00')
|
||||
structure += getattr(self, val).ljust(self.length[val], b'\x00')
|
||||
else:
|
||||
if self.length[val] > 0:
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
@@ -215,7 +227,7 @@ class FileStructure(object):
|
||||
structure = b''
|
||||
for val in self.vars_:
|
||||
if isinstance(getattr(self, val), bytes):
|
||||
structure += getattr(self, val).ljust(context.bytes, b'\x00')
|
||||
structure += getattr(self, val).ljust(self.length[val], b'\x00')
|
||||
else:
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
if val == v:
|
||||
|
||||
+30
-24
@@ -143,7 +143,6 @@ from __future__ import division
|
||||
|
||||
import os
|
||||
import platform
|
||||
import psutil
|
||||
import random
|
||||
import re
|
||||
import six
|
||||
@@ -353,10 +352,6 @@ def _gdbserver_args(pid=None, path=None, port=0, gdbserver_args=None, args=None,
|
||||
gdbserver_args += ['--wrapper', python_wrapper_script, '--']
|
||||
elif env is not None:
|
||||
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
|
||||
# --no-startup-with-shell is required for forking shells like SHELL=/bin/fish
|
||||
# https://github.com/Gallopsled/pwntools/issues/2377
|
||||
else:
|
||||
gdbserver_args += ['--no-startup-with-shell']
|
||||
|
||||
gdbserver_args += ['localhost:%d' % port]
|
||||
gdbserver_args += args
|
||||
@@ -750,6 +745,12 @@ def binary():
|
||||
>>> gdb.binary() # doctest: +SKIP
|
||||
'/usr/bin/gdb'
|
||||
"""
|
||||
if context.gdb_binary:
|
||||
gdb = misc.which(context.gdb_binary)
|
||||
if not gdb:
|
||||
log.warn_once('Path to gdb binary `{}` not found'.format(context.gdb_binary))
|
||||
return gdb
|
||||
|
||||
gdb = misc.which('pwntools-gdb') or misc.which('gdb')
|
||||
|
||||
if not context.native:
|
||||
@@ -1520,28 +1521,33 @@ def corefile(process):
|
||||
'contain all of the data in the process.\n'
|
||||
'Upgrade to GDB >= 7.11 for better core-dumps.' % binary())
|
||||
|
||||
# This is effectively the same as what the 'gcore' binary does
|
||||
gdb_args = ['-batch',
|
||||
'-q',
|
||||
'-nx',
|
||||
'-ex', 'set pagination off',
|
||||
'-ex', 'set height 0',
|
||||
'-ex', 'set width 0',
|
||||
'-ex', 'set use-coredump-filter on',
|
||||
'-ex', 'generate-core-file %s' % corefile_path,
|
||||
'-ex', 'detach']
|
||||
# This is effectively the same as what the 'gcore' binary does.
|
||||
# Run GDB directly instead of going through attach(), which has to
|
||||
# race GDB to observe the tracer before it already detached again.
|
||||
cmd = [binary(),
|
||||
'-batch',
|
||||
'-q',
|
||||
'-nx',
|
||||
'-ex', 'set pagination off',
|
||||
'-ex', 'set height 0',
|
||||
'-ex', 'set width 0',
|
||||
'-ex', 'set use-coredump-filter on',
|
||||
'-ex', 'attach %d' % process.pid,
|
||||
'-ex', 'generate-core-file %s' % corefile_path,
|
||||
'-ex', 'detach']
|
||||
|
||||
with context.local(terminal = ['sh', '-c']):
|
||||
with context.quiet:
|
||||
pid = attach(process, gdb_args=gdb_args)
|
||||
log.debug("Got GDB pid %d", pid)
|
||||
try:
|
||||
psutil.Process(pid).wait()
|
||||
except psutil.Error:
|
||||
pass
|
||||
with context.quiet:
|
||||
gdb = tubes.process.process(cmd, stdout=tubes.process.PIPE)
|
||||
gdb.wait_for_close(timeout=60)
|
||||
timed_out = gdb.poll() is None
|
||||
output = gdb.recvallS(timeout=0)
|
||||
|
||||
if timed_out:
|
||||
log.error("GDB did not finish dumping process %d within 60 seconds", process.pid)
|
||||
|
||||
if not os.path.exists(corefile_path):
|
||||
log.error("Could not generate a corefile for process %d", process.pid)
|
||||
log.error("Could not generate a corefile for process %d (maybe check /proc/sys/kernel/yama/ptrace_scope)\n%s",
|
||||
process.pid, output)
|
||||
|
||||
return elf.corefile.Core(corefile_path)
|
||||
|
||||
|
||||
+69
-65
@@ -66,6 +66,8 @@ MAP_TYPES = {
|
||||
}
|
||||
|
||||
DEBUGINFOD_SERVERS = [
|
||||
'https://debuginfod.ubuntu.com/',
|
||||
'https://debuginfod.debian.net/',
|
||||
'https://debuginfod.elfutils.org/',
|
||||
]
|
||||
|
||||
@@ -117,16 +119,32 @@ def query_libc_rip(params):
|
||||
import requests
|
||||
|
||||
url = "{}/api/find".format(LIBC_RIP_URL)
|
||||
try:
|
||||
result = requests.post(url, json=params, timeout=20)
|
||||
result.raise_for_status()
|
||||
if result.status_code != 200:
|
||||
log.debug("Error: %s", result.text)
|
||||
# Handle pagination transparently and fetch all results
|
||||
offset = 0
|
||||
count = 1
|
||||
results = []
|
||||
while offset < count:
|
||||
try:
|
||||
pagination_params = {'offset': offset, 'limit': 100}
|
||||
log.debug("Querying libc.rip at %s (pagination %s) with parameters: %s", url, pagination_params, params)
|
||||
result = requests.post(url, params=pagination_params, json=params, timeout=20)
|
||||
result.raise_for_status()
|
||||
if result.status_code != 200:
|
||||
log.debug("Error: %s", result.text)
|
||||
return None
|
||||
imm_results = result.json()
|
||||
# non-paginated result. old libc-database API?
|
||||
if not isinstance(imm_results, dict):
|
||||
return imm_results
|
||||
results.extend(imm_results.get('results', []))
|
||||
count = imm_results.get('total', 0)
|
||||
offset += imm_results['count']
|
||||
if not imm_results['has_more']:
|
||||
break
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc info from libc.rip: %s", e)
|
||||
return None
|
||||
return result.json()
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc info from libc.rip: %s", e)
|
||||
return None
|
||||
return results
|
||||
|
||||
# https://libc.rip/
|
||||
def provider_libc_rip(search_target, search_type):
|
||||
@@ -294,49 +312,6 @@ def search_by_hash(search_target, search_type='build_id', unstrip=True, offline_
|
||||
|
||||
return cache
|
||||
|
||||
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
|
||||
"""
|
||||
Given a hex-encoded build_id, attempt to download a matching debuginfo from the debuginfod server.
|
||||
|
||||
>>> debuginfo_file = _search_debuginfo_by_hash(DEBUGINFOD_SERVERS[0], 'd1704d25fbbb72fa95d517b883131828c0883fe9')
|
||||
>>> debuginfo_file is not None
|
||||
True
|
||||
>>> 'main_arena' in ELF(debuginfo_file).symbols
|
||||
True
|
||||
"""
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
from six.moves import urllib
|
||||
|
||||
# Check if we tried this buildid before.
|
||||
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
|
||||
if cache_valid:
|
||||
return cache
|
||||
|
||||
# We searched for this buildid before, but didn't find anything.
|
||||
if cache is None:
|
||||
return None
|
||||
|
||||
# Try to find separate debuginfo.
|
||||
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
|
||||
url = urllib.parse.urljoin(base_url, url)
|
||||
data = b""
|
||||
log.debug("Downloading data from debuginfod: %s", url)
|
||||
try:
|
||||
data = wget(url, timeout=20)
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, base_url, e)
|
||||
|
||||
# Save whatever we got to the cache
|
||||
write(cache, data or b'')
|
||||
|
||||
# Return ``None`` if we did not get a valid ELF file
|
||||
if not data or not data.startswith(b'\x7FELF'):
|
||||
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, base_url)
|
||||
return None
|
||||
|
||||
return cache
|
||||
|
||||
def _check_elf_cache(cache_type, search_target, search_type):
|
||||
"""
|
||||
Check if there already is an ELF file for this hash in the cache.
|
||||
@@ -395,15 +370,15 @@ def unstrip_libc(filename):
|
||||
>>> libc = ELF(filename)
|
||||
>>> 'main_arena' in libc.symbols
|
||||
False
|
||||
>>> unstrip_libc(filename)
|
||||
>>> unstrip_libc(filename) # doctest: +SKIP
|
||||
True
|
||||
>>> libc = ELF(filename)
|
||||
>>> hex(libc.symbols.main_arena)
|
||||
>>> hex(libc.symbols.main_arena) # doctest: +SKIP
|
||||
'0x219c80'
|
||||
>>> unstrip_libc(pwnlib.data.elf.get('test-x86'))
|
||||
False
|
||||
>>> filename = search_by_build_id('d1704d25fbbb72fa95d517b883131828c0883fe9', unstrip=True)
|
||||
>>> 'main_arena' in ELF(filename).symbols
|
||||
>>> 'main_arena' in ELF(filename).symbols # doctest: +SKIP
|
||||
True
|
||||
"""
|
||||
if not which('eu-unstrip'):
|
||||
@@ -421,16 +396,45 @@ def unstrip_libc(filename):
|
||||
|
||||
log.debug('Trying debuginfod servers: %r', DEBUGINFOD_SERVERS)
|
||||
|
||||
for server_url in DEBUGINFOD_SERVERS:
|
||||
libc_dbg = _search_debuginfo_by_hash(server_url, enhex(libc.buildid))
|
||||
if libc_dbg:
|
||||
break
|
||||
else:
|
||||
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
|
||||
return False
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
from six.moves import urllib
|
||||
|
||||
hex_encoded_id = enhex(libc.buildid)
|
||||
|
||||
# Check if we tried this buildid before.
|
||||
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
|
||||
if not cache_valid:
|
||||
# Cached negative result so we don't look for missing debug info everytime.
|
||||
if cache is None:
|
||||
return False
|
||||
else:
|
||||
for server_url in DEBUGINFOD_SERVERS:
|
||||
# Try to find separate debuginfo.
|
||||
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
|
||||
url = urllib.parse.urljoin(server_url, url)
|
||||
data = b""
|
||||
log.debug("Downloading data from debuginfod: %s", url)
|
||||
try:
|
||||
data = wget(url, timeout=20)
|
||||
|
||||
# Try next server if we didn't get a valid ELF file
|
||||
if not data or not data.startswith(b'\x7FELF'):
|
||||
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, server_url)
|
||||
continue
|
||||
break
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, server_url, e)
|
||||
else:
|
||||
write(cache, data or b'')
|
||||
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
|
||||
return False
|
||||
|
||||
# Save whatever we got to the cache
|
||||
write(cache, data or b'')
|
||||
|
||||
# Add debug info to given libc binary inplace.
|
||||
p = process(['eu-unstrip', '-o', filename, filename, libc_dbg])
|
||||
p = process(['eu-unstrip', '-o', filename, filename, cache])
|
||||
output = p.recvall()
|
||||
p.close()
|
||||
|
||||
@@ -709,8 +713,8 @@ def search_by_symbol_offsets(symbols, select_index=None, unstrip=True, offline_o
|
||||
>>> matched_libcs = search_by_symbol_offsets({'__libc_start_main_ret': '7f89ad926550'}, return_as_list=True)
|
||||
>>> len(matched_libcs) > 1
|
||||
True
|
||||
>>> for buildid in matched_libcs: # doctest +SKIP
|
||||
... libc = ELF(search_by_build_id(buildid)) # doctest +SKIP
|
||||
>>> for buildid in matched_libcs: # doctest: +SKIP
|
||||
... libc = ELF(search_by_build_id(buildid)) # doctest: +SKIP
|
||||
"""
|
||||
assert search_type in TYPES, search_type
|
||||
|
||||
|
||||
+2
-1
@@ -546,7 +546,8 @@ class Handler(logging.StreamHandler):
|
||||
|
||||
# if the record originates from a `Progress` object and term handling
|
||||
# is enabled we can have animated spinners! so check that
|
||||
if progress is None or not term.term_mode:
|
||||
# Don't show spinners if the output is being redirected
|
||||
if progress is None or not term.term_mode or not os.path.sameopenfile(context.log_console.fileno(), term.term.fd.fileno()):
|
||||
super(Handler, self).emit(record)
|
||||
return
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ def archname():
|
||||
('powerpc64', 'little'): 'ppc64le',
|
||||
('thumb', 'little'): 'arm',
|
||||
('thumb', 'big'): 'armeb',
|
||||
('aarch64', 'big'): 'aarch64_be',
|
||||
}.get((context.arch, context.endian), context.arch)
|
||||
|
||||
@LocalContext
|
||||
|
||||
+51
-16
@@ -15,7 +15,7 @@ from pwnlib.log import getLogger
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
def check_cycle(reg, assignments):
|
||||
def check_cycle(reg, assignments, mapping=None):
|
||||
"""Walk down the assignment list of a register,
|
||||
return the path walked if it is encountered again.
|
||||
|
||||
@@ -37,31 +37,38 @@ def check_cycle(reg, assignments):
|
||||
>>> check_cycle('a', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'a'})
|
||||
['a', 'b', 'c', 'd']
|
||||
"""
|
||||
return check_cycle_(reg, assignments, [])
|
||||
if mapping is None:
|
||||
mapping = {}
|
||||
return check_cycle_(reg, assignments, [], mapping)
|
||||
|
||||
def check_cycle_(reg, assignments, path):
|
||||
target = assignments[reg]
|
||||
def check_cycle_(reg, assignments, path, mapping):
|
||||
target = assignments.get(reg)
|
||||
if target is None:
|
||||
real_reg = mapping.get(reg, reg)
|
||||
reg, target = next((k, v) for k,v in assignments.items() if mapping.get(k, k) == real_reg)
|
||||
path.append(reg)
|
||||
|
||||
real_target = mapping.get(target, target)
|
||||
|
||||
# No cycle, some other value (e.g. 1)
|
||||
if target not in assignments:
|
||||
if all(real_target != mapping.get(k, k) for k in assignments):
|
||||
return []
|
||||
|
||||
# Found a cycle
|
||||
if target in path:
|
||||
if any(real_target == mapping.get(k, k) for k in path):
|
||||
# Does the cycle *start* with target?
|
||||
# This determines whether the original register is
|
||||
# in the cycle, or just depends on registers in one.
|
||||
if target == path[0]:
|
||||
if real_target == mapping.get(path[0], path[0]):
|
||||
return path
|
||||
|
||||
# Just depends on one.
|
||||
return []
|
||||
|
||||
# Recurse
|
||||
return check_cycle_(target, assignments, path)
|
||||
return check_cycle_(target, assignments, path, mapping)
|
||||
|
||||
def extract_dependencies(reg, assignments):
|
||||
def extract_dependencies(reg, assignments, mapping=None):
|
||||
"""Return a list of all registers which directly
|
||||
depend on the specified register.
|
||||
|
||||
@@ -77,7 +84,9 @@ def extract_dependencies(reg, assignments):
|
||||
['b', 'c']
|
||||
"""
|
||||
# sorted() is only for determinism
|
||||
return sorted([k for k,v in assignments.items() if v == reg])
|
||||
if mapping is None:
|
||||
mapping = {}
|
||||
return sorted([k for k,v in assignments.items() if mapping.get(v, v) == mapping.get(reg, reg)])
|
||||
|
||||
|
||||
def resolve_order(reg, deps):
|
||||
@@ -110,7 +119,7 @@ def depends_on_cycle(reg, assignments, in_cycles):
|
||||
reg = assignments.get(reg, None)
|
||||
return False
|
||||
|
||||
def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
def regsort(in_out, all_regs, mapping = None, tmp = None, xchg = True, randomize = None):
|
||||
"""
|
||||
Sorts register dependencies.
|
||||
|
||||
@@ -181,6 +190,15 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
|
||||
>>> regsort({'a': 1, 'b': 2}, R)
|
||||
[('mov', 'a', 1), ('mov', 'b', 2)]
|
||||
>>> M = {'ax': 'eax', 'eax': 'eax', 'ebx': 'ebx'}
|
||||
|
||||
Registers which are assigned one of their own aliases
|
||||
are dropped, like same-name self-assignments.
|
||||
|
||||
>>> regsort({'ax': 'eax'}, M)
|
||||
[]
|
||||
>>> regsort({'ax': 'eax'}, M, xchg=False)
|
||||
[]
|
||||
>>> regsort({'a': 'b', 'b': 'a'}, R)
|
||||
[('xchg', 'a', 'b')]
|
||||
>>> regsort({'a': 'b', 'b': 'a'}, R, tmp='X') #doctest: +NORMALIZE_WHITESPACE
|
||||
@@ -233,16 +251,24 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
if randomize is None:
|
||||
randomize = context.randomize
|
||||
|
||||
if mapping is None:
|
||||
if hasattr(all_regs, 'keys'):
|
||||
mapping = all_regs
|
||||
else:
|
||||
mapping = {}
|
||||
|
||||
sentinel = object()
|
||||
|
||||
# Drop all registers which will be set to themselves.
|
||||
#
|
||||
# For example, {'eax': 'eax'}
|
||||
in_out = {k:v for k,v in in_out.items() if k != v}
|
||||
# This must go through the mapping, so that {'ax': 'eax'} is
|
||||
# recognized as a self-assignment as well.
|
||||
in_out = {k:v for k,v in in_out.items() if mapping.get(k, k) != mapping.get(v, v)}
|
||||
|
||||
# Collapse constant values
|
||||
#
|
||||
# For eaxmple, {'eax': 0, 'ebx': 0} => {'eax': 0, 'ebx': 'eax'}
|
||||
# For example, {'eax': 0, 'ebx': 0} => {'eax': 0, 'ebx': 'eax'}
|
||||
v_k = defaultdict(list)
|
||||
for k,v in sorted(in_out.items()):
|
||||
if v not in all_regs and v != 0:
|
||||
@@ -263,7 +289,9 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
# which are also 'outputs'.
|
||||
#
|
||||
# For example, {'eax': 1, 'ebx': 2, 'ecx': 'edx'}
|
||||
if not any(v in in_out for k,v in in_out.items()):
|
||||
inps = {mapping.get(k, k) for k in in_out}
|
||||
outs = {mapping.get(v, v) for v in in_out.values()}
|
||||
if not inps & outs:
|
||||
result = [('mov', k,in_out[k]) for k in sorted(in_out)]
|
||||
|
||||
if randomize:
|
||||
@@ -280,7 +308,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
# Output: {'A': [], 'B': ['A', 'C'], 'C': []}
|
||||
#
|
||||
# In this case, both A and C must be set before B.
|
||||
deps = {r: extract_dependencies(r, in_out) for r in in_out}
|
||||
deps = {r: extract_dependencies(r, in_out, mapping) for r in in_out}
|
||||
|
||||
# Final result which will be returned
|
||||
result = []
|
||||
@@ -299,7 +327,7 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
|
||||
while cycle_candidates:
|
||||
reg = cycle_candidates[0]
|
||||
cycle = check_cycle(reg, in_out)
|
||||
cycle = check_cycle(reg, in_out, mapping)
|
||||
|
||||
if cycle:
|
||||
if randomize:
|
||||
@@ -395,6 +423,10 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
|
||||
if tmp:
|
||||
for cycle in cycles:
|
||||
if len(cycle) == 1:
|
||||
[reg] = cycle
|
||||
result.append(('mov', reg, in_out[reg]))
|
||||
continue
|
||||
|
||||
first = cycle[0]
|
||||
last = cycle[-1]
|
||||
@@ -411,6 +443,9 @@ def regsort(in_out, all_regs, tmp = None, xchg = True, randomize = None):
|
||||
else:
|
||||
for cycle in cycles:
|
||||
size = len(cycle)
|
||||
if size == 1:
|
||||
[reg] = cycle
|
||||
result.append(('mov', reg, in_out[reg]))
|
||||
for i in range(size-1):
|
||||
result.append(('xchg', cycle[i], cycle[(i+1) % size]))
|
||||
|
||||
|
||||
+12
-6
@@ -390,6 +390,7 @@ from pwnlib.rop.call import Unresolved
|
||||
from pwnlib.rop.gadgets import Gadget
|
||||
from pwnlib.util import lists
|
||||
from pwnlib.util import packing
|
||||
from pwnlib.util import safeeval
|
||||
from pwnlib.util.cyclic import cyclic
|
||||
from pwnlib.util.packing import pack
|
||||
from pwnlib.util.misc import python_2_bytes_compatible
|
||||
@@ -1272,7 +1273,7 @@ class ROP(object):
|
||||
filename = self.__get_cachefile_name(elf)
|
||||
if filename is None or not os.path.exists(filename):
|
||||
return None
|
||||
gadgets = eval(open(filename).read())
|
||||
gadgets = safeeval.const(open(filename).read())
|
||||
gadgets = {k - elf.load_addr + elf.address:v for k, v in gadgets.items()}
|
||||
log.info_once('Loaded %s cached gadgets for %r', len(gadgets), elf.file.name)
|
||||
return gadgets
|
||||
@@ -1391,6 +1392,8 @@ class ROP(object):
|
||||
if pop.match(insn):
|
||||
regs.append(pop.match(insn).group(1))
|
||||
sp_move += context.bytes
|
||||
if 'sp' in insn:
|
||||
sp_move += 9999999
|
||||
elif add.match(insn):
|
||||
arg = int(add.match(insn).group(1), 16)
|
||||
sp_move += arg
|
||||
@@ -1418,7 +1421,7 @@ class ROP(object):
|
||||
if not set(['rsp', 'esp']) & set(regs):
|
||||
self.pivots[sp_move] = addr
|
||||
|
||||
leave = self.search(regs=frame_regs, order='regs')
|
||||
leave = self.search(regs=frame_regs, order='leav')
|
||||
if leave and leave.regs != frame_regs:
|
||||
leave = None
|
||||
self.leave = leave
|
||||
@@ -1451,7 +1454,7 @@ class ROP(object):
|
||||
pointer is adjusted.
|
||||
regs(list): Minimum list of registers which are popped off the
|
||||
stack.
|
||||
order(str): Either the string 'size' or 'regs'. Decides how to
|
||||
order(str): Either the string 'size', 'leav' or 'regs'. Decides how to
|
||||
order multiple gadgets the fulfill the requirements.
|
||||
|
||||
The search will try to minimize the number of bytes popped more than
|
||||
@@ -1459,7 +1462,9 @@ class ROP(object):
|
||||
the address.
|
||||
|
||||
If ``order == 'size'``, then gadgets are compared lexicographically
|
||||
by ``(total_moves, total_regs, addr)``, otherwise by ``(total_regs, total_moves, addr)``.
|
||||
by ``(total_moves, total_regs, addr)``, if ``order == 'regs'``,
|
||||
then by ``(total_regs, total_moves, addr)``. ``order == 'leav'``
|
||||
is specifically for ``leave`` insn.
|
||||
|
||||
Returns:
|
||||
A :class:`.Gadget` object
|
||||
@@ -1471,8 +1476,9 @@ class ROP(object):
|
||||
# Search for an exact match, save the closest match
|
||||
key = {
|
||||
'size': lambda g: (g.move, len(g.regs), g.address),
|
||||
'regs': lambda g: (len(g.regs), g.move, g.address)
|
||||
}[order]
|
||||
'regs': lambda g: (len(g.regs), g.move, g.address),
|
||||
'leav': lambda g: ('leave' not in g.insns, len(g.regs), g.address)
|
||||
}[order] # False is prior than True
|
||||
|
||||
try:
|
||||
result = min(matches, key=key)
|
||||
|
||||
+5
-5
@@ -185,7 +185,7 @@ registers = {
|
||||
80: 'r13', 88: 'r14', 96: 'r15', 104: 'rdi', 112: 'rsi', 120: 'rbp', 128: 'rbx',
|
||||
136: 'rdx', 144: 'rax', 152: 'rcx', 160: 'rsp', 168: 'rip', 176: 'eflags',
|
||||
184: 'csgsfs', 192: 'err', 200: 'trapno', 208: 'oldmask', 216: 'cr2',
|
||||
224: '&fpstate', 232: '__reserved', 240: 'sigmask'},
|
||||
224: '&fpstate', 232: '__reserved', 296: 'sigmask'},
|
||||
# Reference : http://lxr.free-electrons.com/source/arch/arm/include/uapi/asm/sigcontext.h#L15
|
||||
'arm' : {0: 'uc_flags', 4: 'uc_link', 8: 'uc_stack.ss_sp', 12: 'uc_stack.ss_flags',
|
||||
16: 'uc_stack.ss_size', 20: 'trap_no', 24: 'error_code', 28: 'oldmask', 32: 'r0',
|
||||
@@ -270,15 +270,15 @@ class SigreturnFrame(dict):
|
||||
>>> context.clear(arch='amd64')
|
||||
>>> s = SigreturnFrame()
|
||||
>>> unpack_many(bytes(s))
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0]
|
||||
>>> assert len(s) == 248
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
>>> assert len(s) == 304
|
||||
>>> s.rax = 0xa
|
||||
>>> s.rdi = 0x00601000
|
||||
>>> s.rsi = 0x1000
|
||||
>>> s.rdx = 0x7
|
||||
>>> assert len(bytes(s)) == 248
|
||||
>>> assert len(bytes(s)) == 304
|
||||
>>> unpack_many(bytes(s))
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6295552, 4096, 0, 0, 7, 10, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0]
|
||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6295552, 4096, 0, 0, 7, 10, 0, 0, 0, 0, 51, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||
|
||||
Crafting a SigreturnFrame that calls mprotect on i386
|
||||
|
||||
|
||||
@@ -170,21 +170,47 @@ tether = sys.modules[__name__]
|
||||
# Create the module structure
|
||||
shellcraft = module(__name__, '')
|
||||
|
||||
class LazyImporter:
|
||||
def find_module(self, fullname, path=None):
|
||||
if not fullname.startswith('pwnlib.shellcraft.'):
|
||||
return None
|
||||
|
||||
parts = fullname.split('.')[2:]
|
||||
cur = shellcraft
|
||||
for part in parts:
|
||||
cur = getattr(cur, part, None)
|
||||
if not isinstance(cur, ModuleType):
|
||||
if sys.version_info >= (3, 4):
|
||||
import importlib.abc
|
||||
import importlib.util
|
||||
class LazyImporter(importlib.abc.MetaPathFinder, importlib.abc.Loader):
|
||||
def find_spec(self, fullname, path=None, target=None):
|
||||
if not fullname.startswith('pwnlib.shellcraft.'):
|
||||
return None
|
||||
|
||||
return self
|
||||
parts = fullname.split('.')[2:]
|
||||
cur = shellcraft
|
||||
for part in parts:
|
||||
cur = getattr(cur, part, None)
|
||||
if not isinstance(cur, ModuleType):
|
||||
return None
|
||||
|
||||
def load_module(self, fullname):
|
||||
return sys.modules[fullname]
|
||||
return importlib.util.spec_from_loader(fullname, self)
|
||||
|
||||
def create_module(self, spec):
|
||||
# The submodule was already built and registered in sys.modules by
|
||||
# the getattr walk in find_spec, so hand that object back instead
|
||||
# of letting the import system make a fresh empty module.
|
||||
return sys.modules.get(spec.name)
|
||||
|
||||
def exec_module(self, module):
|
||||
pass
|
||||
else:
|
||||
class LazyImporter:
|
||||
def find_module(self, fullname, path=None):
|
||||
if not fullname.startswith('pwnlib.shellcraft.'):
|
||||
return None
|
||||
|
||||
parts = fullname.split('.')[2:]
|
||||
cur = shellcraft
|
||||
for part in parts:
|
||||
cur = getattr(cur, part, None)
|
||||
if not isinstance(cur, ModuleType):
|
||||
return None
|
||||
|
||||
return self
|
||||
|
||||
def load_module(self, fullname):
|
||||
return sys.modules[fullname]
|
||||
|
||||
sys.meta_path.append(LazyImporter())
|
||||
@@ -116,10 +116,10 @@ i386_ordered = [
|
||||
['rbx', 'ebx', 'bx', 'bl'],
|
||||
['rcx', 'ecx', 'cx', 'cl'],
|
||||
['rdx', 'edx', 'dx', 'dl'],
|
||||
['rdi', 'edi', 'di'],
|
||||
['rsi', 'esi', 'si'],
|
||||
['rbp', 'ebp', 'bp'],
|
||||
['rsp', 'esp', 'sp'],
|
||||
['rdi', 'edi', 'di', 'dil'],
|
||||
['rsi', 'esi', 'si', 'sil'],
|
||||
['rbp', 'ebp', 'bp', 'bpl'],
|
||||
['rsp', 'esp', 'sp', 'spl'],
|
||||
['r8', 'r8d', 'r8w', 'r8b'],
|
||||
['r9', 'r9d', 'r9w', 'r9b'],
|
||||
['r10', 'r10d', 'r10w', 'r10b'],
|
||||
|
||||
@@ -51,8 +51,8 @@ Example:
|
||||
xor eax, eax
|
||||
mov ax, 0xc0c0
|
||||
>>> print(shellcraft.amd64.mov('rdi', 0xff).rstrip())
|
||||
mov edi, 0x1010101 /* 255 == 0xff */
|
||||
xor edi, 0x10101fe
|
||||
xor edi, edi
|
||||
mov dil, 0xff
|
||||
>>> print(shellcraft.amd64.mov('rax', 0xdead00ff).rstrip())
|
||||
mov eax, 0x1010101 /* 3735879935 == 0xdead00ff */
|
||||
xor eax, 0xdfac01fe
|
||||
|
||||
@@ -55,7 +55,7 @@ if isinstance(eax, six.integer_types) and isinstance(edx, six.integer_types) and
|
||||
cdq = True
|
||||
reg_context.pop('rdx')
|
||||
|
||||
sorted_regs = regsort(reg_context, registers.amd64)
|
||||
sorted_regs = regsort(reg_context, registers.amd64, registers.native64)
|
||||
%>
|
||||
% if not sorted_regs:
|
||||
/* setregs noop */
|
||||
|
||||
@@ -1,24 +1,30 @@
|
||||
<%
|
||||
from pwnlib.shellcraft import amd64, pretty
|
||||
from pwnlib.shellcraft import amd64, common, pretty
|
||||
from pwnlib.util.packing import u64, _need_bytes
|
||||
%>
|
||||
<%docstring>Find the address of an exported function in a dll
|
||||
by manually iterating through the PE export table.
|
||||
|
||||
The dll must be `b'kernel32.dll'` or `b'ntdll.dll'` at the moment.
|
||||
Behavior is undefined if the function is not found.
|
||||
|
||||
Args:
|
||||
function_name (str): The name of the function to find.
|
||||
dll(str): The name of the DLL to find the function in.
|
||||
function_name (bytes): The name of the function to find.
|
||||
dll(bytes): The name of the DLL to find the function in.
|
||||
dest (str): The register to load the function address into.
|
||||
</%docstring>
|
||||
<%page args="function_name,dll='kernel32.dll',dest='rax'"/>
|
||||
<%page args="function_name,dll=b'kernel32.dll',dest='rax'"/>
|
||||
<%
|
||||
function_name = _need_bytes(function_name)
|
||||
dll = _need_bytes(dll)
|
||||
if len(function_name) > 8:
|
||||
raise ValueError('function_name must be <= 8 bytes')
|
||||
assert dll == b'kernel32.dll'
|
||||
assert dll in (b'kernel32.dll', b'ntdll.dll')
|
||||
find_function_loop = common.label('find_function').strip()
|
||||
%>
|
||||
% if dll == b'kernel32.dll':
|
||||
${amd64.windows.kernel32base('rbx')} /* rbx = kernel32.dll PE base */
|
||||
% elif dll == b'ntdll.dll':
|
||||
${amd64.windows.ntdllbase('rbx')} /* rbx = ntdll.dll PE base */
|
||||
% endif
|
||||
mov r8d, [rbx + 0x3c]
|
||||
mov rdx, r8
|
||||
add rdx, rbx
|
||||
@@ -26,25 +32,42 @@ assert dll == b'kernel32.dll'
|
||||
add rdx, r9
|
||||
mov r8d, [rdx]
|
||||
add r8, rbx /* r8 = export table */
|
||||
mov esi, [r8 + 0x20]
|
||||
add rsi, rbx /* rsi = names table */
|
||||
xor rcx, rcx
|
||||
mov edx, [r8 + 0x20]
|
||||
add rdx, rbx /* rdx = names table */
|
||||
% if len(function_name) <= 8:
|
||||
mov r9, ${pretty(u64(function_name.ljust(8, b'\x00')))}
|
||||
% else:
|
||||
${amd64.pushstr(function_name)}
|
||||
mov r9, rsp
|
||||
% endif
|
||||
push r8
|
||||
xor r8, r8
|
||||
|
||||
/* Loop through the names table */
|
||||
FindFunction:
|
||||
inc rcx
|
||||
mov eax, [rsi + rcx * 4]
|
||||
${find_function_loop}:
|
||||
inc r8
|
||||
mov eax, [rdx + r8 * 4]
|
||||
add rax, rbx
|
||||
## TODO: implement strcmp properly for function names > 8 bytes
|
||||
## strcmp
|
||||
% if len(function_name) <= 8:
|
||||
cmp qword ptr [rax], r9
|
||||
jnz FindFunction
|
||||
% else:
|
||||
${amd64.setregs({
|
||||
'rdi': 'rax',
|
||||
'rsi': 'r9',
|
||||
'rcx': len(function_name),
|
||||
})}
|
||||
repe cmpsb
|
||||
movzx rax, byte ptr [rsi-1]
|
||||
movzx rcx, byte ptr [rdi-1]
|
||||
sub rax, rcx
|
||||
% endif
|
||||
jnz ${find_function_loop}
|
||||
|
||||
## Assume we find the function, we'll crash walking past the
|
||||
## end of the names table otherwise.
|
||||
mov rcx, r8
|
||||
pop r8
|
||||
mov esi, [r8 + 0x24]
|
||||
add rsi, rbx /* rsi = ordinals table */
|
||||
mov cx, [rsi + rcx * 2]
|
||||
@@ -52,4 +75,4 @@ assert dll == b'kernel32.dll'
|
||||
add rsi, rbx /* rsi = address table */
|
||||
mov eax, [rsi + rcx * 4]
|
||||
add rax, rbx /* rax = function address */
|
||||
mov ${dest}, rax
|
||||
${amd64.mov(dest, 'rax')}
|
||||
@@ -11,7 +11,7 @@ Args:
|
||||
dll(str): The name of the DLL to find the function in.
|
||||
dest (str): The register to load the function address into.
|
||||
</%docstring>
|
||||
<%page args="function_name,dll='kernel32.dll',dest='rax'"/>
|
||||
<%page args="function_name,dll=b'kernel32.dll',dest='rax'"/>
|
||||
<%
|
||||
function_name = _need_bytes(function_name)
|
||||
dll = _need_bytes(dll)
|
||||
@@ -25,4 +25,4 @@ assert dll == b'kernel32.dll'
|
||||
sub rsp, 0x30
|
||||
call rdi
|
||||
add rsp, ${pretty(0x30+align(8, len(function_name)))}
|
||||
mov ${dest}, rax
|
||||
${amd64.mov(dest, 'rax')}
|
||||
@@ -58,12 +58,9 @@ Example:
|
||||
>>> print(shellcraft.i386.mov('eax', 0xdead00ff).rstrip())
|
||||
mov eax, -0xdead00ff
|
||||
neg eax
|
||||
>>> print(shellcraft.i386.mov('eax', 0xc0).rstrip())
|
||||
xor eax, eax
|
||||
mov al, 0xc0
|
||||
>>> print(shellcraft.i386.mov('edi', 0xc0).rstrip())
|
||||
mov edi, -0xc0
|
||||
neg edi
|
||||
xor edi, edi
|
||||
mov dil, 0xc0
|
||||
>>> print(shellcraft.i386.mov('eax', 0xc000).rstrip())
|
||||
xor eax, eax
|
||||
mov ah, 0xc000 >> 8
|
||||
|
||||
@@ -54,7 +54,7 @@ sorted_regs = regsort(reg_context, registers.i386)
|
||||
% if not sorted_regs:
|
||||
/* setregs noop */
|
||||
% else:
|
||||
% for how, src, dst in regsort(reg_context, registers.i386):
|
||||
% for how, src, dst in regsort(reg_context, registers.i386, registers.native32):
|
||||
% if how == 'xchg':
|
||||
xchg ${src}, ${dst}
|
||||
% else:
|
||||
|
||||
@@ -380,7 +380,7 @@ def readline(_size=-1, prompt='', float=True, priority=10):
|
||||
from pwnlib.term import term_mode
|
||||
if not term_mode:
|
||||
six.print_(prompt, end='', flush=True)
|
||||
return getattr(sys.stdin, 'buffer', sys.stdin).readline(_size).rstrip(b'\n')
|
||||
return force_to_bytes(getattr(sys.stdin, 'buffer', sys.stdin).readline(_size)).rstrip(b'\n')
|
||||
show_suggestions = False
|
||||
eof = False
|
||||
if prompt:
|
||||
|
||||
+19
-16
@@ -767,9 +767,13 @@ class process(tube):
|
||||
|
||||
if IS_WINDOWS:
|
||||
with self.countdown(timeout=timeout):
|
||||
while self.timeout and self._read_queue.empty():
|
||||
while self.timeout and self._read_queue.empty() and self._read_thread.is_alive():
|
||||
time.sleep(0.01)
|
||||
return not self._read_queue.empty()
|
||||
if not self._read_queue.empty():
|
||||
return True
|
||||
if not self._read_thread.is_alive():
|
||||
raise EOFError
|
||||
return False
|
||||
|
||||
try:
|
||||
if timeout is None:
|
||||
@@ -1259,6 +1263,9 @@ class process(tube):
|
||||
|
||||
>>> from pwn import *
|
||||
>>> p = process(['cat'])
|
||||
>>> p.send(b'meow')
|
||||
>>> p.recvuntil(b'meow')
|
||||
b'meow'
|
||||
>>> libc_size = p.lib_size(p.libc.path)
|
||||
>>> hex(libc_size) # doctest: +SKIP
|
||||
'0x1d5000'
|
||||
@@ -1325,8 +1332,7 @@ class process(tube):
|
||||
by the process to the address it is loaded at in the process' address
|
||||
space.
|
||||
"""
|
||||
from pwnlib.util.proc import memory_maps
|
||||
maps_raw = self.poll() is not None and memory_maps(self.pid)
|
||||
maps_raw = self.poll() is None and self.maps()
|
||||
|
||||
if not maps_raw:
|
||||
import pwnlib.elf.elf
|
||||
@@ -1335,23 +1341,15 @@ class process(tube):
|
||||
return pwnlib.elf.elf.ELF(self.executable).maps
|
||||
|
||||
# Enumerate all of the libraries actually loaded right now.
|
||||
maps = {}
|
||||
libs = {}
|
||||
for mapping in maps_raw:
|
||||
path = mapping.path
|
||||
if os.sep not in path: continue
|
||||
path = os.path.realpath(path)
|
||||
if path not in maps:
|
||||
maps[path]=0
|
||||
if path not in libs:
|
||||
libs[path] = mapping.addr
|
||||
|
||||
for lib in maps:
|
||||
path = os.path.realpath(lib)
|
||||
for mapping in maps_raw:
|
||||
if mapping.path == path:
|
||||
address = mapping.addr.split('-')[0]
|
||||
maps[lib] = int(address, 16)
|
||||
break
|
||||
|
||||
return maps
|
||||
return libs
|
||||
|
||||
@property
|
||||
def libc(self):
|
||||
@@ -1364,7 +1362,12 @@ class process(tube):
|
||||
Example:
|
||||
|
||||
>>> p = process("/bin/cat")
|
||||
>>> p.send(b"meow")
|
||||
>>> p.recvuntil(b"meow")
|
||||
b'meow'
|
||||
>>> libc = p.libc
|
||||
>>> libc is not None
|
||||
True
|
||||
>>> libc # doctest: +SKIP
|
||||
ELF('/lib64/libc-...so')
|
||||
>>> p.close()
|
||||
|
||||
@@ -86,7 +86,7 @@ class remote(sock):
|
||||
except socket.gaierror as e:
|
||||
if e.errno != socket.EAI_NONAME:
|
||||
raise
|
||||
self.error('Could not resolve hostname: %r', host)
|
||||
self.exception('Could not resolve hostname: %r', host)
|
||||
if self.sock:
|
||||
self.settimeout(self.timeout)
|
||||
self.lhost, self.lport = self.sock.getsockname()[:2]
|
||||
|
||||
@@ -127,7 +127,7 @@ class server(sock):
|
||||
return
|
||||
|
||||
self.rhost, self.rport = rhost[:2]
|
||||
r = remote(self.rhost, self.rport, sock = sock)
|
||||
r = remote(self.rhost, self.rport, sock = sock, level = self.level)
|
||||
h.success('Got connection from %s on port %d' % (self.rhost, self.rport))
|
||||
if callback:
|
||||
if not blocking:
|
||||
|
||||
+21
-5
@@ -692,6 +692,7 @@ class ssh(Timeout, Logger):
|
||||
if user and auth_none and str(e) == "No authentication methods available":
|
||||
self.client.get_transport().auth_none(user)
|
||||
else:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
self.transport = self.client.get_transport()
|
||||
@@ -913,6 +914,13 @@ class ssh(Timeout, Logger):
|
||||
>>> io.recvline()
|
||||
b''
|
||||
|
||||
>>> io = s.process(['tty'], tty=True)
|
||||
>>> io.recvline() # doctest: +ELLIPSIS
|
||||
b'/dev/pts/...\n'
|
||||
>>> io = s.process(['tty'], tty=False)
|
||||
>>> io.recvline()
|
||||
b'not a tty\n'
|
||||
|
||||
>>> # Testing that empty argv works
|
||||
>>> io = s.process([], executable='sh')
|
||||
>>> io.sendline(b'echo $0')
|
||||
@@ -970,7 +978,7 @@ class ssh(Timeout, Logger):
|
||||
|
||||
script = 'echo PWNTOOLS; for py in python3 python2.7 python2 python; do test -x "$(command -v $py 2>&1)" && echo $py && exec $py -c %s check; done; echo 2' % sh_string(script)
|
||||
with context.quiet:
|
||||
python = ssh_process(self, script, tty=True, cwd=cwd, raw=True, level=self.level, timeout=timeout)
|
||||
python = ssh_process(self, script, tty=tty, cwd=cwd, raw=raw, level=self.level, timeout=timeout)
|
||||
|
||||
try:
|
||||
python.recvline_contains(b'PWNTOOLS') # Magic flag so that any sh/bash initialization errors are swallowed
|
||||
@@ -1359,7 +1367,11 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
update(len(data), total)
|
||||
|
||||
result = c.wait()
|
||||
if result != 0:
|
||||
|
||||
if result == -1:
|
||||
self.warn_once("Could not verify success of file download %r, no error code" % (remote))
|
||||
|
||||
if result != 0 and result != -1:
|
||||
h.failure('Could not download file %r (%r)' % (remote, result))
|
||||
return
|
||||
|
||||
@@ -1539,7 +1551,11 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
s.shutdown('send')
|
||||
data = s.recvall()
|
||||
result = s.wait()
|
||||
if result != 0:
|
||||
|
||||
if result == -1:
|
||||
self.warn_once("Could not verify success of file upload %r, no error code" % (remote))
|
||||
|
||||
if result != 0 and result != -1:
|
||||
self.error("Could not upload file %r (%r)\n%s" % (remote, result, data))
|
||||
|
||||
def upload_file(self, filename, remote = None):
|
||||
@@ -1890,7 +1906,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
if not self.which('lsb_release'):
|
||||
return
|
||||
|
||||
with self.process(['lsb_release', '-irs']) as io:
|
||||
with self.process(['lsb_release', '-irs'], stderr='/dev/null') as io:
|
||||
lsb_info = io.recvall().strip().decode()
|
||||
self._platform_info['distro'], self._platform_info['distro_ver'] = lsb_info.split()
|
||||
except Exception:
|
||||
@@ -2073,7 +2089,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
Example:
|
||||
|
||||
>>> s = ssh("travis", "example.pwnme")
|
||||
>>> s.ibt
|
||||
>>> s.ibt # doctest: +SKIP
|
||||
False
|
||||
"""
|
||||
if self._ibt is None:
|
||||
|
||||
+53
-32
@@ -6,10 +6,12 @@ from __future__ import division
|
||||
|
||||
import collections
|
||||
import copy
|
||||
import marshal
|
||||
import multiprocessing
|
||||
import operator
|
||||
import random
|
||||
import time
|
||||
import types
|
||||
from itertools import *
|
||||
from six.moves import map, filter, filterfalse, range, zip, zip_longest
|
||||
|
||||
@@ -27,7 +29,6 @@ __all__ = [
|
||||
'group' ,
|
||||
'iter_except' ,
|
||||
'lexicographic' ,
|
||||
'lookahead' ,
|
||||
'nth' ,
|
||||
'pad' ,
|
||||
'pairwise' ,
|
||||
@@ -683,37 +684,6 @@ def random_combination_with_replacement(iterable, r):
|
||||
indices = sorted(random.randrange(n) for i in range(r))
|
||||
return tuple(pool[i] for i in indices)
|
||||
|
||||
def lookahead(n, iterable):
|
||||
"""lookahead(n, iterable) -> object
|
||||
|
||||
Inspects the upcoming element at index `n` without advancing the iterator.
|
||||
Raises ``IndexError`` if `iterable` has too few elements.
|
||||
|
||||
Arguments:
|
||||
n(int): Index of the element to return.
|
||||
iterable: An iterable.
|
||||
|
||||
Returns:
|
||||
The element in `iterable` at index `n`.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> i = count()
|
||||
>>> lookahead(4, i)
|
||||
4
|
||||
>>> next(i)
|
||||
0
|
||||
>>> i = count()
|
||||
>>> nth(4, i)
|
||||
4
|
||||
>>> next(i)
|
||||
5
|
||||
>>> lookahead(4, i)
|
||||
10
|
||||
"""
|
||||
for value in islice(copy.copy(iterable), n, None):
|
||||
return value
|
||||
raise IndexError(n)
|
||||
|
||||
def lexicographic(alphabet):
|
||||
"""lexicographic(alphabet) -> iterator
|
||||
@@ -877,6 +847,54 @@ def _mbruteforcewrap(func, alphabet, length, method, start, databag):
|
||||
databag["result"] = res
|
||||
|
||||
|
||||
class CustomPickler:
|
||||
def __init__(self, *reduction):
|
||||
self.reduction = reduction
|
||||
|
||||
def __reduce__(self):
|
||||
return self.reduction
|
||||
|
||||
def __repr__(self):
|
||||
return self.__class__.__name__ + repr(self.reduction)
|
||||
|
||||
|
||||
def construct_func(*args):
|
||||
return types.FunctionType(*args)
|
||||
|
||||
|
||||
class PicklableFunc:
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def __call__(self, *a, **kw):
|
||||
return self.f(*a, **kw)
|
||||
|
||||
def __getattr__(self, a):
|
||||
return getattr(self.f, a)
|
||||
|
||||
def __reduce__(self):
|
||||
# Constructor types.FunctionType tries to be __main__.function;
|
||||
# globs could likewise be <module>.__dict__. Neither
|
||||
# type('FunctionType', (), {'__module__':'types'})
|
||||
# nor
|
||||
# def __reduce__(self): return "FunctionType"
|
||||
# works because of checks for identity :(
|
||||
globs = CustomPickler(vars, (CustomPickler(__import__, (self.f.__module__ or '__main__',)),))
|
||||
return type(self), (CustomPickler(
|
||||
construct_func, # ideally self.f.__class__
|
||||
# but types.FunctionType tries to be __main__.function
|
||||
(
|
||||
CustomPickler(marshal.loads, (marshal.dumps(self.f.__code__),)),
|
||||
globs,
|
||||
self.f.__name__,
|
||||
self.f.__defaults__,
|
||||
self.f.__closure__,
|
||||
self.f.__kwdefaults__,
|
||||
),
|
||||
self.f.__dict__,
|
||||
),)
|
||||
|
||||
|
||||
def mbruteforce(func, alphabet, length, method = 'upto', start = None, threads = None):
|
||||
"""mbruteforce(func, alphabet, length, method = 'upto', start = None, threads = None)
|
||||
|
||||
@@ -914,6 +932,9 @@ def mbruteforce(func, alphabet, length, method = 'upto', start = None, threads =
|
||||
(i2, N2) = start
|
||||
totalchunks = threads * N2
|
||||
|
||||
if isinstance(func, types.FunctionType):
|
||||
func = PicklableFunc(func)
|
||||
|
||||
for i in range(threads):
|
||||
shareddata[i] = multiprocessing.Manager().dict()
|
||||
shareddata[i]['result'] = None
|
||||
|
||||
+3
-2
@@ -868,6 +868,7 @@ if sys.argv[-1] == 'check':
|
||||
sys.stdout.write(str(os.getgid()) + "\n")
|
||||
sys.stdout.write(str(suid) + "\n")
|
||||
sys.stdout.write(str(sgid) + "\n")
|
||||
sys.stdout.flush()
|
||||
getattr(sys.stdout, 'buffer', sys.stdout).write(os.path.realpath(exe) + b'\x00')
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -886,7 +887,7 @@ if not %(aslr)r:
|
||||
ADDR_NO_RANDOMIZE = 0x0040000
|
||||
ctypes.CDLL('libc.so.6').personality(ADDR_NO_RANDOMIZE)
|
||||
|
||||
resource.setrlimit(resource.RLIMIT_STACK, (-1, -1))
|
||||
resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
|
||||
|
||||
# Attempt to dump ALL core file regions
|
||||
try:
|
||||
@@ -897,7 +898,7 @@ except Exception:
|
||||
|
||||
# Assume that the user would prefer to have core dumps.
|
||||
try:
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (-1, -1))
|
||||
resource.setrlimit(resource.RLIMIT_CORE, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
+11
-1
@@ -988,6 +988,11 @@ def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False):
|
||||
('H', 'e', 'l', 'l', 'o', b'?')
|
||||
>>> dd(list('Hello!'), (63,), skip = 5)
|
||||
['H', 'e', 'l', 'l', 'o', b'?']
|
||||
>>> buf = bytearray(b'Hello!')
|
||||
>>> dd(buf, b'?', skip = 5)
|
||||
bytearray(b'Hello?')
|
||||
>>> buf
|
||||
bytearray(b'Hello?')
|
||||
>>> _ = open('/tmp/foo', 'w').write('A' * 10)
|
||||
>>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4).read()
|
||||
'AAA\\x00\\x00\\x00\\x00AAA'
|
||||
@@ -1105,7 +1110,12 @@ def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False):
|
||||
dst = real_dst
|
||||
|
||||
elif isinstance(dst, (list, bytearray)):
|
||||
dst[skip : skip + len(src)] = list(map(p8, bytearray(src)))
|
||||
# A bytearray slice takes a byte string, while a list slice takes the
|
||||
# single-byte bytes objects that p8() returns.
|
||||
if isinstance(dst, bytearray):
|
||||
dst[skip : skip + len(src)] = src
|
||||
else:
|
||||
dst[skip : skip + len(src)] = list(map(p8, bytearray(src)))
|
||||
if truncate:
|
||||
while len(dst) > truncate:
|
||||
dst.pop()
|
||||
|
||||
@@ -4,8 +4,8 @@ _const_codes = [
|
||||
'POP_TOP','ROT_TWO','ROT_THREE','ROT_FOUR','DUP_TOP',
|
||||
'BUILD_LIST','BUILD_MAP', 'MAP_ADD', 'BUILD_TUPLE','BUILD_SET',
|
||||
'BUILD_CONST_KEY_MAP', 'BUILD_STRING',
|
||||
'LOAD_CONST','RETURN_VALUE','STORE_SUBSCR', 'STORE_MAP',
|
||||
'LIST_TO_TUPLE', 'LIST_EXTEND', 'SET_UPDATE', 'DICT_UPDATE', 'DICT_MERGE',
|
||||
'LOAD_CONST','LOAD_SMALL_INT','RETURN_VALUE','STORE_SUBSCR', 'STORE_MAP',
|
||||
'LIST_TO_TUPLE', 'LIST_APPEND', 'LIST_EXTEND', 'SET_ADD', 'SET_UPDATE', 'DICT_UPDATE', 'DICT_MERGE',
|
||||
'COPY', 'RESUME', 'RETURN_CONST'
|
||||
]
|
||||
|
||||
@@ -29,7 +29,7 @@ def _get_opcodes(codeobj):
|
||||
Extract the actual opcodes as a list from a code object
|
||||
|
||||
>>> c = compile("[1 + 2, (1,2)]", "", "eval")
|
||||
>>> _get_opcodes(c) # doctest: +ELLIPSIS
|
||||
>>> _get_opcodes(c) # doctest: +SKIP
|
||||
[...100, 100, 103, 83]
|
||||
"""
|
||||
import dis
|
||||
@@ -85,6 +85,10 @@ def const(expr):
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: opcode BINARY_ADD not allowed
|
||||
>>> const("[" + ",".join(str(i) for i in range(40)) + "]") == list(range(40))
|
||||
True
|
||||
>>> const("{" + ",".join(str(i) for i in range(40)) + "}") == set(range(40))
|
||||
True
|
||||
"""
|
||||
|
||||
c = test_expr(expr, _const_codes)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = '4.15.0beta1'
|
||||
__version__ = '4.15.0'
|
||||
+1
-2
@@ -42,7 +42,6 @@ dependencies = [
|
||||
"ropgadget>=5.3",
|
||||
"pyserial>=2.7",
|
||||
"requests>=2.0",
|
||||
"pip>=6.0.8",
|
||||
"pygments>=2.0",
|
||||
"pysocks",
|
||||
"python-dateutil",
|
||||
@@ -50,7 +49,7 @@ dependencies = [
|
||||
"psutil>=3.3.0",
|
||||
"intervaltree>=3.0",
|
||||
"sortedcontainers",
|
||||
"unicorn>=2.0.1, !=2.1.3",
|
||||
"unicorn>=2.0.1",
|
||||
"six>=1.12.0",
|
||||
"rpyc",
|
||||
"colored_traceback<0.4; python_version < '3'",
|
||||
|
||||
@@ -87,7 +87,7 @@ if not os.path.exists(PythonH):
|
||||
sys.exit(-1)
|
||||
|
||||
setup(
|
||||
version = '4.15.0beta1',
|
||||
version = '4.15.0',
|
||||
data_files = [('pwntools-doc',
|
||||
glob.glob('*.md') + glob.glob('*.txt')),
|
||||
],
|
||||
|
||||
@@ -8,18 +8,26 @@ http {
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent"';
|
||||
access_log /dev/stdout cache_st;
|
||||
# Use Docker's embedded DNS server to resolve hostnames at runtime
|
||||
# instead of at startup.
|
||||
resolver 127.0.0.11 8.8.8.8 ipv6=off valid=30s;
|
||||
# Set default resolver for all upstreams to avoid startup-time DNS resolution
|
||||
# This is a workaround for the occasional 'host not found in upstream' error
|
||||
# in Docker environments where DNS may not be immediately available.
|
||||
|
||||
server {
|
||||
listen 3000;
|
||||
proxy_cache my_cache;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host debuginfod.elfutils.org;
|
||||
set $upstream_host "debuginfod.elfutils.org";
|
||||
proxy_set_header Host $upstream_host;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_cache_revalidate on;
|
||||
proxy_cache_key $scheme://$host$uri$is_args$query_string;
|
||||
proxy_cache_valid 200 404 12w;
|
||||
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
|
||||
proxy_pass https://debuginfod.elfutils.org/;
|
||||
proxy_pass https://$upstream_host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +36,15 @@ http {
|
||||
proxy_cache my_cache;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host libc.rip;
|
||||
set $upstream_host "libc.rip";
|
||||
proxy_set_header Host $upstream_host;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_cache_methods GET HEAD POST;
|
||||
proxy_cache_revalidate on;
|
||||
proxy_cache_key $scheme://$host$uri$is_args$query_string$request_body;
|
||||
proxy_cache_valid 200 404 12w;
|
||||
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
|
||||
proxy_pass https://libc.rip/;
|
||||
proxy_pass https://$upstream_host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,12 +53,13 @@ http {
|
||||
proxy_cache my_cache;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host archive.ubuntu.com;
|
||||
set $upstream_host "archive.ubuntu.com";
|
||||
proxy_set_header Host $upstream_host;
|
||||
proxy_cache_revalidate on;
|
||||
proxy_cache_key $scheme://$host$uri$is_args$query_string;
|
||||
proxy_cache_valid 200 404 12w;
|
||||
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
|
||||
proxy_pass http://archive.ubuntu.com/;
|
||||
proxy_pass http://$upstream_host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,13 +68,32 @@ http {
|
||||
proxy_cache my_cache;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host gitlab.com;
|
||||
set $upstream_host "gitlab.com";
|
||||
proxy_set_header Host $upstream_host;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_cache_revalidate on;
|
||||
proxy_cache_key $scheme://$host$uri$is_args$query_string;
|
||||
proxy_cache_valid 200 404 12w;
|
||||
proxy_ignore_headers Cache-Control Set-Cookie;
|
||||
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
|
||||
proxy_pass https://$upstream_host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 3004;
|
||||
proxy_cache my_cache;
|
||||
|
||||
location / {
|
||||
set $upstream_host "debuginfod.ubuntu.com";
|
||||
proxy_set_header Host $upstream_host;
|
||||
proxy_ssl_server_name on;
|
||||
proxy_cache_revalidate on;
|
||||
proxy_cache_key $scheme://$host$uri$is_args$query_string;
|
||||
proxy_cache_valid 200 404 12w;
|
||||
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504 http_429;
|
||||
proxy_pass https://gitlab.com/;
|
||||
proxy_pass https://$upstream_host$request_uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Executable
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
docker run \
|
||||
--name pwntools-test-nginx-cache \
|
||||
--rm \
|
||||
-v ./libcdb_nginx_cache.conf:/etc/nginx/nginx.conf:ro \
|
||||
-p 3000:3000 \
|
||||
-p 3001:3001 \
|
||||
-p 3002:3002 \
|
||||
-p 3003:3003 \
|
||||
nginx
|
||||
|
||||
# curl -v localhost:3000/buildid/8e9fd827446c24067541ac5390e6f527fb5947bb/debuginfo
|
||||
# curl -v localhost:3003/libcdb/libcdb/raw/master/hashes/build_id/fe136e485814fee2268cf19e5c124ed0f73f4400
|
||||
# curl -v localhost:3003/libcdb/libcdb/raw/master/libc/libc6-i386-2.18-6/lib32/libc-2.18.so
|
||||
# curl -v localhost:3003/libcdb/libcdb/raw/master/hashes/build_id/XX
|
||||
Reference in new issue
Block a user