Compare commits

...
26 Commits
Author SHA1 Message Date
Mathias Svensson c888a8cbe0 Release 3.12.0 2018-02-22 16:42:25 +01:00
Mathias Svensson 9f9d884790 Merge branch 'beta' into stable 2018-02-22 13:54:37 +01:00
Mathias Svensson 39bc5808ea Update CHANGELOG from dev 2018-02-22 13:54:19 +01:00
Zach Riggle 31f7c61c1f Merge remote-tracking branch 'stable' into beta 2018-02-20 14:49:38 -06:00
Zach Riggle 2a73356c78 Fix test breakage caused by Sphinx deprecation (#1113)
* Fix test breakage caused by Sphinx deprecation

* Fix missing import in RST

Closes #1112
2018-02-20 14:49:09 -06:00
david942j 25c84daa8a Fix usage of syscall open in shellcraft templates (#1106) 2018-02-09 12:53:00 -06:00
Christophe GRENIER 3ff7a9e940 Fix for very old openssl: try sha256 and sha256sum before "openssl sha256" (#1068)
Check for sha256 and sha256sum before trying openssl sha256 as "openssl sha256" doesn't return an error code in some old versions of openssl (OpenSSL 0.9.8g 19 Oct 2007 at least) that doesn't support sha256.
2018-01-23 16:58:56 -08:00
Mathias Svensson e50b978398 Release 3.12.0beta0 2018-01-03 08:21:05 +01:00
Zach Riggle 35789287d8 Update changelog for 3.11 and 3.12 2018-01-02 12:53:30 -06:00
Zach Riggle 7a1ab829eb Expose pwnlib.tubes.server.server in the toplevel 2018-01-02 12:47:43 -06:00
Zach Riggle 899baec704 Android bootldr extraction, gdb fixes (#1085)
* Add sysroot= argument to gdb.debug, expose via 'pwn debug'

Also sets a sane default sysroot for Android debugging

* Add missing newline for "set deatch-on-fork off"

Introduced in e89a9b66ac

* Add support for extracting Android boot.img

* Fix incorrect variable in 'pwn debug'

* Ensure "file /path/to/file" is the first command
2017-12-21 15:18:38 -06:00
Disconnect3d 932cb57bf9 Better error message when gdbserver fails on LD_PRELOAD (#1083)
When one passes a `LD_PRELOAD` which cannot be preloaded the `process_created` string might end up looking like this:
```
"ERROR: ld.so: object '/bin/bash' from LD_PRELOAD cannot be preloaded (cannot dynamically load executable): ignored.\n"
```

When we end up in such situation, pwndbg shown unintuitive error:
```
$ python -c 'from pwn import *; gdb.debug("/bin/ls", env={"LD_PRELOAD": "/bin/bash"})'
[+] Starting local process '/usr/bin/gdbserver': pid 20691
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "pwnlib/context/__init__.py", line 1349, in setter
    return function(*a, **kw)
  File "pwnlib/gdb.py", line 397, in debug
    port = _gdbserver_port(gdbserver, ssh)
  File "pwnlib/gdb.py", line 234, in _gdbserver_port
    gdbserver.pid   = int(process_created.split()[-1], 0)
ValueError: invalid literal for int() with base 0: 'ignored.'
```

After this patch it will show:
```
$ python -c 'from pwn import *; gdb.debug("/bin/ls", env={"LD_PRELOAD": "/bin/bash"})'
[+] Starting local process '/usr/bin/gdbserver': pid 20863
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "pwnlib/context/__init__.py", line 1349, in setter
    return function(*a, **kw)
  File "pwnlib/gdb.py", line 402, in debug
    port = _gdbserver_port(gdbserver, ssh)
  File "pwnlib/gdb.py", line 236, in _gdbserver_port
    'Failed to spawn process under gdbserver. gdbserver error message: %s' % process_created
ValueError: Failed to spawn process under gdbserver. gdbserver error message: ERROR: ld.so: object '/bin/bash' from LD_PRELOAD cannot be preloaded (cannot dynamically load executable): ignored.

[*] Stopped process '/bin/ls' (pid 20863)
```
2017-12-12 14:22:43 -06:00
Zach Riggle 6a61617c73 Fix emulation of fault_addr (#1066)
As @bennofs noticed, our detection logic was incorrect:

"This is not a good test if I understand the issue correctly.

From my tests, it seems that fault_addr was only null before this patch if it
would've been >= 2**47.

This is because on 64 bit, that's where kernel memory is mapped so we get
"protection fault" instead of "segfault".

A protection fault does not have the same siginfo as a normal segfault:
si_code will be SI_KERNEL = 0x80 and there won't be a fault_addr

(see http://elixir.free-electrons.com/linux/v4.14-rc8/source/kernel/signal.c#L1052)"
2017-12-11 17:48:25 -06:00
Devin Neal ab8879afa3 Add server module (#1067)
* add server module

* modify example

* fix travis errors

* add queuing and blocking, fix requests

* add server.rst, change accepter to daemon

* fix import in server.rst

* added server entry in sockets.rst

* fix import in sockets.rst
2017-12-11 17:27:51 -06:00
bannsec 515f678f2d Adding GNU Screen Support (#1076)
* Adding GNU Screen support

* Adding doc

Fixes Gallopsled/pwntools#1070
2017-12-07 15:25:52 -06:00
bannsec c3afdd681d Adding non-default gdb handling (#1074)
Fixes Gallopsled/pwntools#1071
2017-12-07 15:25:27 -06:00
Owlz 5d537a6189 Fix typo on Gadget.addr -> Gadget.address 2017-12-04 14:30:41 -06:00
Zach Riggle 9ba966ddba Switch server to ftp.ubuntu.org
Currently ftp.debian.org is down on port 21
2017-12-04 14:30:41 -06:00
Zach Riggle 6ea5722f5b Add a comment to dup shellcodes 2017-11-10 17:00:25 -06:00
Zach Riggle 4e24f665b8 Use hex() for some ROP values (#1050)
* Use hex() for some ROP values

* Call str() on everything else

* Fix one more doctest
2017-11-01 16:15:25 -05:00
Zach Riggle 34cedc96d8 Remove old / unused lines in ABI 2017-11-01 16:14:40 -05:00
Zach Riggle e8065440c2 Use badbytes for ROP.search_iter 2017-11-01 16:14:40 -05:00
Zach Riggle de34906b4c Better handling for Corefiles with unsupported architectures 2017-11-01 16:14:40 -05:00
Zach Riggle e89a9b66ac Handle debugging under Android better with 'pwn debug' (#1053)
* Handle debugging under Android better with 'pwn debug'

Previously, when using --process or --pid, we would terminate the gdbserver instance
when pwntools exited.  Now we wait for the user to tell us to exit.

* Handle launching Android processes by filename

* Use the --multi option with gdbserver; disable detach-on-fork

* Intentionally break doctest to make sure its running

* Revert "Intentionally break doctest to make sure its running"

This reverts commit 00578ad177.
2017-10-30 14:17:47 -05:00
cpupower 64acece61b Copy env using .copy method to preserve order (OrderedDict) (#1061)
* Copy env in process._validate using .copy method to keep the order in case of OrderedDict

* Use supplied env object to preserve order in case of OrderedDict
2017-10-30 14:12:45 -05:00
Mathias Svensson bb9004c298 Begin working on 3.12.0 2017-10-25 12:23:09 +02:00
31 changed files with 472 additions and 69 deletions

No files matched your search

+28 -7
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.13.0](#3130) | `dev` | Mar 31, 2018 (planned)
| [3.12.0](#3120) | `beta` | Feb 17, 2018 (planned)
| [3.11.0](#3110) | `stable` | Jan 3, 2018
| [3.14.0](#3130) | `dev` | Jun 12, 2018 (planned)
| [3.13.0](#3130) | `beta` | Mar 31, 2018 (planned)
| [3.12.0](#3120) | `stable` | Feb 22, 2018
| [3.11.0](#3110) | | Jan 3, 2018
| [3.10.0](#3100) | | Oct 25, 2017
| [3.9.2](#392) | | Oct 5, 2017
| [3.9.1](#391) | | Sep 28, 2017
@@ -41,9 +42,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
## 3.12.0
## 3.14.0
To be released on Feb 17, 2018.
To be released on Jun 12, 2018.
## 3.13.0
To be released on Mar 31, 2018.
- [#1104][1104] Add `DynELF.dump()` for dumping remote ELF files
- [#1101][1101] Set `context.os` via `context.binary`, useful for Android exploitation
- [5fdc08][5fdc08] Work around broken `pidof` on Android
- [63dfed][63dfed] Print warning when Corefile deletion fails instead of throwing an exception
- [#1094][1094] Make hexdump output alignment more consistent
- [#1096][1096] `flat()` and `fit()` are now the same function
- [#1068][1068] Work around very old OpenSSL versions which don't have sha256 support *AND* don't exit with an error code when trying to use it
[1104]: https://github.com/Gallopsled/pwntools/pull/1104
[1101]: https://github.com/Gallopsled/pwntools/pull/1101
[1094]: https://github.com/Gallopsled/pwntools/pull/1094
[1096]: https://github.com/Gallopsled/pwntools/pull/1096
[1068]: https://github.com/Gallopsled/pwntools/pull/1068
[5fdc08]: https://github.com/Gallopsled/pwntools/commit/5fdc08
[63dfed]: https://github.com/Gallopsled/pwntools/commit/63dfed
## 3.12.0
- [#1083][1083] Better error messages for `gdb` when `LD_PRELOAD` is incorrect
- [#1085][1085] Add support for extracting Android `BOOTLDR!` images
@@ -59,7 +82,6 @@ To be released on Feb 17, 2018.
[1067]: https://github.com/Gallopsled/pwntools/pull/1067
[1063]: https://github.com/Gallopsled/pwntools/pull/1063
## 3.11.0
- [#1044][1044] Enhancements to ROP
@@ -95,7 +117,6 @@ To be released on Feb 17, 2018.
[1057]: https://github.com/Gallopsled/pwntools/pull/1057
[1058]: https://github.com/Gallopsled/pwntools/pull/1058
## 3.9.2
- [#1043][1043] Do not attempt to populate the libraries used by statically-linked binaries
+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.11.0-green.svg?style=flat)](https://pypi.python.org/pypi/pwntools/)
[![PyPI](https://img.shields.io/badge/pypi-v3.12.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)
+21
View File
@@ -24,6 +24,27 @@ sys.path.insert(0, os.path.abspath('../..'))
import pwnlib
# -- WORK-AROUNDS FOR DEPRECATION ----------------------------------------------
# Deprecated
# 1.6b1
# sphinx.util.compat.Directive class is now deprecated.
# Please use instead docutils.parsers.rst.Directive
#
# Pwntools Note:
# Can't just "do the right thing" since we have dependencies that
# are also affected by this, specifically sphinxcontrib.autoprogram
try:
import sphinx.util.compat
except ImportError:
import sys
import types
import sphinx.util
import docutils.parsers.rst
class compat(types.ModuleType):
Directive = docutils.parsers.rst.Directive
sphinx.util.compat = compat('sphinx.util.compat')
sys.modules['sphinx.util.compat'] = sphinx.util.compat
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
+1
View File
@@ -1,5 +1,6 @@
.. testsetup:: *
from glob import glob
from pwn import *
:mod:`pwnlib.elf.corefile` --- Core Files
+7
View File
@@ -1,6 +1,7 @@
.. testsetup:: *
from pwn import *
from pwnlib.tubes.server import server
:mod:`pwnlib.tubes.sock` --- Sockets
===========================================================
@@ -23,3 +24,9 @@
.. autoclass:: pwnlib.tubes.listen.listen
:members:
:show-inheritance:
.. automodule:: pwnlib.tubes.server
.. autoclass:: pwnlib.tubes.server.server
:members:
:show-inheritance:
+1
View File
@@ -43,6 +43,7 @@ from pwnlib.tubes.listen import listen
from pwnlib.tubes.process import process, PTY, PIPE, STDOUT
from pwnlib.tubes.remote import remote, tcp, udp, connect
from pwnlib.tubes.serialtube import serialtube
from pwnlib.tubes.server import server
from pwnlib.tubes.ssh import ssh
from pwnlib.tubes.tube import tube
from pwnlib.ui import *
-5
View File
@@ -124,8 +124,3 @@ linux_aarch64_sigreturn = SigreturnABI('sp', ['x8'], 16, 0)
windows_i386 = ABI('esp', [], 4, 0)
windows_amd64 = ABI('rsp', ['rcx','rdx','r8','r9'], 32, 32)
# Fake ABIs used by SROP
linux_i386_srop = ABI('esp', ['eax'], 4, 0)
linux_amd64_srop = ABI('rsp', ['rax'], 4, 0)
linux_arm_srop = ABI('sp', ['r7'], 4, 0)
+1
View File
@@ -820,6 +820,7 @@ echo $PATH | while read -d: directory; do
[ -x "$directory/{name}" ] || continue;
echo -n "$directory/{name}\\x00";
done
[ -x "{name}" ] && echo -n "$PWD/{name}\\x00"
'''.format(name=name)
which_cmd = which_cmd.strip()
+103
View File
@@ -0,0 +1,103 @@
from __future__ import unicode_literals
import ctypes
import io
import os
import sys
class img_info(ctypes.Structure):
_fields_ = [
('name', ctypes.c_char * 64),
('size', ctypes.c_uint32)
]
class bootloader_images_header(ctypes.Structure):
_fields_ = [
('magic', ctypes.c_char * 8),
('num_images', ctypes.c_uint32),
('start_offset', ctypes.c_uint32),
('bootldr_size', ctypes.c_uint32),
]
def __init__(self, *a, **kw):
super(bootloader_images_header, self).__init__(*a, **kw)
if self.magic != self.MAGIC:
raise ValueError("Incorrect magic (%r, expected %r)" % (self.magic, self.MAGIC))
MAGIC = 'BOOTLDR!'
class BootloaderImage(object):
def __init__(self, data):
"""Android Bootloader image
Arguments:
data(str): Binary data from the image file.
"""
self.data = data
self.header = bootloader_images_header.from_buffer_copy(data)
imgarray = ctypes.ARRAY(img_info, self.header.num_images)
self.img_info = imgarray.from_buffer_copy(data, ctypes.sizeof(self.header))
def extract(self, index_or_name):
"""extract(index_or_name) -> bytes
Extract the contents of an image.
Arguments:
index_or_name(str,int): Either an image index or name.
Returns:
Contents of the image.
"""
if isinstance(index_or_name, int):
index = index_or_name
else:
for i in range(len(self.img_info)):
if self.img_info[i].name == index_or_name:
index = i
break
else:
raise ValueError("Invalid img name: %r" % index_or_name)
if index >= len(self.img_info):
raise ValueError("index out of range (%s, max %s)" % (index, len(self.img_info)))
offset = self.header.start_offset
for i in range(index):
offset += self.img_info[i].size
return self.data[offset:offset + self.img_info[index].size]
def extract_all(self, path):
"""extract_all(path)
Extracts all images to the provided path. The filenames are taken
from the image name, with '.img' appended.
"""
if not os.path.isdir(path):
raise ValueError("%r does not exist or is not a directory" % path)
for img in self.img_info:
imgpath = os.path.join(path, img.name + '.img')
with open(imgpath, 'wb+') as f:
data = self.extract(img.name)
f.write(data)
def __str__(self):
rv = []
rv.append("Bootloader")
rv.append(" Magic: %r" % self.header.magic)
rv.append(" Offset: %#x" % self.header.start_offset)
rv.append(" Size: %#x" % self.header.bootldr_size)
rv.append(" Images: %s" % self.header.num_images)
for img in self.img_info:
rv.append(" Name: %s" % img.name)
rv.append(" Size: %#x" % img.size)
rv.append(" Data: %r..." % self.extract(img.name)[:32])
return '\n'.join(rv)
if __name__ == '__main__':
# Easy sanity checking
b = BootloaderImage(open(sys.argv[1]).read())
print(b)
+37 -7
View File
@@ -30,13 +30,25 @@ parser.add_argument(
help = 'The os/architecture/endianness/bits the shellcode will run in (default: linux/i386), choose from: %s' % common.choices,
)
parser.add_argument(
'--exec', type=file, dest='executable',
'--exec',
# NOTE: Type cannot be "file" because we may be referring to a remote
# file, or a file on an Android device.
type=str,
dest='executable',
help='File to debug'
)
parser.add_argument(
'--process', metavar='PROCESS_NAME',
help='Name of the process to attach to (e.g. "bash")'
)
parser.add_argument(
'--sysroot', metavar='SYSROOT',
type=str,
default='',
help="GDB sysroot path"
)
def main(args):
gdbscript = ''
@@ -47,23 +59,41 @@ def main(args):
context.device = adb.wait_for_device()
if args.executable:
context.binary = ELF(args.executable.name)
target = context.binary.path
if os.path.exists(args.executable):
context.binary = ELF(args.executable)
target = context.binary.path
# This path does nothing, but avoids the "print_usage()"
# path below.
elif context.os == 'android':
target = args.executable
elif args.pid:
target = int(args.pid)
elif args.process:
if context.os == 'android':
target = adb.pidof(process)
target = adb.pidof(args.process)
else:
target = pidof(process)
target = pidof(args.process)
# pidof() returns a list
if not target:
log.error("Could not find a PID for %r", args.process)
target = target[0]
else:
parser.print_usage()
return 1
if args.pid or args.process:
gdb.attach(target, gdbscript=gdbscript)
pid = gdb.attach(target, gdbscript=gdbscript, sysroot=args.sysroot)
# Since we spawned the gdbserver process, and process registers an
# atexit handler to close itself, gdbserver will be terminated when
# we exit. This will manifest as a "remote connected ended" or
# similar error message. Hold it open for the user.
log.info("GDB connection forwarding will terminate when you press enter")
pause()
else:
gdb.debug(target, gdbscript=gdbscript).interactive()
gdb.debug(target, gdbscript=gdbscript, sysroot=args.sysroot).interactive()
if __name__ == '__main__':
pwnlib.commandline.common.main(__file__)
+29 -21
View File
@@ -753,30 +753,38 @@ class Corefile(ELF):
>>> io.corefile.fault_addr == io.corefile.eax == 0xdeadbeef
True
"""
fault_addr = 0
if self.siginfo:
fault_addr = int(self.siginfo.sigfault_addr)
if not self.siginfo:
return getattr(self, 'pc', 0)
# The fault_addr on AMD64 is zero if the crash occurs
# after a "ret" instruction, if the "ret" would return
# to an invalid address. We need to extract the address
# manually, as a convenience.
if fault_addr == 0 and self.arch == 'amd64' and self.pc and self.sp:
try:
code = self.read(self.pc, 1)
if code != '\xc3':
return fault_addr
address = self.unpack(self.sp)
return address
except Exception:
# Could not read $rsp or $rip
pass
fault_addr = int(self.siginfo.sigfault_addr)
return fault_addr
# The fault_addr is zero if the crash occurs due to a
# "protection fault", e.g. a dereference of 0x4141414141414141
# because this is technically a kernel address.
#
# A protection fault does not set "fault_addr" in the siginfo.
# (http://elixir.free-electrons.com/linux/v4.14-rc8/source/kernel/signal.c#L1052)
#
# Since a common use for corefiles is to spray the stack with a
# cyclic pattern to find the offset to get control of $PC,
# check for a "ret" instruction ("\xc3").
#
# If we find a RET at $PC, extract the "return address" from the
# top of the stack.
if fault_addr == 0 and self.siginfo.si_code == 0x80:
try:
code = self.read(self.pc, 1)
RET = '\xc3'
if code == RET:
fault_addr = self.unpack(self.sp)
except Exception:
# Could not read $rsp or $rip
pass
return fault_addr
# No embedded siginfo structure, so just return the
# current instruction pointer.
return getattr(self, 'pc', 0)
@property
def _pc_register(self):
@@ -819,8 +827,8 @@ class Corefile(ELF):
fields = [
repr(self.path),
'%-10s %s' % ('Arch:', gnu_triplet),
'%-10s %#x' % ('%s:' % self._pc_register.upper(), getattr(self, 'pc', 0)),
'%-10s %#x' % ('%s:' % self._sp_register.upper(), getattr(self, 'sp', 0)),
'%-10s %#x' % ('%s:' % self._pc_register.upper(), self.pc or 0),
'%-10s %#x' % ('%s:' % self._sp_register.upper(), self.sp or 0),
]
if self.exe and self.exe.name:
+7 -3
View File
@@ -262,9 +262,13 @@ class ELF(ELFFile):
self.bits = 64
# Is this a native binary? Should we be checking QEMU?
with context.local(arch=self.arch):
#: Whether this ELF should be able to run natively
self.native = context.native
try:
with context.local(arch=self.arch):
#: Whether this ELF should be able to run natively
self.native = context.native
except AttributeError:
# The architecture may not be supported in pwntools
self.native = False
self._address = 0
if self.elftype != 'DYN':
+38 -9
View File
@@ -229,7 +229,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None):
orig_args = args
gdbserver_args = [gdbserver]
gdbserver_args = [gdbserver, '--multi']
if context.aslr:
gdbserver_args += ['--no-disable-randomization']
else:
@@ -249,6 +249,12 @@ def _gdbserver_port(gdbserver, ssh):
# Process /bin/bash created; pid = 14366
# Listening on port 34816
process_created = gdbserver.recvline()
if process_created.startswith('ERROR:'):
raise ValueError(
'Failed to spawn process under gdbserver. gdbserver error message: %s' % process_created
)
gdbserver.pid = int(process_created.split()[-1], 0)
listening_on = ''
@@ -287,7 +293,7 @@ def _get_runner(ssh=None):
else: return tubes.process.process
@LocalContext
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
def debug(args, gdbscript=None, exe=None, ssh=None, env=None, sysroot=None, **kwargs):
"""debug(args) -> tube
Launch a GDB server with the specified command line,
@@ -299,6 +305,8 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
exe(str): Path to the executable on disk
env(dict): Environment to start the binary in
ssh(:class:`.ssh`): Remote ssh session to use to launch the process.
sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries
and Android targets.
Returns:
:class:`.process` or :class:`.ssh_channel`: A tube connected to the target process
@@ -404,7 +412,6 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
runner = _get_runner(ssh)
which = _get_which(ssh)
sysroot = None
gdbscript = gdbscript or ''
if context.noptrace:
@@ -416,11 +423,15 @@ def debug(args, gdbscript=None, exe=None, ssh=None, env=None, **kwargs):
else:
qemu_port = random.randint(1024, 65535)
qemu_user = qemu.user_path()
sysroot = qemu.ld_prefix(env)
sysroot = sysroot or qemu.ld_prefix(env)
if not qemu_user:
log.error("Cannot debug %s binaries without appropriate QEMU binaries" % context.arch)
args = [qemu_user, '-g', str(qemu_port)] + args
# Use a sane default sysroot for Android
if not sysroot and context.os == 'android':
sysroot = 'remote:/'
# Make sure gdbserver/qemu is installed
if not which(args[0]):
log.error("%s is not installed" % args[0])
@@ -478,7 +489,7 @@ def binary():
>>> gdb.binary() # doctest: +SKIP
'/usr/bin/gdb'
"""
gdb = misc.which('gdb')
gdb = misc.which('pwntools-gdb') or misc.which('gdb')
if not context.native:
multiarch = misc.which('gdb-multiarch')
@@ -508,6 +519,7 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
detect the architechture automatically (if it is supported).
gdb_args(list): List of additional arguments to pass to GDB.
sysroot(str): Foreign-architecture sysroot, used for QEMU-emulated binaries
and Android targets.
Returns:
PID of the GDB process (or the window which it is running in).
@@ -607,6 +619,10 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
if gdbscript and not gdbscript.endswith('\n'):
gdbscript += '\n'
# Use a sane default sysroot for Android
if not sysroot and context.os == 'android':
sysroot = 'remote:/'
# gdb script to run before `gdbscript`
pre = ''
if not context.native:
@@ -671,7 +687,15 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
exe = exe or target.executable
elif isinstance(target, tuple) and len(target) == 2:
host, port = target
pre += 'target remote %s:%d\n' % (host, port)
if context.os != 'android':
pre += 'target remote %s:%d\n' % (host, port)
else:
# Android debugging is done over gdbserver, which can't follow
# new inferiors (tldr; follow-fork-mode child) unless it is run
# in extended-remote mode.
pre += 'target extended-remote %s:%d\n' % (host, port)
pre += 'set detach-on-fork off\n'
def findexe():
for spid in proc.pidof(target):
@@ -699,7 +723,8 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
log.error('could not find target process')
if exe:
pre += 'file %s\n' % exe
# The 'file' statement should go first
pre = 'file %s\n%s' % (exe, pre)
cmd = binary()
@@ -731,7 +756,11 @@ def attach(target, gdbscript = None, exe = None, need_ptrace_scope = True, gdb_a
gdbserver = runner(gdb_cmd)
port = _gdbserver_port(gdbserver, None)
host = context.adb_host
pre += 'target remote %s:%i' % (context.adb_host, port)
pre += 'target extended-remote %s:%i\n' % (context.adb_host, port)
# gdbserver on Android sets 'detach-on-fork on' which breaks things
# when you're trying to debug anything that forks.
pre += 'set detach-on-fork off\n'
gdbscript = pre + (gdbscript or '')
@@ -759,7 +788,7 @@ def ssh_gdb(ssh, argv, gdbscript = None, arch = None, **kwargs):
argv = [argv]
exe = argv[0]
argv = ["gdbserver", "127.0.0.1:0"] + argv
argv = ["gdbserver", "--multi", "127.0.0.1:0"] + argv
# Download the executable
local_exe = os.path.basename(exe)
+9 -1
View File
@@ -233,4 +233,12 @@ class Call(object):
args.extend(map(repr, arg.values))
else:
args.append(arg)
return '%s(%s)' % (self.name or fmt % self.target, ', '.join(map(str, args)))
name = self.name or fmt & self.target
arg_str = []
for arg in args:
if isinstance(arg, (int,long)) and arg > 0x100:
arg_str.append(hex(arg))
else:
arg_str.append(str(arg))
return '%s(%s)' % (name, ', '.join(arg_str))
+3 -1
View File
@@ -105,7 +105,7 @@ Finally, let's build our ROP stack
>>> rop.write(c.STDOUT_FILENO, binary.symbols['flag'], 8)
>>> rop.exit()
>>> print rop.dump()
0x0000: 0x10000012 write(STDOUT_FILENO, 268435494, 8)
0x0000: 0x10000012 write(STDOUT_FILENO, 0x10000026, 8)
0x0004: 0x1000000e <adjust @0x18> add esp, 0x10; ret
0x0008: 0x1 arg0
0x000c: 0x10000026 flag
@@ -1156,6 +1156,8 @@ class ROP(object):
regs = set(regs or ())
for addr, gadget in self.gadgets.items():
addr_bytes = set(pack(gadget.address))
if addr_bytes & self._badchars: continue
if gadget.insns[-1] != 'ret': continue
if gadget.move < move: continue
if not (regs <= set(gadget.regs)): continue
@@ -1,7 +1,7 @@
<%
from pwnlib import shellcraft
%>
<%page args="filename, mode='O_RDONLY'"/>
<%page args="filename, flags='O_RDONLY', mode=0"/>
<%docstring>
Opens a file
</%docstring>
@@ -9,4 +9,4 @@ Opens a file
AT_FDCWD=-100
%>
${shellcraft.pushstr(filename)}
${shellcraft.syscall('SYS_openat', AT_FDCWD, 'sp', mode, 0)}
${shellcraft.syscall('SYS_openat', AT_FDCWD, 'sp', flags, mode)}
@@ -8,5 +8,5 @@ Opens a file and writes its contents to the specified file descriptor.
</%docstring>
${pushstr(filename)}
${syscall('SYS_open', 'rsp', 0, 'O_RDONLY')}
${syscall('SYS_open', 'rsp', 'O_RDONLY', 0)}
${syscall('SYS_sendfile', fd, 'rax', 0, 0x7fffffff)}
@@ -10,7 +10,7 @@ Args: [sock (imm/reg) = rbp]
after = common.label("after")
%>
/* dup() file descriptor ${sock} into stdin/stdout/stderr */
${dup}:
${amd64.mov('rbp', sock)}
@@ -15,5 +15,5 @@ Example:
</%docstring>
${arm.pushstr(filename)}
${arm.linux.open('sp', 0, int(constants.O_RDONLY))}
${arm.linux.open('sp', int(constants.O_RDONLY), 0)}
${arm.linux.sendfile(fd, 'r0', 0, 0x7fffffff)}
@@ -19,5 +19,5 @@ label = common.label("sendfile_loop")
%>
${sc.pushstr(filename)}
${sc.open('esp', 0, 'O_RDONLY')}
${sc.open('esp', 'O_RDONLY', 0)}
${sc.sendfile(fd, 'eax', 0, 0x7fffffff)}
@@ -11,6 +11,7 @@ Args: [sock (imm/reg) = ebp]
looplabel = common.label("loop")
%>
/* dup() file descriptor ${sock} into stdin/stdout/stderr */
${dup}:
${mov('ebx', sock)}
${mov('ecx', 3)}
@@ -22,5 +22,5 @@ label = common.label("sendfile_loop")
%>
${mips.pushstr(filename)}
${mips.open('$sp', 0, int(constants.O_RDONLY))}
${mips.open('$sp', int(constants.O_RDONLY), 0)}
${mips.sendfile(fd, '$v0', 0, 0x7fffffff)}
@@ -20,6 +20,6 @@ label = common.label("sendfile_loop")
%>
${thumb.pushstr(filename)}
${thumb.linux.open('sp', 0, constants.O_RDONLY)}
${thumb.linux.open('sp', constants.O_RDONLY, 0)}
${thumb.mov('r5', 'r0')}
${thumb.linux.sendfile(fd, 'r5', 0, 0x7fffffff)}
@@ -9,6 +9,7 @@ Args: [sock (imm/reg) = r6]
dup = common.label("dup")
looplabel = common.label("loop")
%>
/* dup() file descriptor ${sock} into stdin/stdout/stderr */
${dup}:
${mov('r1', 2)}
${mov('r7', 'SYS_dup2')}
+2 -1
View File
@@ -15,8 +15,9 @@ from pwnlib.tubes import listen
from pwnlib.tubes import process
from pwnlib.tubes import remote
from pwnlib.tubes import serialtube
from pwnlib.tubes import server
from pwnlib.tubes import sock
from pwnlib.tubes import ssh
from pwnlib.tubes import tube
__all__ = ['tube', 'sock', 'remote', 'listen', 'process', 'serialtube', 'ssh']
__all__ = ['tube', 'sock', 'remote', 'listen', 'process', 'serialtube', 'server', 'ssh']
+1 -1
View File
@@ -565,7 +565,7 @@ class process(tube):
#
# Create a duplicate so we can modify it safely
env = dict(os.environ if env is None else env)
env = (os.environ if env is None else env).copy()
for k,v in env.items():
if not isinstance(k, (str, unicode)):
+162
View File
@@ -0,0 +1,162 @@
from __future__ import absolute_import
import errno
import socket
import threading
from pwnlib.context import context
from pwnlib.log import getLogger
from pwnlib.tubes.sock import sock
from pwnlib.tubes.remote import remote
log = getLogger(__name__)
class server(sock):
r"""Creates an TCP or UDP-server to listen for connections. It supports
both IPv4 and IPv6.
Arguments:
port(int): The port to connect to.
Defaults to a port auto-selected by the operating system.
bindaddr(str): The address to bind to.
Defaults to ``0.0.0.0`` / `::`.
fam: The string "any", "ipv4" or "ipv6" or an integer to pass to :func:`socket.getaddrinfo`.
typ: The string "tcp" or "udp" or an integer to pass to :func:`socket.getaddrinfo`.
callback: A function to be started on incoming connections. It should take a :class:`pwnlib.tubes.remote` as its only argument.
Examples:
>>> s = server(8888)
>>> client_conn = remote('localhost', s.lport)
>>> server_conn = s.next_connection()
>>> client_conn.sendline('Hello')
>>> server_conn.recvline()
'Hello\n'
>>> def cb(r):
... client_input = r.readline()
... r.send(client_input[::-1])
...
>>> t = server(8889, callback=cb)
>>> client_conn = remote('localhost', t.lport)
>>> client_conn.sendline('callback')
>>> client_conn.recv()
'\nkcabllac'
"""
#: Local port
lport = 0
#: Local host
lhost = None
#: Socket type (e.g. socket.SOCK_STREAM)
type = None
#: Socket family
family = None
#: Socket protocol
protocol = None
#: Canonical name of the listening interface
canonname = None
#: Sockaddr structure that is being listened on
sockaddr = None
_accepter = None
def __init__(self, port=0, bindaddr = "0.0.0.0", fam = "any", typ = "tcp",
callback = None, blocking = False, *args, **kwargs):
super(server, self).__init__(*args, **kwargs)
port = int(port)
fam = {socket.AF_INET: 'ipv4',
socket.AF_INET6: 'ipv6'}.get(fam, fam)
fam = self._get_family(fam)
typ = self._get_type(typ)
if fam == socket.AF_INET6 and bindaddr == '0.0.0.0':
bindaddr = '::'
h = self.waitfor('Trying to bind to %s on port %d' % (bindaddr, port))
for res in socket.getaddrinfo(bindaddr, port, fam, typ, 0, socket.AI_PASSIVE):
self.family, self.type, self.proto, self.canonname, self.sockaddr = res
if self.type not in [socket.SOCK_STREAM, socket.SOCK_DGRAM]:
continue
h.status("Trying %s" % self.sockaddr[0])
listen_sock = socket.socket(self.family, self.type, self.proto)
listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_sock.bind(self.sockaddr)
self.lhost, self.lport = listen_sock.getsockname()[:2]
if self.type == socket.SOCK_STREAM:
listen_sock.listen(1)
break
else:
h.failure()
self.error("Could not bind to %s on port %d" % (bindaddr, port))
h.success()
self.sock = listen_sock
self.connections_waiting = threading.Event()
self.connections = []
def accepter():
while True:
h = self.waitfor('Waiting for connections on %s:%s' % (self.lhost, self.lport))
while True:
try:
if self.type == socket.SOCK_STREAM:
sock, rhost = listen_sock.accept()
else:
data, rhost = listen_sock.recvfrom(4096)
listen_sock.connect(rhost)
sock = listen_sock
sock.unrecv(data)
sock.settimeout(self.timeout)
break
except socket.error as e:
if e.errno == errno.EINTR:
continue
h.failure()
self.exception("Socket failure while waiting for connection")
sock = None
return
self.rhost, self.rport = rhost[:2]
r = remote(self.rhost, self.rport, sock = sock)
h.success('Got connection from %s on port %d' % (self.rhost, self.rport))
if callback:
if not blocking:
t = context.Thread(target = callback, args = (r,))
t.daemon = True
t.start()
else:
callback(r)
else:
self.connections.append(r)
if not self.connections_waiting.is_set():
self.connections_waiting.set()
self._accepter = context.Thread(target = accepter)
self._accepter.daemon = True
self._accepter.start()
def next_connection(self):
if not self.connections_waiting.is_set():
self.connections_waiting.wait()
conn = self.connections.pop(0)
if not self.connections:
self.connections_waiting.clear()
return conn
def close(self):
# since `close` is scheduled to run on exit we must check that we got
# a connection or the program will hang in the `join` call above
if self._accepter and self._accepter.is_alive():
return
super(server, self).close()
+4 -2
View File
@@ -874,6 +874,8 @@ os.chdir(%(cwd)r)
if env is not None:
os.environ.clear()
os.environ.update(env)
else:
env = os.environ
def is_exe(path):
return os.path.isfile(path) and os.access(path, os.X_OK)
@@ -961,7 +963,7 @@ except Exception:
%(func_src)s
apply(%(func_name)s, %(func_args)r)
os.execve(exe, argv, os.environ)
os.execve(exe, argv, env)
""" % locals()
script = script.strip()
@@ -1302,7 +1304,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
return misc.parse_ldd_output(data)
def _get_fingerprint(self, remote):
cmd = '(openssl sha256 || sha256 || sha256sum) 2>/dev/null < '
cmd = '(sha256 || sha256sum || openssl sha256) 2>/dev/null < '
cmd = cmd + sh_string(remote)
data, status = self.run_to_end(cmd)
+5
View File
@@ -190,6 +190,8 @@ def run_in_new_terminal(command, terminal = None, args = None):
variable), ``x-terminal-emulator`` is used.
- If tmux is detected (by the presence of the ``$TMUX`` environment
variable), a new pane will be opened.
- If GNU Screen is detected (by the presence of the ``$STY`` environment
variable), a new screen will be opened.
Arguments:
command (str): The command to run.
@@ -219,6 +221,9 @@ def run_in_new_terminal(command, terminal = None, args = None):
elif 'TMUX' in os.environ and which('tmux'):
terminal = 'tmux'
args = ['splitw']
elif 'STY' in os.environ and which('screen'):
terminal = 'screen'
args = ['-t','pwntools-gdb','bash','-c']
if not terminal:
log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
+1 -1
View File
@@ -1 +1 @@
__version__ = '3.11.0'
__version__ = '3.12.0'
+1 -1
View File
@@ -86,7 +86,7 @@ except Exception as e:
setup(
name = 'pwntools',
packages = find_packages(),
version = '3.11.0',
version = '3.12.0',
data_files = [('',
glob.glob('*.md') + glob.glob('*.txt')),
],