summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTimo Röhling <roehling@debian.org>2023-09-16 11:08:56 +0200
committerTimo Röhling <roehling@debian.org>2023-09-16 11:08:56 +0200
commite66b79b9ce2cecc89dcae92efcd93110d0704798 (patch)
treed5ea2f5532d948f2c5cf126efa4cfed020efed4f
parentd2a3b5f42b8e380a1ac87084a9fef002655d0555 (diff)
New upstream version 4.11.0
-rw-r--r--.github/workflows/android.yml13
-rw-r--r--.github/workflows/changelog.yml8
-rw-r--r--.github/workflows/ci.yml41
-rw-r--r--.github/workflows/lint.yml6
-rw-r--r--.github/workflows/merge-conflict.yml6
-rw-r--r--.github/workflows/pylint.yml8
-rw-r--r--.readthedocs.yaml12
-rw-r--r--CHANGELOG.md46
-rw-r--r--MANIFEST.in2
-rwxr-xr-xdocs/requirements.txt4
-rw-r--r--docs/source/shellcraft/amd64.rst6
-rw-r--r--pwn/toplevel.py8
-rw-r--r--pwnlib/adb/adb.py8
-rw-r--r--pwnlib/asm.py13
-rw-r--r--pwnlib/context/__init__.py9
-rw-r--r--pwnlib/elf/corefile.py13
-rw-r--r--pwnlib/elf/elf.py354
-rw-r--r--pwnlib/elf/plt.py15
-rw-r--r--pwnlib/fmtstr.py72
-rw-r--r--pwnlib/gdb.py4
-rw-r--r--pwnlib/libcdb.py106
-rw-r--r--pwnlib/log.py2
-rw-r--r--pwnlib/rop/rop.py19
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/__doc__1
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/cmd.asm9
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/getexport.asm55
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/getprocaddress.asm28
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/kernel32base.asm18
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/ntdllbase.asm16
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/peb.asm8
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/teb.asm21
-rw-r--r--pwnlib/shellcraft/templates/amd64/windows/winexec.asm21
-rw-r--r--pwnlib/term/readline.py2
-rw-r--r--pwnlib/tubes/ssh.py94
-rw-r--r--pwnlib/tubes/tube.py43
-rw-r--r--pwnlib/util/packing.py7
-rw-r--r--pwnlib/version.py2
-rw-r--r--pyproject.toml71
-rw-r--r--requirements.txt6
-rwxr-xr-xsetup.py75
-rw-r--r--travis/setup_avd_fast.sh24
41 files changed, 973 insertions, 303 deletions
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index b6ee935..1a5f6aa 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -11,17 +11,17 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Cache for pip
- uses: actions/cache@v1
+ uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
@@ -33,14 +33,13 @@ jobs:
qemu-user-static \
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
- libc6-dbg \
- openjdk-8-jre-headless
+ libc6-dbg
- name: Install Android AVD
run: |
- USER=travis source travis/setup_avd.sh
+ source travis/setup_avd_fast.sh
sed -i 's/skip_android = True/skip_android = False/' docs/source/conf.py
- set | egrep '^(ANDROID|PATH)' >.android.env
+ set | grep ^PATH >.android.env
- name: Install dependencies
run: |
diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml
index 491504e..2550110 100644
--- a/.github/workflows/changelog.yml
+++ b/.github/workflows/changelog.yml
@@ -1,5 +1,9 @@
name: Changelog
-on: [pull_request]
+on:
+ pull_request:
+ paths:
+ - pwnlib/**
+ - pwn/**
env:
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
@@ -11,7 +15,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 32c7142..321457a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,12 +5,12 @@ jobs:
test:
strategy:
matrix:
- python-version: [2.7, 3.8]
+ python_version: [2.7, 3.8]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
@@ -20,16 +20,29 @@ jobs:
git log --oneline --graph -10
- name: Cache for pip
- uses: actions/cache@v1
+ uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- - name: Set up Python ${{ matrix.python-version }}
+ - name: Set up Python ${{ matrix.python_version }}
+ if: matrix.python_version != '2.7'
uses: actions/setup-python@v4
with:
- python-version: ${{ matrix.python-version }}
+ python-version: ${{ matrix.python_version }}
+
+ - name: Set up Python 2.7
+ if: matrix.python_version == '2.7'
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y \
+ python2.7 python2.7-dev python2-pip-whl
+ sudo ln -sf python2.7 /usr/bin/python
+ export PYTHONPATH=`echo /usr/share/python-wheels/pip-*py2*.whl`
+ sudo --preserve-env=PYTHONPATH python -m pip install --upgrade pip setuptools wheel
+ sudo chown -R $USER /usr/local/lib/python2.7
+
- name: Verify tag against version
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
@@ -49,7 +62,8 @@ jobs:
sudo apt-get update
sudo apt-get install -y --no-install-recommends -o Acquire::Retries=3 \
ash bash-static dash ksh mksh zsh \
- pandoc gdb gdbserver socat sshpass \
+ python3-rpyc \
+ gdb gdbserver socat \
binutils-multiarch qemu-user-static \
binutils-aarch64-linux-gnu \
binutils-arm-linux-gnueabihf \
@@ -62,11 +76,6 @@ jobs:
libc6-dbg \
elfutils
- - name: Install RPyC for GDB
- run: |
- sudo apt-get install -y python3-pip
- /usr/bin/python3 -m pip install rpyc
-
- name: Testing Corefiles
run: |
ulimit -a
@@ -82,9 +91,8 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade pip
- pip install --upgrade wheel
+ pip install --upgrade wheel build
pip install --upgrade flake8 appdirs
- python setup.py egg_info
pip install --upgrade --editable .
- name: Sanity checks
@@ -94,7 +102,8 @@ jobs:
run: pip install -r docs/requirements.txt
- name: Manually install non-broken Unicorn
- run: pip install unicorn==1.0.2rc3
+ if: matrix.python_version == '2.7'
+ run: pip install unicorn==2.0.0rc7
- name: Disable yama ptrace_scope
run: |
@@ -163,9 +172,9 @@ jobs:
pwn libcdb hash b229d1da1e161f95e839cf90cded5f719e5de308
- name: Build source and wheel distributions
+ if: matrix.python_version > '2.7'
run: |
- python setup.py sdist
- python setup.py bdist_wheel --universal
+ python -m build
- uses: actions/upload-artifact@v2
with:
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 221bac0..ac7f9ca 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -10,16 +10,16 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Cache for pip
- uses: actions/cache@v1
+ uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
diff --git a/.github/workflows/merge-conflict.yml b/.github/workflows/merge-conflict.yml
index cf1ac73..984c438 100644
--- a/.github/workflows/merge-conflict.yml
+++ b/.github/workflows/merge-conflict.yml
@@ -1,5 +1,7 @@
name: Check for merge markers
-on: [pull_request]
+on:
+ pull_request:
+ types: [synchronize]
env:
GITHUB_BASE: origin/${{ github.event.pull_request.base.ref }}
@@ -11,7 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 5
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
with:
fetch-depth: 0
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index fbe46f4..9e95784 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -10,16 +10,16 @@ jobs:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Cache for pip
- uses: actions/cache@v1
+ uses: actions/cache@v3
id: cache-pip
with:
path: ~/.cache/pip
key: ${{ matrix.os }}-cache-pip
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v1
+ uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
@@ -27,7 +27,7 @@ jobs:
run: |
set -x
pip install pylint
- pip install --upgrade -r requirements.txt
+ pip install --upgrade -e .
pylint --exit-zero --errors-only pwnlib -f parseable | cut -d ' ' -f2- > current.txt
git fetch origin
git checkout origin/"$GITHUB_BASE_REF"
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..5b502e3
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,12 @@
+# https://docs.readthedocs.io/en/stable/config-file/v2.html
+
+version: 2
+
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3"
+
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03bfd99..17c1a20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,9 +9,10 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
-| [4.12.0](#4120) | `dev` |
-| [4.11.0](#4110) | `beta` |
-| [4.10.0](#4100) | `stable` | May 21, 2023
+| [4.13.0](#4130-dev) | `dev` |
+| [4.12.0](#4120-beta) | `beta` |
+| [4.11.0](#4110-stable) | `stable` | Sep 15, 2023
+| [4.10.0](#4100) | | May 21, 2023
| [4.9.0](#490) | | Dec 29, 2022
| [4.8.0](#480) | | Apr 21, 2022
| [4.7.1](#471) | | Apr 20, 2022
@@ -66,35 +67,48 @@ 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.12.0 (`dev`)
+## 4.13.0 (`dev`)
-## 4.11.0 (`beta`)
+## 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
+- [#2219][2219] Fix passing arguments on the stack in shellcraft syscall template
+- [#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()
+
+[2202]: https://github.com/Gallopsled/pwntools/pull/2202
+[2117]: https://github.com/Gallopsled/pwntools/pull/2117
+[2221]: https://github.com/Gallopsled/pwntools/pull/2221
+[2219]: https://github.com/Gallopsled/pwntools/pull/2219
+[2212]: https://github.com/Gallopsled/pwntools/pull/2212
+[2257]: https://github.com/Gallopsled/pwntools/pull/2257
+[2225]: https://github.com/Gallopsled/pwntools/pull/2225
+
+## 4.11.0 (`stable`)
- [#2185][2185] make fmtstr module able to create payload without $ notation
-- [#2062][2062] make pwn cyclic -l work with entry larger than 4 bytes
-- [#2092][2092] shellcraft: dup() is now called dupio() consistently across all supported arches
-- [#2093][2093] setresuid() in shellcraft uses current euid by default
- [#2103][2103] Add search for libc binary by leaked function addresses `libcdb.search_by_symbol_offsets()`
-- [#2125][2125] Allow tube.recvregex to return capture groups
-- [#2144][2144] Removes `p2align 2` `asm()` headers from `x86-32`, `x86-64` and `mips` architectures to avoid inconsistent instruction length when patching binaries
- [#2177][2177] Support for RISC-V 64-bit architecture
- [#2186][2186] Enhance `ELF.nx` and `ELF.execstack`
- [#2129][2129] Handle `context.newline` correctly when typing in `tube.interactive()`
+- [#2214][2214] Fix bug at ssh.py:`download` and `download_file` with relative paths
+- [#2241][2241] Fix ssh.process not setting ssh_process.cwd attribute
+- [#2261][2261] Fix corefile module after pyelftools update
[2185]: https://github.com/Gallopsled/pwntools/pull/2185
-[2062]: https://github.com/Gallopsled/pwntools/pull/2062
-[2092]: https://github.com/Gallopsled/pwntools/pull/2092
-[2093]: https://github.com/Gallopsled/pwntools/pull/2093
[2103]: https://github.com/Gallopsled/pwntools/pull/2103
-[2125]: https://github.com/Gallopsled/pwntools/pull/2125
-[2144]: https://github.com/Gallopsled/pwntools/pull/2144
[2177]: https://github.com/Gallopsled/pwntools/pull/2177
[2186]: https://github.com/Gallopsled/pwntools/pull/2186
[2129]: https://github.com/Gallopsled/pwntools/pull/2129
+[2214]: https://github.com/Gallopsled/pwntools/pull/2214
+[2241]: https://github.com/Gallopsled/pwntools/pull/2241
+[2261]: https://github.com/Gallopsled/pwntools/pull/2261
-## 4.10.0 (`stable`)
+## 4.10.0
In memoriam — [Zach Riggle][zach] — long time contributor and maintainer of Pwntools.
diff --git a/MANIFEST.in b/MANIFEST.in
index dcf6b23..8f001ea 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,7 @@
+graft build
graft examples
graft extra
+graft travis
include *.md *.txt *.sh *.yml MANIFEST.in
recursive-include docs *.rst *.png Makefile *.py *.txt
recursive-include pwnlib *.py *.asm *.rst *.md *.txt *.sh __doc__ *.mako
diff --git a/docs/requirements.txt b/docs/requirements.txt
index b54a40f..d44363c 100755
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,7 +1,9 @@
capstone
coveralls
+python-dateutil
doc2dash
docutils<0.18
+intervaltree
isort
mako>=1.0.0
paramiko>=1.15.2
@@ -10,6 +12,8 @@ pyelftools>=0.2.3
pygments>=2.0
pypandoc
pyserial>=2.7
+pysocks
+psutil
requests>=2.5.1
ropgadget>=5.3
sphinx==1.8.6; python_version<'3'
diff --git a/docs/source/shellcraft/amd64.rst b/docs/source/shellcraft/amd64.rst
index 03dc8cd..8aced2c 100644
--- a/docs/source/shellcraft/amd64.rst
+++ b/docs/source/shellcraft/amd64.rst
@@ -17,3 +17,9 @@
.. automodule:: pwnlib.shellcraft.amd64.linux
:members:
+
+:mod:`pwnlib.shellcraft.amd64.windows`
+---------------------------------------
+
+.. automodule:: pwnlib.shellcraft.amd64.windows
+ :members:
diff --git a/pwn/toplevel.py b/pwn/toplevel.py
index f0658a7..92b01ad 100644
--- a/pwn/toplevel.py
+++ b/pwn/toplevel.py
@@ -16,7 +16,6 @@ import tempfile
import threading
import time
-import colored_traceback
from pprint import pprint
import pwnlib
@@ -85,7 +84,12 @@ info = log.info
debug = log.debug
success = log.success
-colored_traceback.add_hook()
+try:
+ import colored_traceback
+except ImportError:
+ pass
+else:
+ colored_traceback.add_hook()
# Equivalence with the default behavior of "from import *"
# __all__ = [x for x in tuple(globals()) if not x.startswith('_')]
diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py
index 14299b8..789546f 100644
--- a/pwnlib/adb/adb.py
+++ b/pwnlib/adb/adb.py
@@ -1370,8 +1370,12 @@ def compile(source):
ndk_build = misc.which('ndk-build')
if not ndk_build:
# Ensure that we can find the NDK.
- ndk = os.environ.get('NDK', None)
- if ndk is None:
+ for envvar in ('NDK', 'ANDROID_NDK', 'ANDROID_NDK_ROOT',
+ 'ANDROID_NDK_HOME', 'ANDROID_NDK_LATEST_HOME'):
+ ndk = os.environ.get(envvar)
+ if ndk is not None:
+ break
+ else:
log.error('$NDK must be set to the Android NDK directory')
ndk_build = os.path.join(ndk, 'ndk-build')
diff --git a/pwnlib/asm.py b/pwnlib/asm.py
index 745ac74..b37d39c 100644
--- a/pwnlib/asm.py
+++ b/pwnlib/asm.py
@@ -185,6 +185,8 @@ def which_binutils(util, check_version=False):
'mips64': ['mips'],
'powerpc64': ['powerpc'],
'sparc64': ['sparc'],
+ 'riscv32': ['riscv32', 'riscv64', 'riscv'],
+ 'riscv64': ['riscv64', 'riscv32', 'riscv'],
}.get(arch, [])
# If one of the candidate architectures matches the native
@@ -262,7 +264,11 @@ def _assembler():
'powerpc64': [gas, '-m%s' % context.endianness, '-mppc%s' % context.bits],
# ia64 only accepts -mbe or -mle
- 'ia64': [gas, '-m%ce' % context.endianness[0]]
+ 'ia64': [gas, '-m%ce' % context.endianness[0]],
+
+ # riscv64-unknown-elf-as supports riscv32 as well as riscv64
+ 'riscv32': [gas, '-march=rv32gc', '-mabi=ilp32'],
+ 'riscv64': [gas, '-march=rv64gc', '-mabi=lp64'],
}
assembler = assemblers.get(context.arch, [gas])
@@ -362,7 +368,8 @@ def _bfdname():
'msp430' : 'elf32-msp430',
'powerpc' : 'elf32-powerpc',
'powerpc64' : 'elf64-powerpc',
- 'riscv' : 'elf%d-%sriscv' % (context.bits, E),
+ 'riscv32' : 'elf%d-%sriscv' % (context.bits, E),
+ 'riscv64' : 'elf%d-%sriscv' % (context.bits, E),
'vax' : 'elf32-vax',
's390' : 'elf%d-s390' % context.bits,
'sparc' : 'elf32-sparc',
@@ -385,6 +392,8 @@ def _bfdarch():
'powerpc64': 'powerpc',
'sparc64': 'sparc',
'thumb': 'arm',
+ 'riscv32': 'riscv',
+ 'riscv64': 'riscv',
}
if arch in convert:
diff --git a/pwnlib/context/__init__.py b/pwnlib/context/__init__.py
index dfeacdd..10a7a4f 100644
--- a/pwnlib/context/__init__.py
+++ b/pwnlib/context/__init__.py
@@ -404,7 +404,8 @@ class ContextType(object):
'msp430': little_16,
'powerpc': big_32,
'powerpc64': big_64,
- 'riscv': little_32,
+ 'riscv32': little_32,
+ 'riscv64': little_64,
's390': big_32,
'sparc': big_32,
'sparc64': big_64,
@@ -775,7 +776,9 @@ class ContextType(object):
('i686', 'i386'),
('armv7l', 'arm'),
('armeabi', 'arm'),
- ('arm64', 'aarch64')]
+ ('arm64', 'aarch64'),
+ ('rv32', 'riscv32'),
+ ('rv64', 'riscv64')]
for k, v in transform:
if arch.startswith(k):
arch = v
@@ -1447,7 +1450,7 @@ class ContextType(object):
from pwnlib.tubes.ssh import ssh
if not isinstance(shell, ssh):
- raise AttributeError("context.ssh_session must be an ssh tube")
+ raise AttributeError("context.ssh_session must be an ssh tube")
return shell
diff --git a/pwnlib/elf/corefile.py b/pwnlib/elf/corefile.py
index 9d81995..b5a248a 100644
--- a/pwnlib/elf/corefile.py
+++ b/pwnlib/elf/corefile.py
@@ -76,7 +76,6 @@ import tempfile
from io import BytesIO, StringIO
import elftools
-from elftools.common.py3compat import bytes2str
from elftools.common.utils import roundup
from elftools.common.utils import struct_parse
from elftools.construct import CString
@@ -94,6 +93,7 @@ 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
@@ -134,12 +134,13 @@ def iter_notes(self):
self.stream.seek(offset)
# n_namesz is 4-byte aligned.
disk_namesz = roundup(note['n_namesz'], 2)
- note['n_name'] = bytes2str(
- CString('').parse(self.stream.read(disk_namesz)))
- offset += disk_namesz
+ with context.local(encoding='latin-1'):
+ note['n_name'] = _decode(
+ CString('').parse(self.stream.read(disk_namesz)))
+ offset += disk_namesz
- desc_data = bytes2str(self.stream.read(note['n_descsz']))
- note['n_desc'] = desc_data
+ 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
diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py
index 9053a1a..8bbf0b8 100644
--- a/pwnlib/elf/elf.py
+++ b/pwnlib/elf/elf.py
@@ -49,7 +49,6 @@ from six import BytesIO
from collections import namedtuple
-from elftools.elf.constants import E_FLAGS
from elftools.elf.constants import P_FLAGS
from elftools.elf.constants import SHN_INDICES
from elftools.elf.descriptions import describe_e_type
@@ -227,20 +226,13 @@ class ELF(ELFFile):
#: :class:`str`: Path to the file
self.path = packing._need_text(os.path.abspath(path))
- #: :class:`str`: Architecture of the file (e.g. ``'i386'``, ``'arm'``).
- #:
- #: See: :attr:`.ContextType.arch`
- self.arch = self.get_machine_arch()
- if isinstance(self.arch, (bytes, six.text_type)):
- self.arch = self.arch.lower()
-
#: :class:`dotdict` of ``name`` to ``address`` for all symbols in the ELF
self.symbols = dotdict()
#: :class:`dotdict` of ``name`` to ``address`` for all Global Offset Table (GOT) entries
self.got = dotdict()
- #: :class:`dotdict` of ``name`` to ``address`` for all Procedure Linkate Table (PLT) entries
+ #: :class:`dotdict` of ``name`` to ``address`` for all Procedure Linkage Table (PLT) entries
self.plt = dotdict()
#: :class:`dotdict` of ``name`` to :class:`.Function` for each function in the ELF
@@ -268,17 +260,12 @@ class ELF(ELFFile):
#: :class:`int`: Pointer width, in bytes
self.bytes = self.bits // 8
- if self.arch == 'mips':
- mask = lambda a, b: a & b == b
- flags = self.header['e_flags']
-
- if mask(flags, E_FLAGS.EF_MIPS_ARCH_32) \
- or mask(flags, E_FLAGS.EF_MIPS_ARCH_32R2):
- pass
- elif mask(flags, E_FLAGS.EF_MIPS_ARCH_64) \
- or mask(flags, E_FLAGS.EF_MIPS_ARCH_64R2):
- self.arch = 'mips64'
- self.bits = 64
+ #: :class:`str`: Architecture of the file (e.g. ``'i386'``, ``'arm'``).
+ #:
+ #: See: :attr:`.ContextType.arch`
+ self.arch = self.get_machine_arch()
+ if isinstance(self.arch, (bytes, six.text_type)):
+ self.arch = self.arch.lower()
self._sections = None
self._segments = None
@@ -471,18 +458,21 @@ class ELF(ELFFile):
def get_machine_arch(self):
return {
- 'EM_X86_64': 'amd64',
- 'EM_386' :'i386',
- 'EM_486': 'i386',
- 'EM_ARM': 'arm',
- 'EM_AARCH64': 'aarch64',
- 'EM_MIPS': 'mips',
- 'EM_PPC': 'powerpc',
- 'EM_PPC64': 'powerpc64',
- 'EM_SPARC32PLUS': 'sparc',
- 'EM_SPARCV9': 'sparc64',
- 'EM_IA_64': 'ia64'
- }.get(self['e_machine'], self['e_machine'])
+ ('EM_X86_64', 64): 'amd64',
+ ('EM_386', 32): 'i386',
+ ('EM_486', 32): 'i386',
+ ('EM_ARM', 32): 'arm',
+ ('EM_AARCH64', 64): 'aarch64',
+ ('EM_MIPS', 32): 'mips',
+ ('EM_MIPS', 64): 'mips64',
+ ('EM_PPC', 32): 'powerpc',
+ ('EM_PPC64', 64): 'powerpc64',
+ ('EM_SPARC32PLUS', 32): 'sparc',
+ ('EM_SPARCV9', 64): 'sparc64',
+ ('EM_IA_64', 64): 'ia64',
+ ('EM_RISCV', 32): 'riscv32',
+ ('EM_RISCV', 64): 'riscv64',
+ }.get((self['e_machine'], self.bits), self['e_machine'])
@property
def entry(self):
@@ -1061,10 +1051,10 @@ class ELF(ELFFile):
return
banner = self.string(self.symbols.linux_banner)
-
+
# convert banner into a utf-8 string since re.search does not accept bytes anymore
banner = banner.decode('utf-8')
-
+
# 'Linux version 3.18.31-gd0846ecc
regex = r'Linux version (\S+)'
match = re.search(regex, banner)
@@ -1114,7 +1104,7 @@ class ELF(ELFFile):
else:
log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
return 0
-
+
lines = self.functions['__libc_start_main'].disasm().split('\n')
exit_addr = hex(self.symbols['exit'])
calls = [(index, line) for index, line in enumerate(lines) if set(line.split()) & call_instructions]
@@ -1128,7 +1118,7 @@ class ELF(ELFFile):
return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
return return_from_main
-
+
# Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
ret_addr = find_ret_main_addr(lines, calls)
# Pre glibc-2.34 case - `main` is called directly
@@ -1142,7 +1132,7 @@ class ELF(ELFFile):
match = direct_call_pattern.search(line[1])
if not match:
continue
-
+
target_addr = int(match.group(1), 0)
# `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
# we might disassemble a bit too much, but it's a good dynamic estimate.
@@ -1685,61 +1675,244 @@ class ELF(ELFFile):
Specifically, we are checking for ``READ_IMPLIES_EXEC`` being set
by the kernel, as a result of honoring ``PT_GNU_STACK`` in the kernel.
- The **Linux kernel** directly honors ``PT_GNU_STACK`` to `mark the
- stack as executable.`__
+ ``READ_IMPLIES_EXEC`` is set, according to a set of architecture specific
+ rules, that depend on the CPU features, and the presence of ``PT_GNU_STACK``.
- .. __: https://github.com/torvalds/linux/blob/v4.9/fs/binfmt_elf.c#L784-L789
+ Unfortunately, :class:`ELF` is not context-aware, so it's not always possible
+ to determine whether the process of a binary that's missing ``PT_GNU_STACK``
+ will have NX or not.
+
+ The rules are as follows:
+
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | ELF arch | linux | GNU_STACK | other | NX |
+ +===========+==============+===========================+================================================+==========+
+ | i386 | < 5.8 | non-exec | | enabled |
+ | | [#x86_5.7]_ +---------------------------+------------------------------------------------+----------+
+ | | | exec / missing | | disabled |
+ | +--------------+---------------------------+------------------------------------------------+----------+
+ | | >= 5.8 | exec / non-exec | | enabled |
+ | | [#x86_5.8]_ +---------------------------+------------------------------------------------+----------+
+ | | | missing | | disabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | amd64 | < 5.8 | non-exec | | enabled |
+ | | [#x86_5.7]_ +---------------------------+------------------------------------------------+----------+
+ | | | exec / missing | | disabled |
+ | +--------------+---------------------------+------------------------------------------------+----------+
+ | | >= 5.8 | exec / non-exec / missing | | enabled |
+ | | [#x86_5.8]_ | | | |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | arm | < 5.8 | non-exec* | | enabled |
+ | | [#arm_5.7]_ +---------------------------+------------------------------------------------+----------+
+ | | | exec / missing | | disabled |
+ | +--------------+---------------------------+------------------------------------------------+----------+
+ | | >= 5.8 | exec / non-exec* | | enabled |
+ | | [#arm_5.8]_ +---------------------------+------------------------------------------------+----------+
+ | | | missing | | disabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | mips | < 5.18 | non-exec* | | enabled |
+ | | [#mips_5.17]_+---------------------------+------------------------------------------------+----------+
+ | | | exec / missing | | disabled |
+ | +--------------+---------------------------+------------------------------------------------+----------+
+ | | >= 5.18 | exec / non-exec* | | enabled |
+ | | [#mips_5.18]_+---------------------------+------------------------------------------------+----------+
+ | | | missing | | disabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | powerpc | [#powerpc]_ | non-exec / exec | | enabled |
+ | | +---------------------------+------------------------------------------------+----------+
+ | | | missing | | disabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | powerpc64 | [#powerpc]_ | exec / non-exec / missing | | enabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | ia64 | [#ia64]_ | non-exec | | enabled |
+ | | +---------------------------+------------------------------------------------+----------+
+ | | | exec / missing | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK == 0 | enabled |
+ | | + +------------------------------------------------+----------+
+ | | | | e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK != 0 | disabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+ | the rest | [#the_rest]_ | exec / non-exec / missing | | enabled |
+ +-----------+--------------+---------------------------+------------------------------------------------+----------+
+
+ \\* 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;
+
+ .. [#x86_5.7]
+ `source <https://github.com/torvalds/linux/blob/v5.7/arch/x86/include/asm/elf.h#L285-L286>`__
+
+ .. code-block:: c
+
+ #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
+
+ #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
+
+ /*
+ * 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
+
+ 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
+
+ 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
+
+ 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
+
+ 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);
+ }
- .. code-block:: c
+ .. [#powerpc]
+ `source <https://github.com/torvalds/linux/blob/v6.3/arch/powerpc/include/asm/elf.h#L82-L108>`__
- case PT_GNU_STACK:
- if (elf_ppnt->p_flags & PF_X)
- executable_stack = EXSTACK_ENABLE_X;
- else
- executable_stack = EXSTACK_DISABLE_X;
- break;
+ .. code-block:: c
- Additionally, it then sets ``read_implies_exec``, so that `all readable pages
- are executable`__.
+ #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__ */
- .. __: https://github.com/torvalds/linux/blob/v4.9/fs/binfmt_elf.c#L849-L850
+ .. [#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)
+
+ EF_IA_64_LINUX_EXECUTABLE_STACK__:
+ .. __: https://github.com/torvalds/linux/blob/v6.3/arch/ia64/include/asm/elf.h#L33
+
+ .. 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
- if (elf_read_implies_exec(loc->elf_ex, executable_stack))
- current->personality |= READ_IMPLIES_EXEC;
+ # define elf_read_implies_exec(ex, have_pt_gnu_stack) 0
"""
if not self.executable:
return True
-
+
+ exec_bit = None
for seg in self.iter_segments_by_type('GNU_STACK'):
- return not bool(seg.header.p_flags & P_FLAGS.PF_X)
+ exec_bit = bool(seg.header.p_flags & P_FLAGS.PF_X)
- # If you NULL out the PT_GNU_STACK section via ELF.disable_nx(),
- # everything is executable.
- return False
+ non_exec = exec_bit is False
+ missing = exec_bit is None
+ EF_IA_64_LINUX_EXECUTABLE_STACK = 1
+
+ if self.arch in ['i386', 'arm', 'aarch64', 'mips', 'mips64']:
+ if non_exec:
+ return True
+ elif missing:
+ return False
+ return None
+ elif self.arch == 'amd64':
+ return True if non_exec else None
+ elif self.arch == 'powerpc':
+ return not missing
+ elif self.arch == 'powerpc64':
+ return True
+ elif self.arch == 'ia64':
+ if non_exec:
+ return True
+ return not bool(self['e_flags'] & EF_IA_64_LINUX_EXECUTABLE_STACK)
+
+ return True
@property
def execstack(self):
- """:class:`bool`: Whether the current binary uses an executable stack.
+ """:class:`bool`: Whether dynamically loading the current binary will make the stack executable.
- This is based on the presence of a program header PT_GNU_STACK_
- being present, and its setting.
+ This is based on the presence of a program header ``PT_GNU_STACK``,
+ its setting, and the default stack permissions for the architecture.
- ``PT_GNU_STACK``
+ If ``PT_GNU_STACK`` is persent, the stack permissions are `set according to it`__:
- The p_flags member specifies the permissions on the segment
- containing the stack and is used to indicate whether the stack
- should be executable. The absense of this header indicates
- that the stack will be executable.
+ .. __: https://github.com/bminor/glibc/blob/glibc-2.37/elf/dl-load.c#L1218-L1220
- In particular, if the header is missing the stack is executable.
- If the header is present, it may **explicitly** mark that the stack is
- executable.
+ .. code-block:: c
+
+ case PT_GNU_STACK:
+ stack_flags = ph->p_flags;
+ break;
+
+ Else, the stack permissions are set according to the architecture defaults
+ as `defined by`__ ``DEFAULT_STACK_PERMS``:
- This is only somewhat accurate. When using the GNU Linker, it usees
- DEFAULT_STACK_PERMS_ to decide whether a lack of ``PT_GNU_STACK``
- should mark the stack as executable:
+ .. __: https://github.com/bminor/glibc/blob/glibc-2.37/elf/dl-load.c#L1093-L1096
.. code-block:: c
@@ -1754,29 +1927,25 @@ class ELF(ELFFile):
::
$ git grep '#define DEFAULT_STACK_PERMS' | grep -v PF_X
- sysdeps/aarch64/stackinfo.h:31:#define DEFAULT_STACK_PERMS (PF_R|PF_W)
- sysdeps/nios2/stackinfo.h:31:#define DEFAULT_STACK_PERMS (PF_R|PF_W)
- sysdeps/tile/stackinfo.h:31:#define DEFAULT_STACK_PERMS (PF_R|PF_W)
-
- .. _PT_GNU_STACK: https://refspecs.linuxbase.org/LSB_3.0.0/LSB-PDA/LSB-PDA/progheader.html
- .. _DEFAULT_STACK_PERMS: https://github.com/bminor/glibc/blob/glibc-2.25/elf/dl-load.c#L1036-L1038
+ sysdeps/aarch64/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R|PF_W)
+ sysdeps/arc/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R|PF_W)
+ sysdeps/csky/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R|PF_W)
+ sysdeps/ia64/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R|PF_W)
+ sysdeps/loongarch/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R | PF_W)
+ sysdeps/nios2/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R|PF_W)
+ sysdeps/riscv/stackinfo.h: #define DEFAULT_STACK_PERMS (PF_R | PF_W)
"""
- # Dynamic objects do not have the ability to change the executable state of the stack.
if not self.executable:
return False
- # If NX is completely off for the process, the stack is executable.
- if not self.nx:
- return True
-
+ # If the ``PT_GNU_STACK`` program header is preset, use it's premissions.
+ for seg in self.iter_segments_by_type('GNU_STACK'):
+ return bool(seg.header.p_flags & P_FLAGS.PF_X)
+
# If the ``PT_GNU_STACK`` program header is missing, then use the
- # default rules. Only AArch64 gets a non-executable stack by default.
- for _ in self.iter_segments_by_type('GNU_STACK'):
- break
- else:
- return self.arch != 'aarch64'
-
- return False
+ # default rules. Out of the supported architectures, only AArch64,
+ # IA-64, and RISC-V get a non-executable stack by default.
+ return self.arch not in ['aarch64', 'ia64', 'riscv32', 'riscv64']
@property
def canary(self):
@@ -1851,6 +2020,7 @@ class ELF(ELFFile):
"NX:".ljust(10) + {
True: green("NX enabled"),
False: red("NX disabled"),
+ None: yellow("NX unknown - GNU_STACK missing"),
}[self.nx],
"PIE:".ljust(10) + {
True: green("PIE enabled"),
@@ -1859,7 +2029,7 @@ class ELF(ELFFile):
])
# Execstack may be a thing, even with NX enabled, because of glibc
- if self.execstack and self.nx:
+ if self.execstack and self.nx is not False:
res.append("Stack:".ljust(10) + red("Executable"))
# Are there any RWX areas in the binary?
@@ -2071,6 +2241,8 @@ class ELF(ELFFile):
if self.mmap[offset:offset+4] == PT_GNU_STACK:
self.mmap[offset:offset+4] = b'\x00' * 4
self.save()
+ # Invalidate the cached segments, ``PT_GNU_STACK`` was removed.
+ self._segments = None
return
log.error("Could not find PT_GNU_STACK, stack should already be executable")
diff --git a/pwnlib/elf/plt.py b/pwnlib/elf/plt.py
index 34ff142..ff8153d 100644
--- a/pwnlib/elf/plt.py
+++ b/pwnlib/elf/plt.py
@@ -64,8 +64,11 @@ def prepare_unicorn_and_context(elf, got, address, data):
'arm': U.UC_ARCH_ARM,
'i386': U.UC_ARCH_X86,
'mips': U.UC_ARCH_MIPS,
+ 'mips64': U.UC_ARCH_MIPS,
# 'powerpc': U.UC_ARCH_PPC, <-- Not actually supported
'thumb': U.UC_ARCH_ARM,
+ 'riscv32': U.UC_ARCH_RISCV,
+ 'riscv64': U.UC_ARCH_RISCV,
}.get(elf.arch, None)
if arch is None:
@@ -119,18 +122,6 @@ def prepare_unicorn_and_context(elf, got, address, data):
uc.mem_write(got, p_magic)
- # Separately, Unicorn is apparently unable to hook unmapped memory
- # accesses on MIPS. So we also have to map the page that contains
- # the magic address.
- start = magic_addr & (~0xfff)
- try:
- uc.mem_map(start, 0x1000)
- except Exception:
- # Ignore double-mapping
- pass
- trap = packing.p32(0x34000000, endian=elf.endian)
- uc.mem_write(magic_addr, trap)
-
return uc, uc.context_save()
diff --git a/pwnlib/fmtstr.py b/pwnlib/fmtstr.py
index c4b7682..8dddc21 100644
--- a/pwnlib/fmtstr.py
+++ b/pwnlib/fmtstr.py
@@ -675,7 +675,7 @@ def sort_atoms(atoms, numbwritten):
return out
-def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4):
+def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4, no_dollars=False):
r'''
Makes a format-string payload using glibc's dollar syntax to access the arguments.
@@ -688,6 +688,7 @@ def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4):
atoms(list): list of atoms to execute
numbwritten(int): number of byte already written by the printf function
countersize(int): size in bytes of the format string counter (usually 4)
+ 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)])
@@ -697,6 +698,13 @@ def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4):
fmt = ""
counter = numbwritten
+
+ if no_dollars:
+ # since we can't dynamically offset, we have to increment manually the parameter index, use %c, so the number of bytes written is predictable
+ fmt += "%c" * (data_offset - 1)
+ # every %c write a byte, so we need to keep track of that to have the right pad
+ counter += data_offset - 1
+
for idx, atom in enumerate(atoms):
# set format string counter to correct value
padding = atom.compute_padding(counter)
@@ -707,9 +715,54 @@ def make_payload_dollar(data_offset, atoms, numbwritten=0, countersize=4):
log.warn("padding is negative, this will not work on glibc")
# perform write
- if padding:
+ # if the padding is less than 3, it is more convenient to write it : [ len("cc") < len("%2c") ] , this could help save some bytes, if it is 3 it will take the same amout of bytes
+ # we also add ( context.bytes * no_dollars ) because , "%nccccccccc%n...ptr1ptr2" is more convenient than %"n%8c%n...ptr1ccccccccptr2"
+ if padding < 4 + context.bytes * no_dollars:
+ fmt += "c" * padding
+ ## if do not padded with %{n}c do not need to add something in data to use as argument, since we are not using a printf argument
+ else:
fmt += "%" + str(padding) + "c"
- fmt += "%" + str(data_offset + idx) + "$" + SPECIFIER[atom.size]
+
+ if no_dollars:
+ data += b'c' * context.bytes
+ '''
+ [ @murph12F was here ]
+
+ the data += b'c' * context.bytes , is used to keey the arguments aligned when a %c is performed, so it wont use the actual address to write at
+ examplea stack and payload:
+
+ fmtsr = %44c%hhn%66c%hhn
+
+ ---------
+ | addr2 |
+ ---------
+ | 0x000 |
+ ---------
+ | addr1 |
+ ---------
+ | 0x000 | <-- (rsp)
+ ---------
+
+ in this case the the first %44c will use the current arugument used pointed by rsp ( 0 ), and increment rsp
+
+ ---------
+ | addr2 |
+ ---------
+ | 0X000 |
+ ---------
+ | addr1 | <-- (rsp)
+ ---------
+ | 0x000 |
+ ---------
+
+ now it will perform the %hhn, and it will correctly use the addr1 argument
+ '''
+
+ if no_dollars:
+ fmt += "%" + SPECIFIER[atom.size]
+ else:
+ fmt += "%" + str(data_offset + idx) + "$" + SPECIFIER[atom.size]
+
data += pack(atom.start)
return fmt.encode(), data
@@ -762,7 +815,7 @@ def fmtstr_split(offset, writes, numbwritten=0, write_size='byte', write_size_ma
return make_payload_dollar(offset, atoms, numbwritten)
-def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_max='long', overflows=16, strategy="small", badbytes=frozenset(), offset_bytes=0):
+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
Makes payload with given parameter.
@@ -779,6 +832,7 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
write_size(str): must be ``byte``, ``short`` or ``int``. Tells if you want to write byte by byte, short by short or int by int (hhn, hn or n)
overflows(int): how many extra overflows (at size sz) to tolerate to reduce the length of the format string
strategy(str): either 'fast' or 'small' ('small' is default, 'fast' can be used if there are many writes)
+ no_dollars(bool) : flag to generete the payload with or w/o $ notation
Returns:
The payload in order to do needed writes
@@ -798,9 +852,15 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
>>> fmtstr_payload(1, {0x0: 0x1337babe}, write_size='byte')
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'%1c%3$na\x00\x00\x00\x00'
+ b'c%3$naaa\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)
+ b'%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%125c%hhn%17c%hhn%32c%hhn%17c%hhn%203c%hhn%34c%hhn%3618c%hnacccc>@@\x00cccc=@@\x00cccc?@@\x00cccc<@@\x00ccccK@@\x00ccccJ@@\x00ccccH@@\x00'
+ >>> fmtstr_payload(6, {0x404048 : 0xbadbad00}, no_dollars=True)
+ b'%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%229c%hhn%173c%hhn%13c%hhn%33c%hhnccccH@@\x00ccccI@@\x00ccccK@@\x00ccccJ@@\x00'
+ >>> fmtstr_payload(6, {0x4040 : 0xbadbad00, 0x4060: 0xbadbad02}, no_dollars=True)
+ b'%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%212c%hhn%173c%hhn%13c%hhn%33c%hhn%39c%hhn%171c%hhn%13c%hhn%33c%hhnacccc@@\x00\x00ccccA@\x00\x00ccccC@\x00\x00ccccB@\x00\x00cccc`@\x00\x00cccca@\x00\x00ccccc@\x00\x00ccccb@\x00\x00'
"""
sz = WRITE_SIZE[write_size]
szmax = WRITE_SIZE[write_size_max]
@@ -809,7 +869,7 @@ def fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', write_size_
fmt = b""
for _ in range(1000000):
data_offset = (offset_bytes + len(fmt)) // context.bytes
- fmt, data = make_payload_dollar(offset + data_offset, all_atoms, numbwritten=numbwritten)
+ fmt, data = make_payload_dollar(offset + data_offset, all_atoms, numbwritten=numbwritten, no_dollars=no_dollars)
fmt = fmt + cyclic((-len(fmt)-offset_bytes) % context.bytes)
if len(fmt) + offset_bytes == data_offset * context.bytes:
diff --git a/pwnlib/gdb.py b/pwnlib/gdb.py
index 049035a..753955e 100644
--- a/pwnlib/gdb.py
+++ b/pwnlib/gdb.py
@@ -599,7 +599,9 @@ def get_gdb_arch():
'powerpc64': 'powerpc:common64',
'mips64': 'mips:isa64',
'thumb': 'arm',
- 'sparc64': 'sparc:v9'
+ 'sparc64': 'sparc:v9',
+ 'riscv32': 'riscv:rv32',
+ 'riscv64': 'riscv:rv64',
}.get(context.arch, context.arch)
def binary():
diff --git a/pwnlib/libcdb.py b/pwnlib/libcdb.py
index 98f51ad..932f295 100644
--- a/pwnlib/libcdb.py
+++ b/pwnlib/libcdb.py
@@ -77,13 +77,15 @@ def provider_libc_rip(hex_encoded_id, hash_type):
data = b""
try:
result = requests.post(url, json=params, timeout=20)
- if result.status_code != 200 or len(result.json()) == 0:
+ 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)
- log.debug("Error: %s", result.text)
return None
- libc_match = result.json()
- assert len(libc_match) == 1, 'Invalid libc.rip response.'
+ 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)
@@ -217,7 +219,7 @@ def unstrip_libc(filename):
>>> libc = ELF(filename)
>>> hex(libc.symbols.main_arena)
'0x219c80'
- >>> unstrip_libc(which('python'))
+ >>> unstrip_libc(pwnlib.data.elf.get('test-x86'))
False
>>> filename = search_by_build_id('d1704d25fbbb72fa95d517b883131828c0883fe9', unstrip=True)
>>> 'main_arena' in ELF(filename).symbols
@@ -253,6 +255,98 @@ def unstrip_libc(filename):
return True
+def _handle_multiple_matching_libcs(matching_libcs):
+ from pwnlib.term import text
+ from pwnlib.ui import options
+ log.info('Multiple matching libc libraries for requested symbols:')
+ for idx, libc in enumerate(matching_libcs):
+ log.info('%d. %s', idx+1, text.red(libc['id']))
+ log.indented('\t%-20s %s', text.green('BuildID:'), libc['buildid'])
+ log.indented('\t%-20s %s', text.green('MD5:'), libc['md5'])
+ log.indented('\t%-20s %s', text.green('SHA1:'), libc['sha1'])
+ log.indented('\t%-20s %s', text.green('SHA256:'), libc['sha256'])
+ log.indented('\t%s', text.green('Symbols:'))
+ for symbol, address in libc['symbols'].items():
+ log.indented('\t%25s = %s', symbol, address)
+
+ 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):
+ """
+ Lookup possible matching libc versions based on leaked function addresses.
+
+ The leaked function addresses have to be provided as a dict mapping the
+ function name to the leaked value. Only the lower 3 nibbles are relevant
+ for the lookup.
+
+ If there are multiple matches you are presented with a list to select one
+ interactively, unless the ``select_index`` or ``return_as_list`` arguments
+ are used.
+
+ Arguments:
+ symbols(dict):
+ Dictionary mapping symbol names to their addresses.
+ select_index(int):
+ 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.
+ return_as_list(bool):
+ Return a list of build ids of all matching libc versions
+ instead of a path to a downloaded file.
+
+ Returns:
+ Path to the downloaded library on disk, or :const:`None`.
+ If the ``return_as_list`` argument is :const:`True`, a list of build ids
+ is returned instead.
+
+ Examples:
+ >>> filename = search_by_symbol_offsets({'puts': 0x420, 'printf': 0xc90}, select_index=1)
+ >>> libc = ELF(filename)
+ >>> libc.sym.system == 0x52290
+ True
+ >>> 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
+ """
+ import requests
+ 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
+
+ if return_as_list:
+ return [libc['buildid'] for libc in matching_libcs]
+
+ if len(matching_libcs) == 1:
+ return search_by_build_id(matching_libcs[0]['buildid'], unstrip=unstrip)
+
+ 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
+
+ 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)
+ return None
+
def search_by_build_id(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded Build ID, attempt to download a matching libc from libcdb.
@@ -409,4 +503,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']
+__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'unstrip_libc', 'search_by_symbol_offsets']
diff --git a/pwnlib/log.py b/pwnlib/log.py
index df01a52..4e04997 100644
--- a/pwnlib/log.py
+++ b/pwnlib/log.py
@@ -84,7 +84,7 @@ result in a message being emitted but rather an animated progress line (with a
spinner!) being created. Note that other handlers will still see a meaningful
log record.
-The custom handler will only handle log records whith a level of at least
+The custom handler will only handle log records with a level of at least
:data:`context.log_level`. Thus if e.g. the level for the
``'pwnlib.tubes.ssh'`` is set to ``'DEBUG'`` no additional output will show up
unless :data:`context.log_level` is also set to ``'DEBUG'``. Other handlers
diff --git a/pwnlib/rop/rop.py b/pwnlib/rop/rop.py
index 0756ad5..2c99282 100644
--- a/pwnlib/rop/rop.py
+++ b/pwnlib/rop/rop.py
@@ -1544,23 +1544,18 @@ class ROP(object):
# Prioritise non-PIE binaries so we can use _fini
exes = (elf for elf in self.elfs if not elf.library and elf.bits == 64)
- nonpie = csu = None
+ csu = None
for elf in exes:
- if not elf.pie:
- if '__libc_csu_init' in elf.symbols:
- break
- nonpie = elf
- elif '__libc_csu_init' in elf.symbols:
+ if '__libc_csu_init' in elf.symbols:
csu = elf
+ if not elf.pie:
+ break
+
+ if csu:
+ elf = csu
else:
log.error('No non-library binaries in [elfs]')
- if elf.pie:
- if nonpie:
- elf = nonpie
- elif csu:
- elf = csu
-
from .ret2csu import ret2csu
ret2csu(self, elf, edi, rsi, rdx, rbx, rbp, r12, r13, r14, r15, call)
diff --git a/pwnlib/shellcraft/templates/amd64/windows/__doc__ b/pwnlib/shellcraft/templates/amd64/windows/__doc__
new file mode 100644
index 0000000..8e8d7f8
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/__doc__
@@ -0,0 +1 @@
+Shellcraft module containing Intel x86_64 shellcodes for Windows.
diff --git a/pwnlib/shellcraft/templates/amd64/windows/cmd.asm b/pwnlib/shellcraft/templates/amd64/windows/cmd.asm
new file mode 100644
index 0000000..4d44995
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/cmd.asm
@@ -0,0 +1,9 @@
+<%
+ from pwnlib.shellcraft import amd64
+%>
+<%docstring>Execute cmd.exe and keep the parent process
+in an infinite loop.
+</%docstring>
+
+ ${amd64.windows.winexec(b'cmd.exe')}
+ ${amd64.infloop()}
diff --git a/pwnlib/shellcraft/templates/amd64/windows/getexport.asm b/pwnlib/shellcraft/templates/amd64/windows/getexport.asm
new file mode 100644
index 0000000..76f8543
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/getexport.asm
@@ -0,0 +1,55 @@
+<%
+ from pwnlib.shellcraft import amd64, 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.
+
+Args:
+ function_name (str): The name of the function to find.
+ 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'"/>
+<%
+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'
+%>
+ ${amd64.windows.kernel32base('rbx')} /* rbx = kernel32.dll PE base */
+ mov r8d, [rbx + 0x3c]
+ mov rdx, r8
+ add rdx, rbx
+ ${amd64.mov('r9', 0x88)}
+ 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
+% if len(function_name) <= 8:
+ mov r9, ${pretty(u64(function_name.ljust(8, b'\x00')))}
+% else:
+ ${amd64.pushstr(function_name)}
+ mov r9, rsp
+% endif
+
+ /* Loop through the names table */
+ FindFunction:
+ inc rcx
+ mov eax, [rsi + rcx * 4]
+ add rax, rbx
+ ## TODO: implement strcmp properly for function names > 8 bytes
+ cmp qword ptr [rax], r9
+ jnz FindFunction
+
+ mov esi, [r8 + 0x24]
+ add rsi, rbx /* rsi = ordinals table */
+ mov cx, [rsi + rcx * 2]
+ mov esi, [r8 + 0x1c]
+ add rsi, rbx /* rsi = address table */
+ mov eax, [rsi + rcx * 4]
+ add rax, rbx /* rax = function address */
+ mov ${dest}, rax
diff --git a/pwnlib/shellcraft/templates/amd64/windows/getprocaddress.asm b/pwnlib/shellcraft/templates/amd64/windows/getprocaddress.asm
new file mode 100644
index 0000000..6067a76
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/getprocaddress.asm
@@ -0,0 +1,28 @@
+<%
+ from pwnlib.shellcraft import amd64, pretty
+ from pwnlib.util.packing import _need_bytes
+ from pwnlib.util.misc import align
+%>
+<%docstring>Find the address of an exported function
+by calling kernel32::GetProcAddress.
+
+Args:
+ function_name(str): The name of the function to find.
+ 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'"/>
+<%
+function_name = _need_bytes(function_name)
+dll = _need_bytes(dll)
+assert dll == b'kernel32.dll'
+%>
+
+ ${amd64.windows.getexport(b'GetProcA', b'kernel32.dll', dest='rdi')}
+ ${amd64.pushstr(function_name)}
+ mov rdx, rsp
+ ${amd64.windows.kernel32base(dest='rcx')}
+ sub rsp, 0x30
+ call rdi
+ add rsp, ${pretty(0x30+align(8, len(function_name)))}
+ mov ${dest}, rax
diff --git a/pwnlib/shellcraft/templates/amd64/windows/kernel32base.asm b/pwnlib/shellcraft/templates/amd64/windows/kernel32base.asm
new file mode 100644
index 0000000..71d1201
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/kernel32base.asm
@@ -0,0 +1,18 @@
+<% from pwnlib.shellcraft import amd64 %>
+<%docstring>Find the base address of kernel32.dll in memory.
+
+Args:
+ dest (str): The register to load the kernel32.dll base address into.
+</%docstring>
+<%page args="dest='rax'"/>
+## The loaded list of modules always starts with
+## 1. the executable itself
+## 2. ntdll.dll
+## 3. kernel32.dll
+ ${amd64.windows.peb(dest)}
+ mov ${dest}, [${dest} + 0x18] /* PEB->Ldr */
+ mov rsi, [${dest} + 0x20] /* PEB->Ldr.InMemOrder LIST_ENTRY */
+ lodsq
+ xchg rax, rsi
+ lodsq
+ mov ${dest}, [rax + 0x20] /* LDR_DATA_TABLE_ENTRY->DllBase */ \ No newline at end of file
diff --git a/pwnlib/shellcraft/templates/amd64/windows/ntdllbase.asm b/pwnlib/shellcraft/templates/amd64/windows/ntdllbase.asm
new file mode 100644
index 0000000..454f295
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/ntdllbase.asm
@@ -0,0 +1,16 @@
+<% from pwnlib.shellcraft import amd64 %>
+<%docstring>Find the base address of ntdll.dll in memory.
+
+Args:
+ dest (str): The register to load the ntdll.dll base address into.
+</%docstring>
+<%page args="dest='rax'"/>
+## The loaded list of modules always starts with:
+## 1. the executable itself
+## 2. ntdll.dll
+## 3. kernel32.dll
+ ${amd64.windows.peb(dest)}
+ mov ${dest}, [${dest} + 0x18] /* PEB->Ldr */
+ mov rsi, [${dest} + 0x20] /* PEB->Ldr.InMemOrder LIST_ENTRY */
+ lodsq
+ mov ${dest}, [rax + 0x20] /* LDR_DATA_TABLE_ENTRY->DllBase */
diff --git a/pwnlib/shellcraft/templates/amd64/windows/peb.asm b/pwnlib/shellcraft/templates/amd64/windows/peb.asm
new file mode 100644
index 0000000..ee11e21
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/peb.asm
@@ -0,0 +1,8 @@
+<% from pwnlib.shellcraft import amd64 %>
+<%docstring>Loads the Process Environment Block (PEB) into the target register.
+
+Args:
+ dest (str): The register to load the PEB into.
+</%docstring>
+<%page args="dest='rax'"/>
+ ${amd64.windows.teb(dest, 0x60)}
diff --git a/pwnlib/shellcraft/templates/amd64/windows/teb.asm b/pwnlib/shellcraft/templates/amd64/windows/teb.asm
new file mode 100644
index 0000000..6ce201e
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/teb.asm
@@ -0,0 +1,21 @@
+<%
+ from pwnlib.log import getLogger
+ from pwnlib.shellcraft import pretty
+ from pwnlib.shellcraft.registers import get_register, is_register
+ log = getLogger('pwnlib.shellcraft.amd64.teb')
+%>
+<%page args="dest='rax', offset=0"/>
+<%docstring>Loads the Thread Environment Block (TEB) into the target register.
+
+Args:
+ dest (str): The register to load the TEB into.
+ offset (int): The offset from the TEB to load.
+</%docstring>
+<%
+if not is_register(dest):
+ log.error('%r is not a register' % dest)
+
+dest = get_register(dest)
+%>
+ xor esi, esi
+ mov ${dest}, qword ptr gs:[rsi+${pretty(offset)}]
diff --git a/pwnlib/shellcraft/templates/amd64/windows/winexec.asm b/pwnlib/shellcraft/templates/amd64/windows/winexec.asm
new file mode 100644
index 0000000..d680520
--- /dev/null
+++ b/pwnlib/shellcraft/templates/amd64/windows/winexec.asm
@@ -0,0 +1,21 @@
+<%
+ from pwnlib.shellcraft import amd64, pretty
+ from pwnlib.util.packing import _need_bytes
+ from pwnlib.util.misc import align
+%>
+<%docstring>Execute a program using WinExec.
+
+Args:
+ cmd (str): The program to execute.
+</%docstring>
+<%page args="cmd"/>
+<%
+cmd = _need_bytes(cmd)
+%>
+
+ ${amd64.windows.getprocaddress(b'WinExec', b'kernel32.dll', 'rsi')}
+ ${amd64.pushstr(cmd)}
+ mov rcx, rsp
+ sub rsp, 0x30
+ call rsi
+ add rsp, ${pretty(0x30+align(8, len(cmd)))}
diff --git a/pwnlib/term/readline.py b/pwnlib/term/readline.py
index 6891f12..b60b656 100644
--- a/pwnlib/term/readline.py
+++ b/pwnlib/term/readline.py
@@ -404,7 +404,7 @@ def readline(_size=-1, prompt='', float=True, priority=10):
buffer = (buffer_left + buffer_right)
if buffer:
history.insert(0, buffer)
- return force_to_bytes(buffer) + b'\n'
+ return force_to_bytes(buffer)
except KeyboardInterrupt:
control_c()
finally:
diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py
index cd06f34..adda422 100644
--- a/pwnlib/tubes/ssh.py
+++ b/pwnlib/tubes/ssh.py
@@ -57,7 +57,7 @@ class ssh_channel(sock):
#: Command specified for the constructor
process = None
- def __init__(self, parent, process = None, tty = False, wd = None, env = None, raw = True, *args, **kwargs):
+ def __init__(self, parent, process = None, tty = False, cwd = None, env = None, raw = True, *args, **kwargs):
super(ssh_channel, self).__init__(*args, **kwargs)
# keep the parent from being garbage collected in some cases
@@ -68,9 +68,9 @@ class ssh_channel(sock):
self.tty = tty
self.env = env
self.process = process
- self.cwd = wd or '.'
- if isinstance(wd, six.text_type):
- wd = packing._need_bytes(wd, 2, 0x80)
+ self.cwd = cwd or '.'
+ if isinstance(cwd, six.text_type):
+ cwd = packing._need_bytes(cwd, 2, 0x80)
env = env or {}
msg = 'Opening new channel: %r' % (process or 'shell')
@@ -80,8 +80,8 @@ class ssh_channel(sock):
if isinstance(process, six.text_type):
process = packing._need_bytes(process, 2, 0x80)
- if process and wd:
- process = b'cd ' + sh_string(wd) + b' >/dev/null 2>&1; ' + process
+ if process and cwd:
+ process = b'cd ' + sh_string(cwd) + b' >/dev/null 2>&1; ' + process
if process and env:
for name, value in env.items():
@@ -841,8 +841,11 @@ class ssh(Timeout, Logger):
>>> sh = s.process(executable='/bin/sh')
>>> str(sh.pid).encode() in s.pidof('sh') # doctest: +SKIP
True
- >>> s.process(['pwd'], cwd='/tmp').recvall()
+ >>> io = s.process(['pwd'], cwd='/tmp')
+ >>> io.recvall()
b'/tmp\n'
+ >>> io.cwd
+ '/tmp'
>>> p = s.process(['python','-c','import os; os.write(1, os.read(2, 1024))'], stderr=0)
>>> p.send(b'hello')
>>> p.recv()
@@ -1068,7 +1071,7 @@ os.execve(exe, argv, env)
script = 'echo PWNTOOLS; for py in python3 python2.7 python2 python; do test -x "$(which $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, raw=True, level=self.level, timeout=timeout)
+ python = ssh_process(self, script, tty=True, cwd=cwd, raw=True, level=self.level, timeout=timeout)
try:
python.recvline_contains(b'PWNTOOLS') # Magic flag so that any sh/bash initialization errors are swallowed
@@ -1151,14 +1154,19 @@ os.execve(exe, argv, env)
Examples:
>>> s = ssh(host='example.pwnme')
- >>> py = s.run('python -i')
+ >>> py = s.system('python3 -i')
>>> _ = py.recvuntil(b'>>> ')
>>> py.sendline(b'print(2+2)')
- >>> py.sendline(b'exit')
+ >>> py.sendline(b'exit()')
>>> print(repr(py.recvline()))
b'4\n'
>>> s.system('env | grep -a AAAA', env={'AAAA': b'\x90'}).recvall()
b'AAAA=\x90\n'
+ >>> io = s.system('pwd', wd='/tmp')
+ >>> io.recvall()
+ b'/tmp\n'
+ >>> io.cwd
+ '/tmp'
"""
if wd is None:
@@ -1377,17 +1385,10 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if status != 0:
return None
- # OpenSSL outputs in the format of...
- # (stdin)= e3b0c4429...
- data = data.replace(b'(stdin)= ',b'')
-
- # sha256 and sha256sum outputs in the format of...
- # e3b0c442... -
- data = data.replace(b'-',b'').strip()
-
if not isinstance(data, str):
data = data.decode('ascii')
+ data = re.search("([a-fA-F0-9]{64})",data).group()
return data
def _get_cachefile(self, fingerprint):
@@ -1474,7 +1475,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
self._download_raw(remote, local, p)
if not self._verify_local_fingerprint(fingerprint):
- p.error('Could not download file %r' % remote)
+ self.error('Could not download file %r', remote)
return local
@@ -1509,17 +1510,25 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
calling the function twice has little overhead.
Arguments:
- remote(str): The remote filename to download
+ remote(str/bytes): The remote filename to download
local(str): The local filename to save it to. Default is to infer it from the remote filename.
+
+ Examples:
+ >>> with open('/tmp/foobar','w+') as f:
+ ... _ = f.write('Hello, world')
+ >>> s = ssh(host='example.pwnme',
+ ... cache=False)
+ >>> _ = s.set_working_directory(wd='/tmp')
+ >>> _ = s.download_file('foobar', 'barfoo')
+ >>> with open('barfoo','r') as f:
+ ... print(f.read())
+ Hello, world
"""
if not local:
local = os.path.basename(os.path.normpath(remote))
- if os.path.basename(remote) == remote:
- remote = os.path.join(self.cwd, remote)
-
with self.progress('Downloading %r to %r' % (remote, local)) as p:
local_tmp = self._download_to_cache(remote, p)
@@ -1527,7 +1536,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if not os.path.exists(local) or hashes.sha256filehex(local_tmp) != hashes.sha256filehex(local):
shutil.copy2(local_tmp, local)
- def download_dir(self, remote=None, local=None):
+ def download_dir(self, remote=None, local=None, ignore_failed_read=False):
"""Recursively downloads a directory from the remote server
Arguments:
@@ -1540,17 +1549,22 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
remote = packing._encode(self.sftp.normalize(remote))
else:
with context.local(log_level='error'):
- remote = self.system(b'readlink -f ' + sh_string(remote))
+ remote = self.system(b'readlink -f ' + sh_string(remote)).recvall().strip()
local = local or '.'
local = os.path.expanduser(local)
self.info("Downloading %r to %r" % (remote, local))
+ if ignore_failed_read:
+ opts = b" --ignore-failed-read"
+ else:
+ opts = b""
with context.local(log_level='error'):
remote_tar = self.mktemp()
- cmd = b'tar -C %s -czf %s .' % \
- (sh_string(remote),
+ cmd = b'tar %s -C %s -czf %s .' % \
+ (opts,
+ sh_string(remote),
sh_string(remote_tar))
tar = self.system(cmd)
@@ -1560,6 +1574,11 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
local_tar = tempfile.NamedTemporaryFile(suffix='.tar.gz')
self.download_file(remote_tar, local_tar.name)
+ # Delete temporary tarfile from remote host
+ if self.sftp:
+ self.unlink(remote_tar)
+ else:
+ self.system(b'rm ' + sh_string(remote_tar)).wait()
tar = tarfile.open(local_tar.name)
tar.extractall(local)
@@ -1691,6 +1710,18 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
file_or_directory(str): Path to the file or directory to download.
local(str): Local path to store the data.
By default, uses the current directory.
+
+
+ Examples:
+ >>> with open('/tmp/foobar','w+') as f:
+ ... _ = f.write('Hello, world')
+ >>> s = ssh(host='example.pwnme',
+ ... cache=False)
+ >>> _ = s.set_working_directory('/tmp')
+ >>> _ = s.download('foobar', 'barfoo')
+ >>> with open('barfoo','r') as f:
+ ... print(f.read())
+ Hello, world
"""
file_or_directory = packing._encode(file_or_directory)
with self.system(b'test -d ' + sh_string(file_or_directory)) as io:
@@ -1766,7 +1797,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
if self.cwd != '.':
cmd = 'cd ' + sh_string(self.cwd)
- s.sendline(cmd)
+ s.sendline(packing._need_bytes(cmd, 2, 0x80))
s.interactive()
s.close()
@@ -1824,6 +1855,13 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
>>> _=s.set_working_directory(symlink=symlink)
>>> assert b'foo' in s.ls().split(), s.ls().split()
>>> assert homedir != s.pwd()
+
+ >>> _=s.set_working_directory()
+ >>> io = s.system('pwd')
+ >>> io.recvallS().strip() == io.cwd
+ True
+ >>> io.cwd == s.cwd
+ True
"""
status = 0
diff --git a/pwnlib/tubes/tube.py b/pwnlib/tubes/tube.py
index 91ca4f2..39a27d8 100644
--- a/pwnlib/tubes/tube.py
+++ b/pwnlib/tubes/tube.py
@@ -4,6 +4,7 @@ from __future__ import division
import abc
import logging
+import os
import re
import six
import string
@@ -745,9 +746,9 @@ class tube(Timeout, Logger):
return self.buffer.get()
def recvall(self, timeout=Timeout.forever):
- """recvall() -> bytes
+ """recvall(timeout=Timeout.forever) -> bytes
- Receives data until EOF is reached.
+ Receives data until EOF is reached and closes the tube.
"""
with self.waitfor('Receiving all data') as h:
@@ -867,7 +868,7 @@ class tube(Timeout, Logger):
is much more usable, since we are using :mod:`pwnlib.term` to print a
floating prompt.
- Thus it only works in while in :data:`pwnlib.term.term_mode`.
+ Thus it only works while in :data:`pwnlib.term.term_mode`.
"""
self.info('Switching to interactive mode')
@@ -892,13 +893,44 @@ class tube(Timeout, Logger):
t.daemon = True
t.start()
+ from pwnlib.args import term_mode
try:
+ os_linesep = os.linesep.encode()
+ to_skip = b''
while not go.isSet():
if term.term_mode:
data = term.readline.readline(prompt = prompt, float = True)
+ if data:
+ data += self.newline
else:
stdin = getattr(sys.stdin, 'buffer', sys.stdin)
data = stdin.read(1)
+ # Keep OS's line separator if NOTERM is set and
+ # the user did not specify a custom newline
+ # even if stdin is a tty.
+ if sys.stdin.isatty() and (
+ term_mode
+ or context.newline != b"\n"
+ or self._newline is not None
+ ):
+ if to_skip:
+ if to_skip[:1] != data:
+ data = os_linesep[: -len(to_skip)] + data
+ else:
+ to_skip = to_skip[1:]
+ if to_skip:
+ continue
+ data = self.newline
+ # If we observe a prefix of the line separator in a tty,
+ # assume we'll see the rest of it immediately after.
+ # This could stall until the next character is seen if
+ # the line separator is started but never finished, but
+ # that is unlikely to happen in a dynamic tty.
+ elif data and os_linesep.startswith(data):
+ if len(os_linesep) > 1:
+ to_skip = os_linesep[1:]
+ continue
+ data = self.newline
if data:
try:
@@ -1329,7 +1361,7 @@ class tube(Timeout, Logger):
Should not be called directly. Sends data to the tube.
Should return ``exceptions.EOFError``, if it is unable to send any
- more, because of a close tube.
+ more, because of a closed tube.
"""
raise EOFError('Not implemented')
@@ -1345,9 +1377,8 @@ class tube(Timeout, Logger):
def timeout_change(self):
"""
- Informs the raw layer of the tube that the timeout has changed.
+ Should not be called directly. Informs the raw layer of the tube that the timeout has changed.
- Should not be called directly.
Inherited from :class:`Timeout`.
"""
diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py
index 1565116..15dcddc 100644
--- a/pwnlib/util/packing.py
+++ b/pwnlib/util/packing.py
@@ -168,7 +168,7 @@ def pack(number, word_size = None, endianness = None, sign = None, **kwargs):
def unpack(data, word_size = None):
r"""unpack(data, word_size = None, endianness = None, sign = None, **kwargs) -> int
- Packs arbitrary-sized integer.
+ Unpacks arbitrary-sized integer.
Word-size, endianness and signedness is done according to context.
@@ -239,7 +239,7 @@ def unpack(data, word_size = None):
@LocalNoarchContext
def unpack_many(data, word_size = None):
- """unpack(data, word_size = None, endianness = None, sign = None) -> int list
+ """unpack_many(data, word_size = None, endianness = None, sign = None) -> int list
Splits `data` into groups of ``word_size//8`` bytes and calls :func:`unpack` on each group. Returns a list of the results.
@@ -1065,6 +1065,9 @@ def _encode(s):
return s.encode(context.encoding)
def _decode(b):
+ if isinstance(b, (str, six.text_type)):
+ return b # already text
+
if context.encoding == 'auto':
try:
return b.decode('utf-8')
diff --git a/pwnlib/version.py b/pwnlib/version.py
index 8e85738..6c387e4 100644
--- a/pwnlib/version.py
+++ b/pwnlib/version.py
@@ -1 +1 @@
-__version__ = '4.10.0'
+__version__ = '4.11.0'
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..78d315e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,71 @@
+[build-system]
+requires = ["setuptools>=44.0", "wheel", "toml; python_version<'3.4'"]
+build-backend = "setuptools.build_meta"
+
+[project]
+dynamic = ["version", "scripts"]
+
+name = "pwntools"
+description = "Pwntools CTF framework and exploit development library."
+license = {text = "Mostly MIT, some GPL/BSD, see LICENSE-pwntools.txt"}
+readme = "README.md"
+authors = [{name = "Gallopsled et al.", email = "pwntools-users@googlegroups.com"}]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Console",
+ "Intended Audience :: Developers",
+ "Intended Audience :: Science/Research",
+ "Intended Audience :: System Administrators",
+ "License :: OSI Approved :: MIT License",
+ "Natural Language :: English",
+ "Operating System :: POSIX :: Linux",
+ "Programming Language :: Python :: 2.7",
+ "Programming Language :: Python :: 3",
+ "Topic :: Security",
+ "Topic :: Software Development :: Assemblers",
+ "Topic :: Software Development :: Debuggers",
+ "Topic :: Software Development :: Disassemblers",
+ "Topic :: Software Development :: Embedded Systems",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: System :: System Shells",
+ "Topic :: Utilities",
+]
+keywords = ["pwntools", "exploit", "ctf", "capture", "the", "flag", "binary", "wargame", "overflow", "stack", "heap", "defcon"]
+
+requires-python = ">=2.7"
+dependencies = [
+ "paramiko>=1.15.2",
+ "mako>=1.0.0",
+ "pyelftools>=0.24, <0.30; python_version < '3'",
+ "pyelftools>=0.24; python_version >= '3'",
+ "capstone>=3.0.5rc2", # see Gallopsled/pwntools#971, Gallopsled/pwntools#1160
+ "ropgadget>=5.3",
+ "pyserial>=2.7",
+ "requests>=2.0",
+ "pip>=6.0.8",
+ "pygments>=2.0",
+ "pysocks",
+ "python-dateutil",
+ "packaging",
+ "psutil>=3.3.0",
+ "intervaltree>=3.0",
+ "sortedcontainers",
+ "unicorn>=1.0.2rc1", # see unicorn-engine/unicorn#1100 and #1170
+ "six>=1.12.0",
+ "rpyc",
+ "colored_traceback",
+ "pathlib2; python_version < '3.4'",
+]
+
+[project.urls]
+homepage = "https://pwntools.com"
+download = "https://github.com/Gallopsled/pwntools/releases"
+
+[tool.distutils.bdist_wheel]
+universal = 1
+
+[tool.setuptools]
+include-package-data = false
+
+[tool.setuptools.packages.find]
+namespaces = false
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 49d4929..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-# This requirements.txt file simply hits the setup.py file and
-# installs the 'install_requires' python modules, the magic command
-# to do that is simply '-e .': so here it is
-
-# Amazing command
--e .
diff --git a/setup.py b/setup.py
index 8239ac3..e6bb612 100755
--- a/setup.py
+++ b/setup.py
@@ -11,7 +11,6 @@ from distutils.command.install import INSTALL_SCHEMES
from distutils.sysconfig import get_python_inc
from distutils.util import convert_path
-from setuptools import find_packages
from setuptools import setup
# Get all template files
@@ -45,30 +44,15 @@ for filename in glob.glob('pwnlib/commandline/*'):
if not flag:
console_scripts.append(script)
-install_requires = ['paramiko>=1.15.2',
- 'mako>=1.0.0',
- 'pyelftools>=0.2.4',
- 'capstone>=3.0.5rc2', # See Gallopsled/pwntools#971, Gallopsled/pwntools#1160
- 'ropgadget>=5.3',
- 'pyserial>=2.7',
- 'requests>=2.0',
- 'pip>=6.0.8',
- 'pygments>=2.0',
- 'pysocks',
- 'python-dateutil',
- 'packaging',
- 'psutil>=3.3.0',
- 'intervaltree>=3.0',
- 'sortedcontainers',
- # see unicorn-engine/unicorn#1100 and #1170
- 'unicorn>=1.0.2rc1',
- 'six>=1.12.0',
- 'rpyc',
- 'colored_traceback',
-]
+compat = {}
+if sys.version_info < (3, 4):
+ import toml
+ project = toml.load('pyproject.toml')['project']
+ compat['install_requires'] = project['dependencies']
+ compat['name'] = project['name']
+ if '--user' in sys.argv:
+ sys.argv.remove('--user')
-if platform.python_version_tuple()[0] == '2':
- install_requires += ['pathlib2']
# Check that the user has installed the Python development headers
PythonH = os.path.join(get_python_inc(), 'Python.h')
@@ -77,19 +61,8 @@ if not os.path.exists(PythonH):
print("$ apt-get install python-dev", file=sys.stderr)
sys.exit(-1)
-# Convert README.md to reStructuredText for PyPI
-long_description = ''
-try:
- long_description = subprocess.check_output(['pandoc', 'README.md', '--to=rst'], universal_newlines=True)
-except Exception as e:
- print("Failed to convert README.md through pandoc, proceeding anyway", file=sys.stderr)
- traceback.print_exc()
-
setup(
- name = 'pwntools',
- python_requires = '>=2.7',
- packages = find_packages(),
- version = '4.10.0',
+ version = '4.11.0',
data_files = [('pwntools-doc',
glob.glob('*.md') + glob.glob('*.txt')),
],
@@ -105,33 +78,5 @@ setup(
},
entry_points = {'console_scripts': console_scripts},
scripts = glob.glob("bin/*"),
- description = "Pwntools CTF framework and exploit development library.",
- long_description = long_description,
- author = "Gallopsled et al.",
- author_email = "pwntools-users@googlegroups.com",
- url = 'https://pwntools.com',
- download_url = "https://github.com/Gallopsled/pwntools/releases",
- install_requires = install_requires,
- license = "Mostly MIT, some GPL/BSD, see LICENSE-pwntools.txt",
- keywords = 'pwntools exploit ctf capture the flag binary wargame overflow stack heap defcon',
- classifiers = [
- 'Development Status :: 5 - Production/Stable',
- 'Environment :: Console',
- 'Intended Audience :: Developers',
- 'Intended Audience :: Science/Research',
- 'Intended Audience :: System Administrators',
- 'License :: OSI Approved :: MIT License',
- 'Natural Language :: English',
- 'Operating System :: POSIX :: Linux',
- 'Programming Language :: Python :: 2.7',
- 'Programming Language :: Python :: 3',
- 'Topic :: Security',
- 'Topic :: Software Development :: Assemblers',
- 'Topic :: Software Development :: Debuggers',
- 'Topic :: Software Development :: Disassemblers',
- 'Topic :: Software Development :: Embedded Systems',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- 'Topic :: System :: System Shells',
- 'Topic :: Utilities',
- ]
+ **compat
)
diff --git a/travis/setup_avd_fast.sh b/travis/setup_avd_fast.sh
new file mode 100644
index 0000000..577ddc2
--- /dev/null
+++ b/travis/setup_avd_fast.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+set -ex
+
+# Grab prerequisites
+# Valid ABIs:
+# - armeabi-v7a
+# - arm64-v8a
+# - x86
+# - x86_64
+ANDROID_ABI='armeabi-v7a'
+ANDROIDV=android-24
+
+# Create our emulator Android Virtual Device (AVD)
+# --snapshot flag is deprecated, see bitrise-steplib/steps-create-android-emulator#18
+export PATH=$PATH:"$ANDROID_HOME"/cmdline-tools/latest/bin:"$ANDROID_HOME"/platform-tools
+yes | sdkmanager --sdk_root="$ANDROID_HOME" --install "system-images;$ANDROIDV;default;$ANDROID_ABI"
+yes | sdkmanager --sdk_root="$ANDROID_HOME" --licenses
+echo no | avdmanager --silent create avd --name android-$ANDROID_ABI --force --package "system-images;$ANDROIDV;default;$ANDROID_ABI"
+
+"$ANDROID_HOME"/emulator/emulator -avd android-$ANDROID_ABI -no-window -no-boot-anim -read-only -no-audio -no-window -no-snapshot &
+adb wait-for-device
+adb shell id
+adb shell getprop