mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-09 01:59:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74f475170d | ||
|
|
0dc0a9acc0 | ||
|
|
774ed3b85e | ||
|
|
292b81af17 | ||
|
|
90056fbeca | ||
|
|
60df8107c0 | ||
|
|
7467402ecf | ||
|
|
80e12c7031 | ||
|
|
f4159bdad4 | ||
|
|
26e6bce81a | ||
|
|
6af09645e7 | ||
|
|
146348f514 | ||
|
|
fe6619b336 | ||
|
|
5ae13e8c8c | ||
|
|
67473560c7 | ||
|
|
1cb0502157 | ||
|
|
9604f7847e | ||
|
|
c888a8cbe0 | ||
|
|
9f9d884790 | ||
|
|
39bc5808ea | ||
|
|
31f7c61c1f | ||
|
|
2a73356c78 | ||
|
|
25c84daa8a | ||
|
|
3ff7a9e940 | ||
|
|
e50b978398 | ||
|
|
35789287d8 | ||
|
|
7a1ab829eb | ||
|
|
899baec704 | ||
|
|
932cb57bf9 | ||
|
|
6a61617c73 | ||
|
|
ab8879afa3 | ||
|
|
515f678f2d | ||
|
|
c3afdd681d | ||
|
|
5d537a6189 | ||
|
|
9ba966ddba | ||
|
|
6ea5722f5b | ||
|
|
4e24f665b8 | ||
|
|
34cedc96d8 | ||
|
|
e8065440c2 | ||
|
|
de34906b4c | ||
|
|
e89a9b66ac | ||
|
|
64acece61b | ||
|
|
bb9004c298 |
No files matched your search
+2
-1
@@ -1,4 +1,4 @@
|
||||
dist: precise
|
||||
dist: trusty
|
||||
language: python
|
||||
addons:
|
||||
apt:
|
||||
@@ -19,6 +19,7 @@ cache:
|
||||
- usr
|
||||
- /home/travis/virtualenv/python2.7.13/lib/python2.7/site-packages/
|
||||
- /home/travis/virtualenv/python2.7.13/bin/
|
||||
sudo: required
|
||||
python:
|
||||
- "2.7"
|
||||
before_install:
|
||||
|
||||
+28
-7
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||

