Compare commits

...
13 Commits
Author SHA1 Message Date
Mathias Svensson cca1083f0b Release 3.10.0 2017-10-25 12:01:59 +02:00
Mathias Svensson 9dacd1b5d9 Merge branch 'stable' into beta 2017-10-25 11:56:07 +02:00
Zach Riggle 74a5876c0d Fix corefile environment variables and speed up ELF.string() (#1055)
* Fix corefile environment variables and speed up ELF.string()

* Even more doctests

* Fix doctest arch

* No longer split on = since it isn't necessary

* Fix arch again

* Delete intermediate corefiles

* Do not flag for extended testing when the commit SHA is what matches
2017-10-23 23:45:11 -05:00
Zach Riggle 9d6b4881fe Fix path checking in ssh_process.corefile for edge cases (#1058)
Fixes #1047
2017-10-23 15:37:48 -05:00
Zach Riggle 91025408a4 Fix incorrect variable in log statement (#1057)
Fixes #1056
2017-10-23 14:39:12 -05:00
Mathias Svensson 513682373e Release 3.10.0beta2 2017-10-05 09:44:33 +02:00
Mathias Svensson 716c67d993 Release 3.10.0beta1 2017-09-28 11:37:08 +02:00
Zach Riggle 69f8cc2c47 Merge branch 'stable' into beta 2017-09-26 12:12:20 -05:00
Mathias Svensson ec98dc1246 Release 3.10.0beta0 2017-09-11 18:59:06 +02:00
Zach Riggle 7cd3c7b300 Merge branch 'beta' into dev 2017-09-08 12:41:42 -05:00
Benno Fünfstück 5831796f6f Add configurable gdbinit (#1007)
* fix typo in comment

* new config option: context.gdbinit

This allows setting the default gdbinit for GDB, which is helpful when you want
to enable some GDB extension only when using pwntools and not by default.
2017-09-06 12:56:55 -05:00
Mathias Svensson 3e33180655 Update CHANGELOG 2017-07-29 13:26:21 +02:00
Mathias Svensson e09543c098 Start working on 3.10 2017-07-29 13:02:55 +02:00
11 changed files with 199 additions and 26 deletions

No files matched your search

+31 -4
View File
@@ -9,9 +9,10 @@ The table below shows which release corresponds to each branch, and what date th
| Version | Branch | Release Date |
| ---------------- | -------- | ---------------------- |
| [3.11.0](#3110) | `dev` | Dec 2, 2017 (planned)
| [3.10.0](#3100) | `beta` | Oct 21, 2017 (planned)
| [3.9.2](#392) | `stable` | Oct 5, 2017
| [3.12.0](#3120) | `dev` | Jan 13, 2018 (planned)
| [3.11.0](#3110) | `beta` | Dec 2, 2017 (planned)
| [3.10.0](#3100) | `stable` | Oct 25, 2017
| [3.9.2](#392) | | Oct 5, 2017
| [3.9.1](#391) | | Sep 28, 2017
| [3.9.0](#390) | | Sep 11, 2017
| [3.8.0](#380) | | Jul 29, 2017
@@ -39,13 +40,39 @@ 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
## 3.12.0
To be released on Jan 13, 2018.
## 3.11.0
To be released on Dec 2, 2017.
- [#1044][1044] Enhancements to ROP
+ Much better support for 64-bit Intel (amd64) ROP
+ ROP gadget selection is optimized to favor multi-pops instead of multiple single-pop gadgets
+ Added support for blacklisting byte values in ROP gadget addresses
- [#1049][1049] Enhancements to `cyclic`
+ `context` now has two additional attributes, `cyclic_alphabet` and `cyclic_length`, which correspond to the arguments `alphabet` and `n` to `cyclic()` and `cyclic_find()` and related routines.
+ The motivation for this change is to allow setting the `alphabet` globally, so that any padding / patterns generated internally to pwntools can be controlled. The specific motivation is blacklisting values in ROP padding.
- [#1052][1052] Enhancements for detecting `QEMU_LD_PREFIX` used by QEMU user-mode emulation for sysroots
[1044]: https://github.com/Gallopsled/pwntools/pull/1044
[1049]: https://github.com/Gallopsled/pwntools/pull/1049
[1052]: https://github.com/Gallopsled/pwntools/pull/1052
## 3.10.0
To be released on Oct 21, 2017.
- [#1007][1007] Add support for setting a `gdbinit` file in the context
- [#1055][1055] Fixes for `Corefile` stack parsing, speed up `ELF.string()`
- [#1057][1057] Fix a variable name typo in `DynELF` logging which results in an exception being thrown
- [#1058][1058] Fix an edge case in `ssh_process.exe`
[1007]: https://github.com/Gallopsled/pwntools/pull/1007
[1055]: https://github.com/Gallopsled/pwntools/pull/1055
[1057]: https://github.com/Gallopsled/pwntools/pull/1057
[1058]: https://github.com/Gallopsled/pwntools/pull/1058
## 3.9.2
+1 -1
View File
@@ -2,7 +2,7 @@
![pwntools logo](https://github.com/Gallopsled/pwntools/blob/stable/docs/source/logo.png?raw=true)
[![Docs](https://readthedocs.org/projects/pwntools/badge/?version=stable)](https://docs.pwntools.com/)
[![PyPI](https://img.shields.io/badge/pypi-v3.9.2-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![PyPI](https://img.shields.io/badge/pypi-v3.10.0-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![Travis](https://travis-ci.org/Gallopsled/pwntools.svg)](https://travis-ci.org/Gallopsled/pwntools)
[![Coveralls](https://img.shields.io/coveralls/Gallopsled/pwntools/dev.svg)](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
[![Twitter](https://img.shields.io/badge/twitter-pwntools-4099FF.svg?style=flat)](https://twitter.com/pwntools)
+19 -1
View File
@@ -150,7 +150,7 @@ class _Tls_DictStack(threading.local, _DictStack):
def _validator(validator):
"""
Validator that tis tightly coupled to the implementation
Validator that is tightly coupled to the implementation
of the classes here.
This expects that the object has a ._tls property which
@@ -340,6 +340,7 @@ class ContextType(object):
'delete_corefiles': False,
'device': os.getenv('ANDROID_SERIAL', None) or None,
'endian': 'little',
'gdbinit': "",
'kernel': None,
'log_level': logging.INFO,
'log_file': _devnull(),
@@ -1231,6 +1232,23 @@ class ContextType(object):
"""
return bool(v)
@_validator
def gdbinit(self, value):
"""Path to the gdbinit that is used when running GDB locally.
This is useful if you want pwntools-launched GDB to include some additional modules,
like PEDA but you do not want to have GDB include them by default.
The setting will only apply when GDB is launched locally since remote hosts may not have
the necessary requirements for the gdbinit.
If set to an empty string, GDB will use the default `~/.gdbinit`.
Default value is ``""``.
"""
return str(value)
#*************************************************************************
# ALIASES
#*************************************************************************
+1 -1
View File
@@ -447,7 +447,7 @@ class DynELF(object):
if debug:
w.status("r_debug.linkmap")
linkmap = self.leak.field(debug, r_debug.r_map)
w.status("r_debug.linkmap %#x" % result)
w.status("r_debug.linkmap %#x" % linkmap)
if not linkmap:
w.failure("Could not find DT_PLTGOT or DT_DEBUG")
+116 -10
View File
@@ -389,7 +389,7 @@ class Corefile(ELF):
>>> core.vdso.data[:4]
'\x7fELF'
>>> core.libc # docteset: +ELLIPSIS
>>> core.libc # doctest: +ELLIPSIS
Mapping('/lib/x86_64-linux-gnu/libc-...', ...)
The corefile also contains a :attr:`.Corefile.stack` property, which gives
@@ -421,7 +421,7 @@ class Corefile(ELF):
>>> s = ssh('travis', 'example.pwnme')
>>> _ = s.set_working_directory()
>>> elf = ELF.from_assembly('int3')
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> path = s.upload(elf.path)
>>> _ =s.chmod('+x', path)
>>> io = s.process(path)
@@ -429,6 +429,46 @@ class Corefile(ELF):
-1
>>> io.corefile.signal == signal.SIGTRAP # doctest: +SKIP
True
Tests:
These are extra tests not meant to serve as examples.
Corefile.getenv() works correctly, even if the environment variable's
value contains embedded '='. Corefile is able to find the stack, even
if the stack pointer doesn't point at the stack.
>>> elf = ELF.from_assembly(shellcraft.crash())
>>> io = elf.process(env={'FOO': 'BAR=BAZ'})
>>> io.wait()
>>> core = io.corefile
>>> core.getenv('FOO')
'BAR=BAZ'
>>> core.sp == 0
True
>>> core.sp in core.stack
False
Corefile gracefully handles the stack being filled with garbage, including
argc / argv / envp being overwritten.
>>> context.clear(arch='i386')
>>> assembly = '''
... LOOP:
... mov dword ptr [esp], 0x41414141
... pop eax
... jmp LOOP
... '''
>>> elf = ELF.from_assembly(assembly)
>>> io = elf.process()
>>> io.wait()
>>> core = io.corefile
>>> core.argc, core.argv, core.env
(0, [], {})
>>> core.stack.data.endswith('AAAA')
True
>>> core.fault_addr == core.sp
True
"""
_fill_gaps = False
@@ -454,6 +494,9 @@ class Corefile(ELF):
#: variable.
#:
#: Note: Use with the :meth:`.ELF.string` method to extract them.
#:
#: Note: If FOO=BAR is in the environment, self.env['FOO'] is the
#: address of the string "BAR\x00".
self.env = {}
#: :class:`list`: List of addresses of arguments on the stack.
@@ -664,7 +707,22 @@ class Corefile(ELF):
@property
def signal(self):
""":class:`int`: Signal which caused the core to be dumped."""
""":class:`int`: Signal which caused the core to be dumped.
Example:
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.signal == signal.SIGTRAP
True
>>> elf = ELF.from_assembly(shellcraft.crash())
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.signal == signal.SIGSEGV
True
"""
if self.siginfo:
return int(self.siginfo.si_signo)
if self.prstatus:
@@ -675,7 +733,17 @@ class Corefile(ELF):
""":class:`int`: Address which generated the fault, for the signals
SIGILL, SIGFPE, SIGSEGV, SIGBUS. This is only available in native
core dumps created by the kernel. If the information is unavailable,
this returns the address of the instruction pointer."""
this returns the address of the instruction pointer.
Example:
>>> elf = ELF.from_assembly('mov eax, 0xdeadbeef; jmp eax', arch='i386')
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.fault_addr == io.corefile.eax == 0xdeadbeef
True
"""
if self.siginfo:
return int(self.siginfo.sigfault_addr)
@@ -848,11 +916,31 @@ class Corefile(ELF):
# Now we can fill in the environment
env_pointer_data = stack[start_of_envp:p_last_env_addr+self.bytes]
for pointer in unpack_many(env_pointer_data):
end = stack.find('=', last_env_addr)
if pointer in stack and end in stack:
name = stack[pointer:end]
self.env[name] = pointer
# If the stack is corrupted, the pointer will be outside of
# the stack.
if pointer not in stack:
continue
try:
name_value = self.string(pointer)
except Exception:
continue
name, value = name_value.split('=', 1)
# "end" points at the byte after the null terminator
end = pointer + len(name_value) + 1
# Do not mark things as environment variables if they point
# outside of the stack itself, or we had to cross into a different
# mapping (after the stack) to read it.
# This may occur when the entire stack is filled with non-NUL bytes,
# and we NULL-terminate on a read failure in .string().
if end not in stack:
continue
self.env[name] = pointer + len(name) + len('=')
# May as well grab the arguments off the stack as well.
# argc comes immediately before argv[0] on the stack, but
@@ -904,15 +992,32 @@ class Corefile(ELF):
Returns:
:class:`str`: The contents of the environment variable.
Example:
>>> elf = ELF.from_assembly(shellcraft.trap())
>>> io = elf.process(env={'GREETING': 'Hello!'})
>>> io.wait()
>>> io.corefile.getenv('GREETING')
'Hello!'
"""
if name not in self.env:
log.error("Environment variable %r not set" % name)
return self.string(self.env[name]).split('=',1)[-1]
return self.string(self.env[name])
@property
def registers(self):
""":class:`dict`: All available registers in the coredump."""
""":class:`dict`: All available registers in the coredump.
Example:
>>> elf = ELF.from_assembly('mov eax, 0xdeadbeef;' + shellcraft.trap(), arch='i386')
>>> io = elf.process()
>>> io.wait()
>>> io.corefile.registers['eax'] == 0xdeadbeef
True
"""
if not self.prstatus:
return {}
@@ -1020,6 +1125,7 @@ class CorefileFinder(object):
new_path = 'core.%i' % core_pid
if core_pid > 0 and new_path != self.core_path:
write(new_path, self.read(self.core_path))
self.unlink(self.core_path)
self.core_path = new_path
# Check the PID
+22 -5
View File
@@ -1686,16 +1686,33 @@ class ELF(ELFFile):
return packing.unpack(self.read(address, context.bytes), *a, **kw)
def string(self, address):
"""Reads a null-terminated string from the specified ``address``"""
"""string(address) -> str
Reads a null-terminated string from the specified ``address``
Returns:
A ``str`` with the string contents (NUL terminator is omitted),
or an empty string if no NUL terminator could be found.
"""
data = ''
while True:
c = self.read(address, 1)
read_size = 0x1000
partial_page = address & 0xfff
if partial_page:
read_size -= partial_page
c = self.read(address, read_size)
if not c:
return ''
if c == '\x00':
return data
data += c
address += 1
if '\x00' in c:
return data[:data.index('\x00')]
address += len(c)
def flat(self, address, *a, **kw):
"""Writes a full array of values to the specified address.
+4
View File
@@ -646,6 +646,10 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
cmd += ' '
cmd += ' '.join(gdb_args)
if context.gdbinit:
cmd += ' -nh ' # ignore ~/.gdbinit
cmd += ' -x %s ' % context.gdbinit # load custom gdbinit
cmd += ' -q '
if exe and context.native:
+2 -1
View File
@@ -341,7 +341,8 @@ class ssh_process(ssh_channel):
libs = self.parent.libs(self.executable)
for lib in libs:
if self.executable in lib:
# Cannot just check "executable in lib", see issue #1047
if lib.endswith(self.executable):
return pwnlib.elf.elf.ELF(lib)
+1 -1
View File
@@ -1 +1 @@
__version__ = '3.9.2'
__version__ = '3.10.0'
+1 -1
View File
@@ -86,7 +86,7 @@ except Exception as e:
setup(
name = 'pwntools',
packages = find_packages(),
version = '3.9.2',
version = '3.10.0',
data_files = [('',
glob.glob('*.md') + glob.glob('*.txt')),
],
+1 -1
View File
@@ -90,7 +90,7 @@ setup_android_emulator()
elif [[ -n "$TRAVIS_TAG" ]]; then
echo "TRAVIS_TAG ($TRAVIS_TAG) indicates a new relase"
echo "Forcing Android Emulator installation"
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb"); then
elif (git log --stat "$TRAVIS_COMMIT_RANGE" | grep -iE "android|adb" | grep -v "commit "); then
echo "Found Android-related commits, forcing Android Emulator installation"
else
# In order to avoid running the doctests that require the Android