Compare commits

...
31 Commits
Author SHA1 Message Date
Arusekk 493a3e3d92 Release 4.8.0 2022-04-21 01:05:15 +02:00
Arusekk f491bc6a22 Merge branch 'stable' into beta 2022-04-20 15:46:34 +02:00
Arusekk cde1b89615 Release 4.7.1 2022-04-20 15:37:20 +02:00
zup 17fc0aeaad fix double decoding of bytestring when logging (#2051) 2022-04-20 13:20:40 +00:00
maybe-sybr c93bdfeed8 fix: Fallback to temp dir if cache does not exist (#1784)
This fixes a misbehaviour in environments where `XDG_CACHE_DIR` is not
set and `~/.cache` does not exist, e.g. in the docker image. We simply
attempt to create a temporary directory which will be registered with
`atexit` to be deleted on exit using `shutil.rmtree()`. A Python 3
approach for future would be to use a `TemporaryDirectory` instead which
would get cleaned up when the held object gets dropped out of the TLS
stack.

It is still possible for the `context.cache_dir` to be `None` if the
default directory exists but is not writable, which means some code
which uses this property is liable to blow up in such a circumstance.
2022-04-20 12:09:41 +00:00
TestzeroandT3stzer0 4fd08b0b00 fix packing error (#2040)
Co-authored-by: T3stzer0 <[email protected]>
2022-04-20 12:00:35 +00:00
Spencer Pogorzelski 8dd566b9e9 Fix env command not found in gdb.debug (#2007) 2022-04-20 11:47:57 +00:00
Sebastiano MarianiandArusekk fca2929de0 Fix download_dir method when using an ssh tube (#1961)
Signed-off-by: Sebastiano Mariani <[email protected]>
Co-authored-by: Arusekk <[email protected]>
2022-04-20 10:51:21 +00:00
lucasg d67bdbc550 [asm] pwn.disam can return <text+0x0> instead of <text> (#1912)
Unit test:

```
(pwntools_mips_bug) lucasg@home:/tmp/pwntools$ git show
commit 78c6b8c2a9 (HEAD -> dev, origin/dev, origin/HEAD)
Merge: e3a5ac58 d7139997
Author: heapcrash <[email protected]>
Date:   Mon May 31 16:06:34 2021 -0500

    Merge branch 'beta' into dev

(pwntools_mips_bug) lucasg@home:/tmp/pwntools$ python
Python 3.7.3 (default, Jan 22 2021, 20:04:44)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pwn, binascii
>>> print(pwn.disasm(binascii.unhexlify("000000000d0000000000000000"), vma=0x00000000, arch="mips"))
   0:   00000000        nop
   4:   0000000d        break
   8:   00000000        nop
        ...
>>> print(pwn.disasm(binascii.unhexlify("000000000d0000000000000000"), vma=0x80000000, arch="mips"))
[ERROR] Could not find .text in objdump output:

    /tmp/pwn-disasm-77tu42pr/step2:     file format elf32-tradlittlemips

    Disassembly of section .text:

    80000000 <.text+0x0>:
    80000000:   00000000        nop
    80000004:   0000000d        break
    80000008:   00000000        nop
        ...
[ERROR] An error occurred while disassembling:
    b'\x00\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x00'
    Traceback (most recent call last):
      File "/tmp/pwntools/pwnlib/asm.py", line 814, in disasm
        log.error('Could not find .text in objdump output:\n%s' % output0)
      File "/tmp/pwntools/pwnlib/log.py", line 424, in error
        raise PwnlibException(message % args)
    pwnlib.exception.PwnlibException: Could not find .text in objdump output:

    /tmp/pwn-disasm-77tu42pr/step2:     file format elf32-tradlittlemips

    Disassembly of section .text:

    80000000 <.text+0x0>:
    80000000:   00000000        nop
    80000004:   0000000d        break
    80000008:   00000000        nop
        ...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/pwntools/pwnlib/context/__init__.py", line 1524, in setter
    return function(*a, **kw)
  File "/tmp/pwntools/pwnlib/asm.py", line 818, in disasm
    log.exception("An error occurred while disassembling:\n%s" % data)
  File "/tmp/pwntools/pwnlib/asm.py", line 814, in disasm
    log.error('Could not find .text in objdump output:\n%s' % output0)
  File "/tmp/pwntools/pwnlib/log.py", line 424, in error
    raise PwnlibException(message % args)
pwnlib.exception.PwnlibException: Could not find .text in objdump output:

/tmp/pwn-disasm-77tu42pr/step2:     file format elf32-tradlittlemips

Disassembly of section .text:

80000000 <.text+0x0>:
80000000:       00000000        nop
80000004:       0000000d        break
80000008:       00000000        nop
        ...
```
2022-04-20 10:25:58 +00:00
Alan YeeandArusekk c3894c24de Make ELF symbol dict accept bytes (#1973)
DynElf symbol lookup uses bytes as key

Fixes #1951

Co-authored-by: Arusekk <[email protected]>
2022-04-20 09:48:13 +00:00
Arusekk 208b11df69 Avoid name clash of plain and android-related test 2022-04-20 11:14:45 +02:00
Will 0a860bd964 Fix incorrect disasm output (#2031)
Use objdump --wide option to ensure we don't have new lines in large instructions (>7 bytes).
2022-04-20 08:58:08 +00:00
0x3c3e 15efc3f1dc Cast int target pid to str (#2012) 2022-04-20 08:24:06 +00:00
gogo2464 2d0f03b66c Update Sphinx in order to fix jinja2 deprecated dependency. (#2053) 2022-04-20 08:44:02 +02:00
peace-maker 2198b74df9 Fix CI after Groovy Gorilla went away for libc unstrip test (#2025)
* Fix CI after Groovy Gorilla went away for libc unstrip test

Build elfutils 0.181 from source since we can't use builds
from a newer ubuntu version anymore.

* Install python wheels in CI
2022-02-01 11:07:39 +01:00
Arusekk 9d8060496c Release 4.8.0beta0 2021-11-15 12:44:33 +01:00
Arusekk 7ea1f1d492 Merge branch 'beta' into dev 2021-11-15 12:25:01 +01:00
Arusekk da8bf2667d Fix CI failures (#1997)
Ubuntu, why are you doing this to us?
I seriously mean that Ubuntu is the most silly distro.

Like, they probably updated their docker (or VM) images last week,
then Github got it, and we started getting failures.
They totally changed the behaviour of that 'apport' thing,
and it now drops core files in /var/lib/apport/coredump
instead of the current working directory.

Why is that even... ugh.  Nevermind.  It works now, at least.
But for how long?
2021-11-15 12:14:18 +01:00
Arusekk a37bce2bdc Display nice error message on unavailable ret2csu (#1991)
Fixes #1941
2021-11-14 15:20:59 +01:00
Arusekk 3dd0b24cc3 Add cat2 to shellcraft (#1995)
* Introduce shellcraft.linux.cat2 to avoid sendfile

Fixes #1871

* Switch CAT_PROC_MAPS_EXIT to shellcraft.linux.cat2
2021-11-14 15:20:43 +01:00
Arusekk a8c8c28f00 Fix CI failures (#1994)
* Try to fix build failures by updating sphinx

See sphinx-doc/sphinx#9811

* Does not work with py2, so pin old docutils
2021-11-11 17:53:45 +01:00
Justin Steven 315a462603 Make cyclic_find work with large int values (#1981)
* Make cyclic_find work with large int values

* Add entry for cyclic_find() large int fix

* Tweak cyclic_find() large int warning
2021-11-11 14:59:05 +01:00
ElJeffeandArusekk ddeddd455e Fix: TypeError: can't concat str to bytes (#1953)
* Fix: TypeError: can't concat str to bytes

sh_string returns a string, but two lines try to concat it with bytes, which throws a TypeError.  Either convert them all to bytes or all to string.

* Update to use packing._need_bytes

I just followed the format from surrounding conversions.  Hopefully that works correctly.

* Update pwnlib/tubes/ssh.py

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

* Conversion to bytes

* Going for higher level conversion

Co-authored-by: Arusekk <[email protected]>
2021-11-01 00:35:10 +01:00
Alan Yee d3140ed983 Using native features of built-in functions over built-in operations config.py (#1980)
Use lazy logging and use second argument of `.get()` instead of relying on `or`
2021-11-01 00:33:32 +01:00
Arusekk fc250a33a1 Add missing copyright notice to ARM alphanumeric (#1955)
Closes #1954
2021-10-07 09:33:34 +02:00
peace-makerandArusekk 92911cbabd libcdb: Load debug info and unstrip libc binary (#1828)
* libcdb: Load debug info and unstrip libc binary

The debug info is obtained from a debuginfod server maintained by systemtap.org. elfutils is used to unstrip the binary and merge the debug info back.

Try to unstrip every libc binary downloaded by the libcdb module by default. #1818

* Install elfutils on CI

* Update CHANGELOG.md

* Add more tests

* Fix negative test

* Raise an exception when eu-unstrip fails

This will hopefully get logged during doctests.

* Install elfutils 0.181 from groovy

Use APT Pinning to select the newer elfutils version from groovy instead of the buggy 0.176 version available in focal.

* Fix missing import of requests

pylint?

Co-authored-by: Arusekk <[email protected]>
2021-10-03 10:51:30 +02:00
Alan Yee a9715d8c74 Use lazy logging instead of native string interpolation (#1972)
https://docs.python.org/3/howto/logging.html#logging-variable-data
2021-10-01 07:48:57 +02:00
Darin Mao bd12d1874f Fix KB, MB, GB globals (#1926) 2021-07-29 19:17:17 +02:00
clubby789 11b8537d17 Fix validation of log level name for Python 3 (#1939)
* Fix validation of log level name for Python 3

* Add changelog

* Reorder changelog and use try/except
2021-07-29 19:16:36 +02:00
Connor NelsonandArusekk f7283bf2eb Fix logic in wait_for_debugger (#1922)
* Fix logic in `wait_for_debugger`

* Update CHANGELOG.md

* Fix CHANGELOG.md

* Fix CHANGELOG.md

Co-authored-by: Arusekk <[email protected]>
2021-07-15 11:42:42 +02:00
Arusekk d5d7b8385c Begin working on 4.8.0 2021-07-12 23:06:17 +02:00
27 changed files with 396 additions and 108 deletions

No files matched your search

+1 -1
View File
@@ -3,7 +3,7 @@ name: Android Tests
on: [push, pull_request]
jobs:
test:
android-test:
strategy:
matrix:
python-version: [3.8]
+26
View File
@@ -60,6 +60,31 @@ jobs:
binutils-sparc64-linux-gnu \
gcc-multilib \
libc6-dbg
- name: Cache for elfutils
uses: actions/cache@v1
id: cache-elfutils
with:
path: ~/.cache/elfutils
key: ${{ matrix.os }}-cache-elfutils
# Install newer elfutils version due to regression in 0.176 available in focal.
- name: Install newer elfutils
env:
ELFUTILS_VERSION: 0.181
run: |
if [[ ! -d ~/.cache/elfutils/elfutils-${ELFUTILS_VERSION} ]]
then
wget -O /tmp/elfutils-${ELFUTILS_VERSION}.tar.bz2 https://sourceware.org/elfutils/ftp/${ELFUTILS_VERSION}/elfutils-${ELFUTILS_VERSION}.tar.bz2
mkdir -p ~/.cache/elfutils && cd ~/.cache/elfutils
tar -xf /tmp/elfutils-${ELFUTILS_VERSION}.tar.bz2
cd elfutils-${ELFUTILS_VERSION}
LDFLAGS=-Wl,-rpath=/usr/local/lib,--enable-new-dtags ./configure --disable-libdebuginfod --disable-debuginfod && make && sudo make install
else
cd ~/.cache/elfutils/elfutils-${ELFUTILS_VERSION}
sudo make install
fi
eu-unstrip --version
- name: Install RPyC for GDB
run: |
@@ -81,6 +106,7 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade pip
pip install --upgrade wheel
pip install --upgrade flake8 appdirs
python setup.py egg_info
pip install --upgrade --editable .
+1
View File
@@ -25,6 +25,7 @@ addons:
- binutils-sparc64-linux-gnu
- bash-static
- python3-pip
- elfutils
cache:
- pip
- directories:
+45 -6
View File
@@ -9,9 +9,11 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [4.9.0](#490) | `dev` |
| [4.8.0](#480) | `beta` |
| [4.7.0](#470) | `stable` | Nov 15, 2021
| [4.10.0](#4100) | `dev` |
| [4.9.0](#490) | `beta` |
| [4.8.0](#480) | `stable` | Apr 21, 2022
| [4.7.1](#471) | | Apr 20, 2022
| [4.7.0](#470) | | Nov 15, 2021
| [4.6.0](#460) | | Jul 12, 2021
| [4.5.1](#451) | | May 30, 2021
| [4.5.0](#450) | | Apr 30, 2021
@@ -62,11 +64,31 @@ 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.9.0 (`dev`)
## 4.10.0 (`dev`)
## 4.8.0 (`beta`)
## 4.9.0 (`beta`)
- [#1975][1975] Add libcdb commandline tool
- [#1979][1979] Add `js_escape()` and `js_unescape()` to `util.fiddling`
- [#2011][2011] Fix tube's debug output of same byte compression
- [#2023][2023] Support KDE Konsole in run_in_new_terminal function
- [#2027][2027] Fix ELF.libc_start_main_return with glibc 2.34
- [#2033][2033] Quote file and core path in generated GDB script
- [#2035][2035] Change Buffer's parent class to object
- [#2037][2037] Allow SSH tunnel to be treated like a TCP socket (with 'raw=True')
[1975]: https://github.com/Gallopsled/pwntools/pull/1975
[1979]: https://github.com/Gallopsled/pwntools/pull/1979
[2011]: https://github.com/Gallopsled/pwntools/pull/2011
[2023]: https://github.com/Gallopsled/pwntools/pull/2023
[2027]: https://github.com/Gallopsled/pwntools/pull/2027
[2033]: https://github.com/Gallopsled/pwntools/pull/2033
[2035]: https://github.com/Gallopsled/pwntools/pull/2035
[2037]: https://github.com/Gallopsled/pwntools/pull/2037
## 4.8.0 (`stable`)
- [#1922][1922] Fix logic in `wait_for_debugger`
- [#1828][1828] libcdb: Load debug info and unstrip libc binary
@@ -78,7 +100,24 @@ The table below shows which release corresponds to each branch, and what date th
[1939]: https://github.com/Gallopsled/pwntools/pull/1939
[1981]: https://github.com/Gallopsled/pwntools/pull/1981
## 4.7.0 (`stable`)
## 4.7.1
- [#1784][1784] Use temporary cache directory when persistent cache cannot be used
- [#1973][1973] ELF symbols can be looked up by bytes values
- several bugfixes ([#2012][2012], [#2031][2031], [#1912][1912], [#1961][1961],
[#2007][2007], [#2040][2040], [#2051][2051])
[1784]: https://github.com/Gallopsled/pwntools/pull/1784
[1912]: https://github.com/Gallopsled/pwntools/pull/1912
[1961]: https://github.com/Gallopsled/pwntools/pull/1961
[1973]: https://github.com/Gallopsled/pwntools/pull/1973
[2007]: https://github.com/Gallopsled/pwntools/pull/2007
[2012]: https://github.com/Gallopsled/pwntools/pull/2012
[2031]: https://github.com/Gallopsled/pwntools/pull/2031
[2040]: https://github.com/Gallopsled/pwntools/pull/2040
[2051]: https://github.com/Gallopsled/pwntools/pull/2051
## 4.7.0
- [#1733][1733] Update libc headers -> more syscalls available!
- [#1876][1876] add `self.message` and change `sys.exc_type` to `sys.exec_info()` in PwnlibException
+2 -1
View File
@@ -12,7 +12,8 @@ pypandoc
pyserial>=2.7
requests>=2.5.1
ropgadget>=5.3
sphinx<3.4
sphinx==1.8.6; python_version < '3.0.0'
sphinx==4.5.0; python_version >= '3.0.0'
sphinx_rtd_theme
sphinxcontrib-napoleon
sphinxcontrib-autoprogram<=0.1.5
+1
View File
@@ -16,6 +16,7 @@ RUN apt-get update \
&& locale-gen en_US.UTF-8 \
&& apt-get install -y \
build-essential \
elfutils \
git \
libssl-dev \
libffi-dev \
+7 -2
View File
@@ -779,6 +779,11 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
0: 60 00 00 00 nop
>>> print(disasm(unhex('00000000'), arch='mips64'))
0: 00000000 nop
>>> print(disasm(unhex('48b84141414141414100c3'), arch='amd64'))
0: 48 b8 41 41 41 41 41 41 41 00 movabs rax, 0x41414141414141
a: c3 ret
>>> print(disasm(unhex('00000000'), vma=0x80000000, arch='mips'))
80000000: 00000000 nop
"""
result = ''
@@ -790,7 +795,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
bfdarch = _bfdarch()
bfdname = _bfdname()
objdump = _objdump() + ['-d', '--adjust-vma', str(vma), '-b', bfdname]
objdump = _objdump() + ['-w', '-d', '--adjust-vma', str(vma), '-b', bfdname]
objcopy = _objcopy() + [
'-I', 'binary',
'-O', bfdname,
@@ -815,7 +820,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
_run(objcopy + [step1, step2])
output0 = _run(objdump + [step2])
output1 = output0.split('<.text>:\n')
output1 = re.split(r'<\.text(?:\+0x0)?>:\n', output0, flags=re.MULTILINE)
if len(output1) != 2:
log.error('Could not find .text in objdump output:\n%s' % output0)
+3 -3
View File
@@ -55,8 +55,8 @@ def initialize():
from pwnlib.log import getLogger
log = getLogger(__name__)
xdg_config_home = (os.environ.get('XDG_CONFIG_HOME') or
os.path.expanduser("~/.config"))
xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
os.path.expanduser("~/.config"))
c = configparser.ConfigParser()
c.read(['/etc/pwn.conf',
@@ -65,7 +65,7 @@ def initialize():
for section in c.sections():
if section not in registered_configs:
log.warn("Unknown configuration section %r" % section)
log.warn("Unknown configuration section %r", section)
continue
settings = dict(c.items(section))
registered_configs[section](settings)
+72 -20
View File
@@ -6,17 +6,20 @@ contexts work properly and as expected.
from __future__ import absolute_import
from __future__ import division
import atexit
import collections
import errno
import functools
import logging
import os
import os.path
import platform
import shutil
import six
import socket
import stat
import string
import subprocess
import sys
import tempfile
import threading
import time
@@ -345,6 +348,10 @@ class ContextType(object):
'binary': None,
'bits': 32,
'buffer_size': 4096,
'cache_dir_base': os.environ.get(
'XDG_CACHE_HOME',
os.path.join(os.path.expanduser('~'), '.cache')
),
'cyclic_alphabet': string.ascii_lowercase.encode(),
'cyclic_size': 4,
'delete_corefiles': False,
@@ -972,7 +979,10 @@ class ContextType(object):
except AttributeError: pass
# Otherwise, fail
level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
try:
level_names = logging._levelToName.values()
except AttributeError:
level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
permitted = sorted(level_names)
raise AttributeError('log_level must be an integer or one of %r' % permitted)
@@ -1269,6 +1279,21 @@ class ContextType(object):
"""
return int(size)
@_validator
def cache_dir_base(self, new_base):
"""Base directory to use for caching content.
Changing this to a different value will clear the `cache_dir` path
stored in TLS since a new path will need to be generated to respect the
new `cache_dir_base` value.
"""
if new_base != self.cache_dir_base:
del self._tls["cache_dir"]
if os.access(new_base, os.F_OK) and not os.access(new_base, os.W_OK):
raise OSError(errno.EPERM, "Cache base dir is not writable")
return new_base
@property
def cache_dir(self):
"""Directory used for caching data.
@@ -1282,32 +1307,59 @@ class ContextType(object):
>>> cache_dir is not None
True
>>> os.chmod(cache_dir, 0o000)
>>> del context._tls['cache_dir']
>>> context.cache_dir is None
True
>>> os.chmod(cache_dir, 0o755)
>>> cache_dir == context.cache_dir
True
"""
xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
os.path.join(os.path.expanduser('~'), '.cache')
try:
# If the TLS already has a cache directory path, we return it
# without any futher checks since it must have been valid when it
# was set and if that has changed, hiding the TOCTOU here would be
# potentially confusing
return self._tls["cache_dir"]
except KeyError:
pass
if not os.access(xdg_cache_home, os.W_OK):
# Attempt to create a Python version specific cache dir and its parents
cache_dirname = '.pwntools-cache-%d.%d' % sys.version_info[:2]
cache_dirpath = os.path.join(self.cache_dir_base, cache_dirname)
try:
os.makedirs(cache_dirpath)
except OSError as exc:
# If we failed for any reason other than the cache directory
# already existing then we'll fall back to a temporary directory
# object which doesn't respect the `cache_dir_base`
if exc.errno != errno.EEXIST:
try:
cache_dirpath = tempfile.mkdtemp(prefix=".pwntools-tmp")
except IOError:
# This implies no good candidates for temporary files so we
# have to return `None`
return None
else:
# Ensure the temporary cache dir is cleaned up on exit. A
# `TemporaryDirectory` would do this better upon garbage
# collection but this is necessary for Python 2 support.
atexit.register(shutil.rmtree, cache_dirpath)
# By this time we have a cache directory which exists but we don't know
# if it is actually writable. Some wargames e.g. pwnable.kr have
# created dummy directories which cannot be modified by the user
# account (owned by root).
if os.access(cache_dirpath, os.W_OK):
# Stash this in TLS for later reuse
self._tls["cache_dir"] = cache_dirpath
return cache_dirpath
else:
return None
cache = os.path.join(xdg_cache_home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
if not os.path.exists(cache):
try:
os.mkdir(cache)
except OSError:
return None
# Some wargames e.g. pwnable.kr have created dummy directories
# which cannot be modified by the user account (owned by root).
if not os.access(cache, os.W_OK):
return None
return cache
@cache_dir.setter
def cache_dir(self, v):
if os.access(v, os.W_OK):
# Stash this in TLS for later reuse
self._tls["cache_dir"] = v
@_validator
def delete_corefiles(self, v):
+5 -6
View File
@@ -15,7 +15,7 @@ Example
# leaks at least one byte at that address.
def leak(address):
data = p.read(address, 4)
log.debug("%#x => %s" % (address, enhex(data or '')))
log.debug("%#x => %s", address, enhex(data or ''))
return data
# For the sake of this example, let's say that we
@@ -62,8 +62,7 @@ from pwnlib.elf import constants
from pwnlib.log import getLogger
from pwnlib.memleak import MemLeak
from pwnlib.util.fiddling import enhex
from pwnlib.util.packing import unpack, _need_bytes
from pwnlib.util.web import wget
from pwnlib.util.packing import _need_bytes
log = getLogger(__name__)
sizeof = ctypes.sizeof
@@ -562,7 +561,7 @@ class DynELF(object):
else: dynlib = self
if dynlib is None:
log.failure("Could not find %r" % lib)
log.failure("Could not find %r", lib)
return None
#
@@ -573,7 +572,7 @@ class DynELF(object):
self.status("Trying lookup based on Build ID")
build_id = dynlib._lookup_build_id(lib=lib)
if build_id:
log.info("Trying lookup based on Build ID: %s" % build_id)
log.info("Trying lookup based on Build ID: %s", build_id)
path = libcdb.search_by_build_id(build_id)
if path:
with context.local(log_level='error'):
@@ -615,7 +614,7 @@ class DynELF(object):
addr = leak.field(cur, LinkMap.l_addr)
cur = leak.field(cur, LinkMap.l_next)
log.debug('Found %r @ %#x' % (name, addr))
log.debug('Found %r @ %#x', name, addr)
self._bases[name] = addr
+6
View File
@@ -157,6 +157,12 @@ class dotdict(dict):
>>> x.bar.baz
4
"""
def __missing__(self, name):
if isinstance(name, (bytes, bytearray)):
name = packing._decode(name)
return self[name]
raise KeyError(name)
def __getattr__(self, name):
if name in self:
return self[name]
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import division
# +------------------------------------------------------------------------+
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import absolute_import
from __future__ import division
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import division
from __future__ import absolute_import
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import absolute_import
from __future__ import division
@@ -1,3 +1,5 @@
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
# Ported to Python by Gallopsled
from __future__ import division
import os
+2 -1
View File
@@ -193,7 +193,8 @@ class FileStructure(object):
if isinstance(self.__getattr__(val), bytes):
structure += self.__getattr__(val).ljust(context.bytes, b'\x00')
else:
structure += pack(self.__getattr__(val), self.length[val]*8)
if self.length[val] > 0:
structure += pack(self.__getattr__(val), self.length[val]*8)
return structure
def struntil(self,v):
+2 -2
View File
@@ -303,7 +303,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
env_args.append(b'%s=%s' % (key, env.pop(key)))
else:
env_args.append(b'%s=%s' % (key, env[key]))
gdbserver_args += ['--wrapper', 'env', '-i'] + env_args + ['--']
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
gdbserver_args += ['localhost:0']
gdbserver_args += args
@@ -915,7 +915,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
cmd = ['sshpass', '-p', shell.password] + cmd
if shell.keyfile:
cmd += ['-i', shell.keyfile]
cmd += ['gdb', '-q', target.executable, target.pid, '-x', tmpfile]
cmd += ['gdb', '-q', target.executable, str(target.pid), '-x', tmpfile]
misc.run_in_new_terminal(cmd)
return
+153 -29
View File
@@ -12,9 +12,12 @@ import tempfile
from pwnlib.context import context
from pwnlib.elf import ELF
from pwnlib.log import getLogger
from pwnlib.tubes.process import process
from pwnlib.util.fiddling import b64d
from pwnlib.util.fiddling import enhex
from pwnlib.util.fiddling import hexdump
from pwnlib.util.misc import read
from pwnlib.util.misc import which
from pwnlib.util.misc import write
from pwnlib.util.safeeval import const
from pwnlib.util.web import wget
@@ -22,6 +25,7 @@ from pwnlib.util.web import wget
log = getLogger(__name__)
HASHES = ['build_id', 'sha1', 'sha256', 'md5']
DEBUGINFOD_SERVERS = ['https://debuginfod.systemtap.org/']
# https://gitlab.com/libcdb/libcdb wasn't updated after 2019,
# but still is a massive database of older libc binaries.
@@ -88,29 +92,13 @@ def provider_libc_rip(hex_encoded_id, hash_type):
PROVIDERS = [provider_libcdb, provider_libc_rip]
def search_by_hash(hex_encoded_id, hash_type='build_id'):
def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
assert hash_type in HASHES, hash_type
# Ensure that the libcdb cache directory exists
cache_dir = os.path.join(context.cache_dir, 'libcdb', hash_type)
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
# If we already downloaded the file, and it looks even passingly like
# a valid ELF file, return it.
cache = os.path.join(cache_dir, hex_encoded_id)
if os.path.exists(cache):
log.debug("Found existing cached libc at %r", cache)
data = read(cache)
if data.startswith(b'\x7FELF'):
log.info_once("Using cached data from %r", cache)
return cache
else:
log.info_once("Skipping unavailable libc %s", hex_encoded_id)
return None
cache, cache_valid = _check_elf_cache('libcdb', hex_encoded_id, hash_type)
if cache_valid:
return cache
# Run through all available libc database providers to see if we have a match.
for provider in PROVIDERS:
@@ -128,16 +116,146 @@ def search_by_hash(hex_encoded_id, hash_type='build_id'):
if not data or not data.startswith(b'\x7FELF'):
return None
# Try to find debug info for this libc.
if unstrip:
unstrip_libc(cache)
return cache
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
# Deferred import because it's slow
import requests
from six.moves import urllib
def search_by_build_id(hex_encoded_id):
# Check if we tried this buildid before.
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
if cache_valid:
return cache
# Try to find separate debuginfo.
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
url = urllib.parse.urljoin(base_url, url)
data = b""
log.debug("Downloading data from debuginfod: %s", url)
try:
data = wget(url, timeout=20)
except requests.RequestException as e:
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, base_url, e)
# Save whatever we got to the cache
write(cache, data or b'')
# Return ``None`` if we did not get a valid ELF file
if not data or not data.startswith(b'\x7FELF'):
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, base_url)
return None
return cache
def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
"""
Check if there already is an ELF file for this hash in the cache.
>>> cache, _ = _check_elf_cache('libcdb', '2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', 'build_id')
>>> os.unlink(cache) if os.path.exists(cache)
>>> filename = search_by_hash('2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', 'build_id', unstrip=False)
>>> hex(ELF(filename).symbols.read)
'0xe56c0'
>>> filename == cache
True
"""
# Ensure that the cache directory exists
cache_dir = os.path.join(context.cache_dir, cache_type, hash_type)
if not os.path.isdir(cache_dir):
os.makedirs(cache_dir)
# If we already downloaded the file, and it looks even passingly like
# a valid ELF file, return it.
cache = os.path.join(cache_dir, hex_encoded_id)
if not os.path.exists(cache):
return cache, False
log.debug("Found existing cached ELF at %r", cache)
data = read(cache)
if not data.startswith(b'\x7FELF'):
log.info_once("Skipping unavailable ELF %s", hex_encoded_id)
return cache, False
log.info_once("Using cached data from %r", cache)
return cache, True
def unstrip_libc(filename):
"""
Given a path to a libc binary, attempt to download matching debug info
and add them back to the given binary.
This modifies the given file.
Arguments:
filename(str):
Path to the libc binary to unstrip.
Returns:
:const:`True` if binary was unstripped, :const:`False` otherwise.
Examples:
>>> filename = search_by_build_id('2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', unstrip=False)
>>> libc = ELF(filename)
>>> hex(libc.symbols.read)
'0xe56c0'
>>> 'main_arena' in libc.symbols
False
>>> unstrip_libc(filename)
True
>>> libc = ELF(filename)
>>> hex(libc.symbols.main_arena)
'0x1d57a0'
>>> unstrip_libc(which('python'))
False
>>> filename = search_by_build_id('06a8004be6e10c4aeabbe0db74423ace392a2d6b', unstrip=True)
>>> 'main_arena' in ELF(filename).symbols
True
"""
if not which('eu-unstrip'):
log.warn_once('Couldn\'t find "eu-unstrip" in PATH. Install elfutils first.')
return False
libc = ELF(filename, checksec=False)
if not libc.buildid:
log.warn_once('Given libc does not have a buildid. Cannot look for debuginfo to unstrip.')
return False
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
# Add debug info to given libc binary inplace.
p = process(['eu-unstrip', '-o', filename, filename, libc_dbg])
output = p.recvall()
p.close()
if output:
log.error('Failed to unstrip libc binary: %r', output)
return False
return True
def search_by_build_id(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded Build ID, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -152,15 +270,17 @@ def search_by_build_id(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'build_id')
return search_by_hash(hex_encoded_id, 'build_id', unstrip)
def search_by_md5(hex_encoded_id):
def search_by_md5(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded md5sum, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded md5sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -175,15 +295,17 @@ def search_by_md5(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'md5')
return search_by_hash(hex_encoded_id, 'md5', unstrip)
def search_by_sha1(hex_encoded_id):
def search_by_sha1(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded sha1, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded sha1sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -198,16 +320,18 @@ def search_by_sha1(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'sha1')
return search_by_hash(hex_encoded_id, 'sha1', unstrip)
def search_by_sha256(hex_encoded_id):
def search_by_sha256(hex_encoded_id, unstrip=True):
"""
Given a hex-encoded sha256, attempt to download a matching libc from libcdb.
Arguments:
hex_encoded_id(str):
Hex-encoded sha256sum (e.g. 'ABCDEF...') of the library
unstrip(bool):
Try to fetch debug info for the libc and apply it to the downloaded file.
Returns:
Path to the downloaded library on disk, or :const:`None`.
@@ -222,7 +346,7 @@ def search_by_sha256(hex_encoded_id):
>>> hex(ELF(filename).symbols.read)
'0xeef40'
"""
return search_by_hash(hex_encoded_id, 'sha256')
return search_by_hash(hex_encoded_id, 'sha256', unstrip)
@@ -279,4 +403,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']
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'unstrip_libc']
+2 -2
View File
@@ -1477,8 +1477,6 @@ class ROP(object):
# Find an appropriate, non-library ELF.
# Prioritise non-PIE binaries so we can use _fini
exes = (elf for elf in self.elfs if not elf.library and elf.bits == 64)
if not exes:
log.error('No non-library binaries in [elfs]')
nonpie = csu = None
for elf in exes:
@@ -1488,6 +1486,8 @@ class ROP(object):
nonpie = elf
elif '__libc_csu_init' in elf.symbols:
csu = elf
else:
log.error('No non-library binaries in [elfs]')
if elf.pie:
if nonpie:
+13 -17
View File
@@ -1523,29 +1523,24 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
local: Local directory
remote: Remote directory
"""
remote = remote or self.cwd
remote = packing._encode(remote or self.cwd)
if self.sftp:
remote = str(self.sftp.normalize(remote))
remote = packing._encode(self.sftp.normalize(remote))
else:
with context.local(log_level='error'):
remote = self.system('readlink -f ' + sh_string(remote))
remote = self.system(b'readlink -f ' + sh_string(remote))
basename = os.path.basename(remote)
local = local or '.'
local = os.path.expanduser(local)
local = local or '.'
local = os.path.expanduser(local)
self.info("Downloading %r to %r" % (basename,local))
self.info("Downloading %r to %r" % (remote, local))
with context.local(log_level='error'):
remote_tar = self.mktemp()
cmd = 'tar -C %s -czf %s %s' % \
cmd = b'tar -C %s -czf %s .' % \
(sh_string(remote),
sh_string(remote_tar),
sh_string(basename))
sh_string(remote_tar))
tar = self.system(cmd)
if 0 != tar.wait():
@@ -1686,10 +1681,8 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
local(str): Local path to store the data.
By default, uses the current directory.
"""
if not self.sftp:
self.error("Cannot determine remote file type without SFTP")
with self.system('test -d ' + sh_string(file_or_directory)) as io:
file_or_directory = packing._encode(file_or_directory)
with self.system(b'test -d ' + sh_string(file_or_directory)) as io:
is_dir = io.wait()
if 0 == is_dir:
@@ -1827,6 +1820,9 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
symlink = os.path.join(self.pwd(), b'*')
if not hasattr(symlink, 'encode') and hasattr(symlink, 'decode'):
symlink = symlink.decode('utf-8')
if isinstance(wd, six.text_type):
wd = packing._need_bytes(wd, 2, 0x80)
if not wd:
wd, status = self.run_to_end('x=$(mktemp -d) && cd $x && chmod +x . && echo $PWD', wd='.')
+32 -4
View File
@@ -197,6 +197,16 @@ def cyclic_find(subseq, alphabet = None, n = None):
>>> cyclic_find(0x61616162, endian='big')
1
Similarly to the case where you can provide a bytes value that is longer
than four bytes, you can provided an integer value that is larger than
what can be held in four bytes. If such a large value is given, it is
automatically truncated.
>>> cyclic_find(0x6161616361616162)
4
>>> cyclic_find(0x6261616163616161, endian='big')
4
You can use anything for the cyclic pattern, including non-printable
characters.
@@ -208,14 +218,32 @@ def cyclic_find(subseq, alphabet = None, n = None):
n = context.cyclic_size
if isinstance(subseq, six.integer_types):
if subseq >= 2**(8*n):
# Assumption: The user has given an integer that is more than 2**(8n) bits, but would otherwise fit within
# a register of size 2**(8m) where m is a multiple of four
notice = ("cyclic_find() expected an integer argument <= {cap:#x}, you gave {gave:#x}\n"
"Unless you specified cyclic(..., n={fits}), you probably just want the first {n} bytes.\n"
"Truncating the data at {n} bytes. Specify cyclic_find(..., n={fits}) to override this.").format(
cap=2**(8*n)-1,
gave=subseq,
# The number of bytes needed to represent subseq, rounded to the next 4
fits=int(round(float(subseq.bit_length()) / 32 + 0.5) * 32) // 8,
n=n,
)
log.warn_once(notice)
if context.endian == 'little':
subseq &= 2**(8*n) - 1
else:
while subseq >= 2**(8*n):
subseq >>= 8*n
subseq = packing.pack(subseq, bytes=n)
subseq = packing._need_bytes(subseq, 2, 0x80)
if len(subseq) != n:
log.warn_once("cyclic_find() expects %i-byte subsequences by default, you gave %r\n"
"Unless you specified cyclic(..., n=%i), you probably just want the first 4 bytes.\n"
"Truncating the data at 4 bytes. Specify cyclic_find(..., n=%i) to override this.",
n, subseq, len(subseq), len(subseq))
log.warn_once("cyclic_find() expected a %i-byte subsequence, you gave %r\n"
"Unless you specified cyclic(..., n=%i), you probably just want the first %d bytes.\n"
"Truncating the data at %d bytes. Specify cyclic_find(..., n=%i) to override this.",
n, subseq, len(subseq), n, n, len(subseq))
subseq = subseq[:n]
if alphabet is None:
+6 -6
View File
@@ -100,13 +100,13 @@ def size(n, abbrev = 'B', si = False):
return '%.02fP%s' % (n / base, abbrev)
KB = 1024
MB = 1024 * KB
GB = 1024 * MB
KB = 1000
MB = 1000 * KB
GB = 1000 * MB
KiB = 1000
MiB = 1000 * KB
GiB = 1000 * MB
KiB = 1024
MiB = 1024 * KiB
GiB = 1024 * MiB
def read(path, count=-1, skip=0):
r"""read(path, count=-1, skip=0) -> str
+1 -1
View File
@@ -1040,7 +1040,7 @@ def _need_text(s, level=1):
if encoding == 'auto':
for encoding in 'ASCII', 'UTF-8', 'ISO-8859-1':
try:
s = s.decode(encoding)
s.decode(encoding)
except UnicodeDecodeError:
pass
else:
+4 -5
View File
@@ -333,9 +333,9 @@ def wait_for_debugger(pid, debugger_pid=None):
t = Timeout()
with t.countdown(timeout=15):
with log.waitfor('Waiting for debugger') as l:
while debugger_pid:
debugger = psutil.Process(debugger_pid)
while t.timeout and tracer(pid) is None:
while t.timeout and tracer(pid) is None:
if debugger_pid:
debugger = psutil.Process(debugger_pid)
try:
debugger.wait(0.01)
except psutil.TimeoutExpired:
@@ -343,8 +343,7 @@ def wait_for_debugger(pid, debugger_pid=None):
else:
debugger_pid = 0
break
else:
while t.timeout and tracer(pid) is None:
else:
time.sleep(0.01)
if tracer(pid):
+1 -1
View File
@@ -1 +1 @@
__version__ = '4.7.0'
__version__ = '4.8.0'
+1 -1
View File
@@ -89,7 +89,7 @@ setup(
name = 'pwntools',
python_requires = '>=2.7',
packages = find_packages(),
version = '4.7.0',
version = '4.8.0',
data_files = [('pwntools-doc',
glob.glob('*.md') + glob.glob('*.txt')),
],