|
||||
|
||||
[](https://docs.pwntools.com/)
|
||||
[](https://pypi.python.org/pypi/pwntools/)
|
||||
[](https://pypi.python.org/pypi/pwntools/)
|
||||
[](https://travis-ci.org/Gallopsled/pwntools)
|
||||
[](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
|
||||
[](https://twitter.com/pwntools)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
sphinxcontrib-napoleon
|
||||
sphinxcontrib-autoprogram==0.1.2
|
||||
sphinxcontrib-autoprogram==0.1.4
|
||||
isort
|
||||
paramiko>=1.15.2
|
||||
mako>=1.0.0
|
||||
@@ -14,3 +14,4 @@ pygments>=2.0
|
||||
sphinx_rtd_theme
|
||||
doc2dash
|
||||
coveralls
|
||||
sphinx<1.8.0
|
||||
@@ -24,8 +24,6 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
import pwnlib
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
.. testsetup:: *
|
||||
|
||||
from glob import glob
|
||||
from pwn import *
|
||||
|
||||
:mod:`pwnlib.elf.corefile` --- Core Files
|
||||
|
||||
@@ -27,7 +27,7 @@ This exposes a standard interface to talk to processes, sockets, serial ports,
|
||||
and all manner of things, along with some nifty helpers for common tasks.
|
||||
For example, remote connections via :mod:`pwnlib.tubes.remote`.
|
||||
|
||||
>>> conn = remote('ftp.ubuntu.org',21)
|
||||
>>> conn = remote('ftp.ubuntu.com',21)
|
||||
>>> conn.recvline() # doctest: +ELLIPSIS
|
||||
'220 ...'
|
||||
>>> conn.send('USER anonymous\r\n')
|
||||
|
||||
@@ -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:
|
||||
@@ -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 *
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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__)
|
||||
+61
-44
@@ -87,6 +87,7 @@ from pwnlib.tubes.process import process
|
||||
from pwnlib.tubes.ssh import ssh_channel
|
||||
from pwnlib.tubes.tube import tube
|
||||
from pwnlib.util.fiddling import b64d
|
||||
from pwnlib.util.fiddling import enhex
|
||||
from pwnlib.util.fiddling import unhex
|
||||
from pwnlib.util.misc import read
|
||||
from pwnlib.util.misc import write
|
||||
@@ -223,6 +224,8 @@ class Mapping(object):
|
||||
return self._core.read(item, 1)
|
||||
|
||||
def __contains__(self, item):
|
||||
if isinstance(item, Mapping):
|
||||
return (self.start <= item.start) and (item.stop <= self.stop)
|
||||
return self.start <= item < self.stop
|
||||
|
||||
def find(self, sub, start=None, end=None):
|
||||
@@ -472,6 +475,7 @@ class Corefile(ELF):
|
||||
>>> io = elf.process()
|
||||
>>> io.wait()
|
||||
>>> core = io.corefile
|
||||
[!] End of the stack is corrupted, skipping stack parsing (got: 4141414141414141)
|
||||
>>> core.argc, core.argv, core.env
|
||||
(0, [], {})
|
||||
>>> core.stack.data.endswith('AAAA')
|
||||
@@ -545,36 +549,40 @@ class Corefile(ELF):
|
||||
for segment in self.segments:
|
||||
if not isinstance(segment, elftools.elf.segments.NoteSegment):
|
||||
continue
|
||||
|
||||
|
||||
# Note that older versions of pyelftools (<=0.24) are missing enum values
|
||||
# for NT_PRSTATUS, NT_PRPSINFO, NT_AUXV, etc.
|
||||
# For this reason, we have to check if note.n_type is any of several values.
|
||||
for note in iter_notes(segment):
|
||||
# Try to find NT_PRSTATUS. Note that pyelftools currently
|
||||
# mis-identifies the enum name as 'NT_GNU_ABI_TAG'.
|
||||
# Try to find NT_PRSTATUS.
|
||||
if prstatus_type and \
|
||||
note.n_descsz == ctypes.sizeof(prstatus_type) and \
|
||||
note.n_type == 'NT_GNU_ABI_TAG':
|
||||
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRSTATUS'):
|
||||
self.NT_PRSTATUS = note
|
||||
self.prstatus = prstatus_type.from_buffer_copy(note.n_desc)
|
||||
|
||||
# Try to find NT_PRPSINFO
|
||||
# Note that pyelftools currently mis-identifies the enum name
|
||||
# as 'NT_GNU_BUILD_ID'
|
||||
if note.n_descsz == ctypes.sizeof(prpsinfo_type) and \
|
||||
note.n_type == 'NT_GNU_BUILD_ID':
|
||||
if prpsinfo_type and \
|
||||
note.n_descsz == ctypes.sizeof(prpsinfo_type) and \
|
||||
note.n_type in ('NT_GNU_ABI_TAG', 'NT_PRPSINFO'):
|
||||
self.NT_PRPSINFO = note
|
||||
self.prpsinfo = prpsinfo_type.from_buffer_copy(note.n_desc)
|
||||
|
||||
# Try to find NT_SIGINFO so we can see the fault
|
||||
if note.n_type == 0x53494749:
|
||||
if note.n_type in (0x53494749, 'NT_SIGINFO'):
|
||||
self.NT_SIGINFO = note
|
||||
self.siginfo = siginfo_type.from_buffer_copy(note.n_desc)
|
||||
|
||||
# Try to find the list of mapped files
|
||||
if note.n_type == constants.NT_FILE:
|
||||
if note.n_type in (constants.NT_FILE, 'NT_FILE'):
|
||||
with context.local(bytes=self.bytes):
|
||||
self._parse_nt_file(note)
|
||||
|
||||
# Try to find the auxiliary vector, which will tell us
|
||||
# where the top of the stack is.
|
||||
if note.n_type == constants.NT_AUXV:
|
||||
if note.n_type in (constants.NT_AUXV, 'NT_AUXV'):
|
||||
self.NT_AUXV = note
|
||||
with context.local(bytes=self.bytes):
|
||||
self._parse_auxv(note)
|
||||
|
||||
@@ -583,19 +591,13 @@ class Corefile(ELF):
|
||||
|
||||
if self.stack and self.mappings:
|
||||
for mapping in self.mappings:
|
||||
if mapping.stop == self.stack:
|
||||
if self.stack in mapping or self.stack == mapping.stop:
|
||||
mapping.name = '[stack]'
|
||||
self.stack = mapping
|
||||
break
|
||||
else:
|
||||
for mapping in self.mappings:
|
||||
if self.stack in mapping:
|
||||
mapping.name = '[stack]'
|
||||
self.stack = mapping
|
||||
break
|
||||
else:
|
||||
log.warn('Could not find the stack!')
|
||||
self.stack = None
|
||||
log.warn('Could not find the stack!')
|
||||
self.stack = None
|
||||
|
||||
with context.local(bytes=self.bytes, log_level='warn'):
|
||||
try:
|
||||
@@ -753,30 +755,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 +829,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:
|
||||
@@ -887,18 +897,25 @@ class Corefile(ELF):
|
||||
if not stack:
|
||||
return
|
||||
|
||||
# If the stack does not end with zeroes, something is very wrong.
|
||||
if not stack.data.endswith('\x00' * 8):
|
||||
log.warn_once("End of the stack is corrupted, skipping stack parsing (got: %s)",
|
||||
enhex(self.data[-8:]))
|
||||
return
|
||||
|
||||
# AT_EXECFN is the start of the filename, e.g. '/bin/sh'
|
||||
# Immediately preceding is a NULL-terminated environment variable string.
|
||||
# We want to find the beginning of it
|
||||
if self.at_execfn:
|
||||
address = self.at_execfn-1
|
||||
else:
|
||||
log.debug('No AT_EXECFN')
|
||||
if not self.at_execfn:
|
||||
address = stack.stop
|
||||
address -= 2*self.bytes
|
||||
address -= 1
|
||||
address = stack.rfind('\x00', None, address)
|
||||
address += 1
|
||||
self.at_execfn = address
|
||||
|
||||
address = self.at_execfn-1
|
||||
|
||||
|
||||
# Sanity check!
|
||||
try:
|
||||
|
||||
+13
-5
@@ -51,12 +51,17 @@ from elftools.elf.constants import P_FLAGS
|
||||
from elftools.elf.constants import SHN_INDICES
|
||||
from elftools.elf.descriptions import describe_e_type
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.enums import ENUM_P_TYPE
|
||||
from elftools.elf.gnuversions import GNUVerDefSection
|
||||
from elftools.elf.relocation import RelocationSection
|
||||
from elftools.elf.sections import SymbolTableSection
|
||||
from elftools.elf.segments import InterpSegment
|
||||
|
||||
# See https://github.com/Gallopsled/pwntools/issues/1189
|
||||
try:
|
||||
from elftools.elf.enums import ENUM_P_TYPE
|
||||
except ImportError:
|
||||
from elftools.elf.enums import ENUM_P_TYPE_BASE as ENUM_P_TYPE
|
||||
|
||||
import intervaltree
|
||||
|
||||
from pwnlib import adb
|
||||
@@ -262,9 +267,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':
|
||||
@@ -1763,4 +1772,3 @@ class ELF(ELFFile):
|
||||
return
|
||||
|
||||
log.error("Could not find PT_GNU_STACK, stack should already be executable")
|
||||
|
||||
+38
-9
@@ -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=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)
|
||||
|
||||
@@ -3,7 +3,7 @@ from os.path import basename
|
||||
|
||||
from docutils import nodes
|
||||
from docutils import statemachine
|
||||
from sphinx.util.compat import Directive
|
||||
from docutils.parsers.rst import Directive
|
||||
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
|
||||
+1
-1
@@ -363,7 +363,7 @@ class Logger(object):
|
||||
"""
|
||||
m = message % args
|
||||
if m not in self._one_time_warnings:
|
||||
if self.isEnabledFor(logging.INFO):
|
||||
if self.isEnabledFor(logging.WARNING):
|
||||
self._one_time_warnings.add(m)
|
||||
self._log(logging.WARNING, message, args, kwargs, 'warning_once')
|
||||
|
||||
|
||||
+9
-1
@@ -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))
|
||||
+30
-4
@@ -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
|
||||
@@ -168,6 +168,21 @@ requirements so that everything "just works".
|
||||
0x0078: 'faabgaab' <pad 0x8>
|
||||
0x0080: 0x10000008 target
|
||||
|
||||
Pwntools will also filter out some bad instructions while setting the registers
|
||||
( e.g. syscall, int 0x80... )
|
||||
|
||||
>>> assembly = 'syscall; pop rdx; pop rsi; ret ; pop rdi ; int 0x80; pop rsi; pop rdx; ret ; pop rdi ; ret'
|
||||
>>> binary = ELF.from_assembly(assembly)
|
||||
>>> rop = ROP(binary)
|
||||
>>> rop.call(0xdeadbeef, [1, 2, 3])
|
||||
>>> print rop.dump()
|
||||
0x0000: 0x1000000b pop rdi; ret
|
||||
0x0008: 0x1 [arg0] rdi = 1
|
||||
0x0010: 0x10000008 pop rsi; pop rdx; ret
|
||||
0x0018: 0x2 [arg1] rsi = 2
|
||||
0x0020: 0x3 [arg2] rdx = 3
|
||||
0x0028: 0xdeadbeef
|
||||
|
||||
ROP + Sigreturn
|
||||
-----------------------
|
||||
|
||||
@@ -495,15 +510,20 @@ class ROP(object):
|
||||
|
||||
regset = set(registers)
|
||||
|
||||
bad_instructions = set(('syscall', 'sysenter', 'int 0x80'))
|
||||
|
||||
# Collect all gadgets which use these registers
|
||||
# Also collect the "best" gadget for each combination of registers
|
||||
gadgets = []
|
||||
best_gadgets = {}
|
||||
|
||||
for gadget in self.gadgets.values():
|
||||
# Do not use gadgets which end in e.g. "int 0x80"
|
||||
# Do not use gadgets which doesn't end with 'ret'
|
||||
if gadget.insns[-1] != 'ret':
|
||||
continue
|
||||
# Do not use gadgets which contain 'syscall' or 'int'
|
||||
if set(gadget.insns) & bad_instructions:
|
||||
continue
|
||||
|
||||
touched = tuple(regset & set(gadget.regs))
|
||||
|
||||
@@ -512,10 +532,14 @@ class ROP(object):
|
||||
|
||||
old = best_gadgets.get(touched, gadget)
|
||||
|
||||
if old is gadget or old.move > gadget.move:
|
||||
# if we have a new gadget for the touched registers, choose it
|
||||
# if the new gadget requires less stack space, choose it
|
||||
# if both gadgets require same stack space, choose the one with less instructions
|
||||
if (old is gadget) \
|
||||
or (old.move > gadget.move) \
|
||||
or (old.move == gadget.move and len(old.insns) > len(gadget.insns)):
|
||||
best_gadgets[touched] = gadget
|
||||
|
||||
|
||||
winner = None
|
||||
budget = 999999999
|
||||
|
||||
@@ -1156,6 +1180,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
|
||||
|
||||
@@ -40,6 +40,8 @@ mips = {
|
||||
'$31': 31, '$ra': 31,
|
||||
}
|
||||
|
||||
mips_list = list(mips)
|
||||
|
||||
arm = map('r{}'.format, range(13))
|
||||
arm += ["sp", "lr", "pc", "cpsr"]
|
||||
|
||||
@@ -211,7 +213,7 @@ def current():
|
||||
'arm': arm,
|
||||
'thumb': arm,
|
||||
'aarch64': aarch64,
|
||||
'mips': mips,
|
||||
'mips': mips_list,
|
||||
'powerpc': powerpc
|
||||
}[context.arch]
|
||||
|
||||
|
||||
@@ -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)}
|
||||
@@ -1,4 +1,13 @@
|
||||
<% from pwnlib.shellcraft import mips %>
|
||||
<%docstring>Execute /bin/sh</%docstring>
|
||||
<%docstring>Execute /bin/sh
|
||||
|
||||
Example:
|
||||
|
||||
>>> p = run_assembly(shellcraft.mips.linux.sh())
|
||||
>>> p.sendline('echo Hello')
|
||||
>>> p.recv()
|
||||
'Hello\n'
|
||||
|
||||
</%docstring>
|
||||
|
||||
${mips.execve('//bin/sh', ['sh'], {})}
|
||||
@@ -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')}
|
||||
|
||||
@@ -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']
|
||||
@@ -434,11 +434,11 @@ class process(tube):
|
||||
# appropriate qemu binary to run it.
|
||||
qemu_path = qemu.user_path(arch=binary.arch)
|
||||
|
||||
if not qemu:
|
||||
if not qemu_path:
|
||||
raise exception
|
||||
|
||||
qemu_path = which(qemu_path)
|
||||
if qemu:
|
||||
if qemu_path:
|
||||
self._qemu = qemu_path
|
||||
|
||||
args = [qemu_path]
|
||||
@@ -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)):
|
||||
@@ -875,7 +875,7 @@ class process(tube):
|
||||
from pwnlib.elf import ELF
|
||||
|
||||
for lib, address in self.libs().items():
|
||||
if 'libc.so' in lib:
|
||||
if 'libc.so' in lib or 'libc-' in lib:
|
||||
e = ELF(lib)
|
||||
e.address = address
|
||||
return e
|
||||
@@ -931,7 +931,7 @@ class process(tube):
|
||||
|
||||
Example:
|
||||
|
||||
>>> e = ELF('/bin/sh')
|
||||
>>> e = ELF('/bin/bash')
|
||||
>>> p = process(e.path)
|
||||
|
||||
In order to make sure there's not a race condition against
|
||||
|
||||
@@ -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()
|
||||
+9
-5
@@ -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)
|
||||
|
||||
@@ -1617,7 +1619,6 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
|
||||
self.error('%r does not exist' % file_or_directory)
|
||||
|
||||
|
||||
def download(self, file_or_directory, local=None):
|
||||
"""download(file_or_directory, local=None)
|
||||
|
||||
@@ -1631,7 +1632,10 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
if not self.sftp:
|
||||
self.error("Cannot determine remote file type without SFTP")
|
||||
|
||||
if 0 == self.system('test -d ' + sh_string(file_or_directory)).wait():
|
||||
with self.system('test -d ' + sh_string(file_or_directory)) as io:
|
||||
is_dir = io.wait()
|
||||
|
||||
if 0 == is_dir:
|
||||
self.download_dir(file_or_directory, local)
|
||||
else:
|
||||
self.download_file(file_or_directory, local)
|
||||
@@ -1992,7 +1996,7 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
if value is not None:
|
||||
with open(path, 'w+') as f:
|
||||
f.write(value)
|
||||
else:
|
||||
elif os.path.exists(path):
|
||||
with open(path, 'r+') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
@@ -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.')
|
||||
|
||||
@@ -245,6 +245,8 @@ def status(pid):
|
||||
try:
|
||||
with open('/proc/%d/status' % pid) as fd:
|
||||
for line in fd:
|
||||
if ':' not in line:
|
||||
continue
|
||||
i = line.index(':')
|
||||
key = line[:i]
|
||||
val = line[i + 2:-1] # initial :\t and trailing \n
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = '3.11.0'
|
||||
__version__ = '3.12.2'
|
||||
@@ -45,7 +45,7 @@ for filename in glob.glob('pwnlib/commandline/*'):
|
||||
install_requires = ['paramiko>=1.15.2',
|
||||
'mako>=1.0.0',
|
||||
'pyelftools>=0.2.4',
|
||||
'capstone',
|
||||
'capstone>=3.0.5rc2', # See Gallopsled/pwntools#971, Gallopsled/pwntools#1160
|
||||
'ropgadget>=5.3',
|
||||
'pyserial>=2.7',
|
||||
'requests>=2.0',
|
||||
@@ -57,7 +57,8 @@ install_requires = ['paramiko>=1.15.2',
|
||||
'pypandoc',
|
||||
'packaging',
|
||||
'psutil>=3.3.0',
|
||||
'intervaltree',
|
||||
'intervaltree<3.0', # See Gallopsled/pwntools#1238
|
||||
'sortedcontainers<2.0', # See Gallopsled/pwntools#1154
|
||||
'unicorn']
|
||||
|
||||
# Check that the user has installed the Python development headers
|
||||
@@ -86,7 +87,7 @@ except Exception as e:
|
||||
setup(
|
||||
name = 'pwntools',
|
||||
packages = find_packages(),
|
||||
version = '3.11.0',
|
||||
version = '3.12.2',
|
||||
data_files = [('',
|
||||
glob.glob('*.md') + glob.glob('*.txt')),
|
||||
],
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ local_deb_extract()
|
||||
|
||||
install_deb()
|
||||
{
|
||||
version=${2:-zesty}
|
||||
version=${2:-artful}
|
||||
package=$1
|
||||
echo "Installing $package"
|
||||
INDEX="http://packages.ubuntu.com/en/$version/amd64/$package/download"
|
||||
|
||||
Reference in new issue
Block a user