Compare commits

...
48 Commits
Author SHA1 Message Date
Mathias Svensson eb38292c31 version bump to 2.1.2 2014-08-01 23:46:57 -07:00
Mathias Svensson 8e90ed06c8 Added README to the examples directory 2014-08-01 23:35:09 -07:00
Mathias Svensson 9807f0e120 Some pylinting 2014-08-01 23:04:00 -07:00
Mathias Svensson 1ed91a3667 Merge branch 'zachriggle-v2-elf' 2014-08-02 05:03:27 +02:00
Mathias Svensson 88d22014b5 Merge branch 'v2-elf' of https://github.com/zachriggle/pwntools into zachriggle-v2-elf
Conflicts:
	pwnlib/elf.py
2014-08-02 05:03:06 +02:00
Zach Riggle d55f10569b Additional documentation and doctests 2014-08-01 19:48:10 -07:00
Mathias Svensson 66db6eb3b9 Fixed #43 2014-08-02 03:27:52 +02:00
Mathias Svensson bb142c754f Merge branch 'zachriggle-v2-elf' 2014-08-02 02:11:18 +02:00
Mathias Svensson ae06ea7d56 Merged pull request 2014-08-02 02:10:35 +02:00
Mathias Svensson 6d62a1b979 Added ssh to toplevel 2014-08-02 02:00:13 +02:00
Mathias Svensson f38e8c7934 Added ssh to toplevel and fixed so that it now emulates xterm instead of vt100 2014-08-02 01:44:35 +02:00
Mathias Svensson a4305e5928 Added SSH module for tubes 2014-08-01 19:20:03 +02:00
Morten Brøns-Pedersen e4a6aac594 fixed off-by-one bug in pwnlib.term.term.render_cell() 2014-08-01 19:00:17 +02:00
Zach Riggle fffa749e28 Add execstack 2014-08-01 00:03:06 -07:00
Zach Riggle d6bc933759 Add three functions I hadn't reimplemented 2014-07-31 23:51:54 -07:00
Zach Riggle d80c0fc329 Pure-Python implementation of pwn.elf.ELF 2014-07-31 23:41:55 -07:00
Mathias Svensson 33065ff17a Added code from zachriggle's pull request #41 2014-08-01 07:46:37 +02:00
Mathias Svensson 9a0783e3db Fixed #40 2014-08-01 07:44:57 +02:00
Morten Brøns-Pedersen 528e2db39b added pwnlib.gdb to the docs 2014-07-31 14:31:49 +02:00
Morten Brøns-Pedersen b310e452fa docstring fix in pwnlib.gdb.attach(): added return value 2014-07-31 14:31:21 +02:00
Morten Brøns-Pedersen 5b11898b8d added documentation and some slight improvements to pwnlib.gdb.attach() 2014-07-31 14:15:02 +02:00
Morten Brøns-Pedersen 9653c20300 removed warning that only IPv4 is supported from docstring in pwnlib.util.proc.pidof 2014-07-31 14:04:45 +02:00
Morten Brøns-Pedersen 15a6540bca pwnlib.util.proc.pidof() now handles pwnlib.tubes.sock.sock objects rather than just pwnlib.tubes.remote.remote 2014-07-31 13:59:47 +02:00
Morten Brøns-Pedersen a0cc2d2304 bugfix: reverted bad import in pwn.toplevel 2014-07-30 22:38:26 +02:00
IdolfHatler c3125c7d15 Merge pull request #39 from zachriggle/patch-4
Documentation fix
2014-07-30 17:18:21 +02:00
Zach Riggle d97034f67b Documentation fix
![http://www.hdwallpapers.in/walls/why_so_serious-wide.jpg]
2014-07-30 10:40:22 -04:00
Morten Brøns-Pedersen 6ad0319ad7 added pwnlib.gdb to pwn toplevel 2014-07-30 15:55:23 +02:00
Morten Brøns-Pedersen 5d63390b0f added alpha version of pwnlib.gdb 2014-07-30 15:53:37 +02:00
Morten Brøns-Pedersen e77f6e8e04 added cmdline(), cwd() and pid_by_inode() to pwnlib.util.proc and added IPv6 support to pwnlib.util.proc.pidof() 2014-07-30 15:53:37 +02:00
Morten Brøns-Pedersen fa8b8cc7c1 added pwnlib.util.net to pwn toplevel 2014-07-30 15:53:37 +02:00
Morten Brøns-Pedersen a828642ce6 added pwnlib.util.misc.run_in_new_terminal() 2014-07-30 15:53:37 +02:00
Morten Brøns-Pedersen 3a27bfa23c slight optimization in pwnlib.util.splash.splash() 2014-07-30 15:53:37 +02:00
Mathias Svensson 683e94b447 Updated splash example 2014-07-30 12:47:13 +02:00
Morten Brøns-Pedersen ed5dec5397 cooler splash() "easteregg" 2014-07-30 11:24:37 +02:00
Mathias Svensson 208e4ac1d4 Alphabetical lists are better 2014-07-30 11:23:58 +02:00
Mathias Svensson 0e1fa51ad4 Updated README 2014-07-30 11:22:18 +02:00
Mathias Svensson 086972fc05 egg-files should not be committed! 2014-07-30 11:05:50 +02:00
Mathias Svensson 63a1762430 Updated .gitignore 2014-07-30 11:04:39 +02:00
Mathias Svensson a5bc72f179 Version bump and fixed some LICENSE.txt and setup.py issues 2014-07-30 11:02:05 +02:00
Mathias Svensson ce934a26e7 Updated the github links to use Gallopsled instead of pwnies 2014-07-30 09:54:36 +02:00
Mathias Svensson b6a0cbb641 Fixed some formatting issues in some shellcodes 2014-07-30 09:35:37 +02:00
Mathias Svensson 1083c4682c Documentation fix 2014-07-29 23:12:21 +02:00
Mathias Svensson e3d7ddac9d Fixed import ordering 2014-07-29 23:11:33 +02:00
Mathias Svensson e12be9308b Fixed bug in dup.asm 2014-07-29 22:54:13 +02:00
Mathias Svensson a0c2b06e44 Updated documentation and version handling a bit 2014-07-29 21:26:44 +02:00
Mathias Svensson 307386f1a8 Added a link to the documentation 2014-07-29 21:03:06 +02:00
Morten Brøns-Pedersen df76e03850 made pwnlib.util.splash.splash() better :D 2014-07-29 16:59:30 +02:00
Morten Brøns-Pedersen 8e9be618fc fixed docstring for pwnlib.ui.pause() 2014-07-29 16:58:26 +02:00
51 changed files with 1546 additions and 555 deletions

No files matched your search

+1
View File
@@ -8,3 +8,4 @@
build
MANIFEST
dist
pwntools.egg-info
+2 -3
View File
@@ -1,7 +1,6 @@
TL;DR version:
As far as we can tell, everything in pwntools is open source. Most is
under an MIT license, but a few pieces are under GPL or a BSD 2-clause
licence.
Everything in pwntools is open source. Most is under an MIT license, but a
few pieces are under GPL or a BSD 2-clause licence.
This license covers everything within this project, except for a few pieces
of code that we either did not write ourselves or which we derived from code
+1
View File
@@ -0,0 +1 @@
include *.txt
+9 -11
View File
@@ -1,8 +1,9 @@
# Introduction
This is the CTF framework used by pwnies in every CTF.
This is the CTF framework used by Gallopsled in every CTF.
Most code is inside the pwnlib folder with some functionality inside pwn or bin. It is typically used as:
Most code is inside the pwnlib folder with some functionality inside pwn or
bin. It is typically used as:
```python
from pwn import *
@@ -11,20 +12,17 @@ context(arch = 'i386', os = 'linux')
# EXPLOIT HERE
```
However we have made command-line frontends for much of the functionality
However we have made command-line frontends for some of the functionality
inside the pwnlib. These are:
* `asm`/`disasm`: Small wrapper for nasm
* `asm`/`disasm`: Small wrapper for various assemblers
* `constgrep`: Tool for finding constants defined in header files
* `cyclic`: De Bruijn sequence generator and lookup tool
* `hex`/`unhex`: Command line tools for doing common hexing/unhexing operations
* `shellcraft`: Frontend to our shellcode
We also have the following tools, not dependent on the pwnlib:
* binutils directory: Assemblers and disassemblers for various architectures
* `hex`/`unhex`: Command line tools for doing common hexing/unhexing operations
# Documentation
Our documentation is available on
Our documentation is available on http://pwntools.readthedocs.org/
# Installation
Pwntools is available as a pip package. You can install it by running
@@ -32,7 +30,7 @@ Pwntools is available as a pip package. You can install it by running
Alternatively if you prefer to have the latest version in git, you can
simply clone this repository, run `pip install -r requirements.txt`
and entries in your `PATH` and `PYTHONPATH` variables. The script
and add entries in your `PATH` and `PYTHONPATH` variables. The script
`install_local.sh` will help you do so, in case you are using bash.
# Contact
+4 -4
View File
@@ -71,9 +71,9 @@ copyright = u'2014, Gallopsled et al'
# built documents.
#
# The short X.Y version.
version = '2.0'
# The full version, including alpha/beta/rc tags.
release = '2.0'
import pwnlib
release = pwnlib.__version__
version = release.rsplit('.', 1)[0]
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
@@ -307,4 +307,4 @@ def linkcode_resolve(domain, info):
filename += '#L%d-%d' % (first, first + len(lines) - 1)
except IOError:
pass
return "https://github.com/pwnies/pwntools/blob/master/%s" % filename
return "https://github.com/Gallopsled/pwntools/blob/%s/%s" % (release, filename)
+8
View File
@@ -0,0 +1,8 @@
.. testsetup:: *
from pwnlib import context, constants
:mod:`pwnlib.constants` --- Easy access to header file constants
================================================================
.. automodule:: pwnlib.constants
+5
View File
@@ -0,0 +1,5 @@
:mod:`pwnlib.dynelf` --- Resolving remote functions using leaks
===============================================================
.. automodule:: pwnlib.dynelf
:members:
+9
View File
@@ -0,0 +1,9 @@
.. testsetup:: *
from pwnlib.elf import *
:mod:`pwnlib.elf` --- Working with ELF binaries
===============================================
.. automodule:: pwnlib.elf
:members:
+5
View File
@@ -0,0 +1,5 @@
:mod:`pwnlib.gdb` --- Working with GDB
======================================
.. automodule:: pwnlib.gdb
:members:
+4
View File
@@ -9,8 +9,12 @@ Contents:
intro
asm
constants
context
dynelf
elf
exception
gdb
log
memleak
tubes
+11 -5
View File
@@ -1,14 +1,20 @@
.. testsetup:: *
from pwnlib import shellcraft
:mod:`pwnlib.shellcraft` --- Shellcode generation
=================================================
.. automodule:: pwnlib.shellcraft
.. TODO:
Write a guide to adding more shellcode.
Submodules
----------
.. toctree::
:glob:
shellcraft/*
Writing shellcode
-----------------
Foo bar
+9
View File
@@ -1,11 +1,20 @@
:mod:`pwnlib.shellcraft.i386` --- Shellcode for Intel 80386
===========================================================
:mod:`pwnlib.shellcraft.i386`
-----------------------------
.. automodule:: pwnlib.shellcraft.i386
:members:
:mod:`pwnlib.shellcraft.i386.linux`
-----------------------------------
.. automodule:: pwnlib.shellcraft.i386.linux
:members:
:mod:`pwnlib.shellcraft.i386.freebsd`
-------------------------------------
.. automodule:: pwnlib.shellcraft.i386.freebsd
:members:
+12 -2
View File
@@ -21,7 +21,6 @@
.. automodule:: pwnlib.tubes.sock
.. autoclass:: pwnlib.tubes.sock.sock()
:members: shutdown
:show-inheritance:
Processes
@@ -30,9 +29,20 @@
.. automodule:: pwnlib.tubes.process
.. autoclass:: pwnlib.tubes.process.process(args, shell = False, executable = None, env = None, timeout = 'default', log_level = INFO)
:members: kill, shutdown, poll, communicate
:members: kill, poll, communicate
:show-inheritance:
SSH
---
.. automodule:: pwnlib.tubes.ssh
.. autoclass:: pwnlib.tubes.ssh.ssh(user, host, port = 22, password = None, key = None, keyfile = None, proxy_command = None, proxy_sock = None, timeout = 'default', log_level = INFO)
:members:
.. autoclass:: pwnlib.tubes.ssh.ssh_channel()
:members: kill, poll, interactive
:show-inheritance:
Common functionality
+6
View File
@@ -0,0 +1,6 @@
While these examples should all work, they are not very representative of
the pwntools project.
We have a plan to create a separate repository with examples, primarily
exploits. Until we do so, we recommend new users to look at
http://pwntools.readthedocs.org, as this is a better overview of our features.
+10
View File
@@ -0,0 +1,10 @@
import pwn
pwn.splash()
pwn.pause()
h = pwn.log.waitfor("Inputting:")
while True:
l = raw_input('> ')
h.status(l.upper())
+9 -7
View File
@@ -3,8 +3,16 @@ from pwnlib import *
# Promote functions from these modules to toplevel
from pwnlib.asm import *
from pwnlib.dynelf import DynELF
from pwnlib.elf import ELF, load
from pwnlib.log_levels import *
from pwnlib.memleak import MemLeak
from pwnlib.tubes.listen import *
from pwnlib.tubes.process import *
from pwnlib.tubes.remote import *
from pwnlib.tubes.ssh import *
from pwnlib.ui import *
from pwnlib.util import crc, proc
from pwnlib.util import crc, net, proc
from pwnlib.util.cyclic import *
from pwnlib.util.fiddling import *
from pwnlib.util.hashes import *
@@ -13,12 +21,6 @@ from pwnlib.util.misc import *
from pwnlib.util.packing import *
from pwnlib.util.proc import pidof
from pwnlib.util.splash import *
from pwnlib.tubes.remote import *
from pwnlib.tubes.listen import *
from pwnlib.tubes.process import *
from pwnlib.memleak import MemLeak
from pwnlib.elf import ELF, load
from pwnlib.dynelf import DynELF
# Promote these modules, so that "from pwn import *" will let you access them
import pwn, pwnlib, re, time, sys, os, string, threading, subprocess, struct
+8 -6
View File
@@ -1,10 +1,12 @@
__all__ = [
'asm', 'constants', 'context', 'dynelf',
'elf', 'exception', 'log', 'memleak',
'shellcraft', 'term', 'tubes', 'ui',
'util'
'elf', 'exception', 'gdb', 'log_levels',
'log', 'memleak', 'shellcraft', 'term',
'tubes', 'ui', 'util'
]
from . import asm, constants, context, dynelf
from . import elf, exception, log, memleak
from . import shellcraft, term, tubes, ui
from . import util
from . import elf, exception, gdb, log_levels
from . import log, memleak, shellcraft, term
from . import tubes, ui, util
from .version import __version__
+35
View File
@@ -1,3 +1,37 @@
"""Module containing constants extracted from header files.
The purpose of this module is to provide quick access to constants from
different architectures and operating systems.
Example:
>>> print constants.freebsd.SYS_stat
188
>>> print constants.linux.i386.SYS_stat
106
>>> print constants.linux.amd64.SYS_stat
4
The submodule ``freebsd`` contains all constants for FreeBSD, while the
constants for Linux have been split up by architecture.
The variables of the submodules will be "lifted up" by setting the
:data:`pwnlib.context.arch` or :data:`pwnlib.context.os` in a manner similar to
what happens in :mod:`pwnlib.shellcraft`.
Example:
>>> with context.local(os = 'freebsd'):
... print constants.SYS_stat
188
>>> with context.local(os = 'linux', arch = 'i386'):
... print constants.SYS_stat
106
>>> with context.local(os = 'linux', arch = 'amd64'):
... print constants.SYS_stat
4
"""
from types import ModuleType
import importlib, sys
from .. import context
@@ -8,6 +42,7 @@ class module(ModuleType):
# Insert nice properties
self.__dict__.update({
'__doc__': __doc__,
'__file__': __file__,
'__package__': __package__,
'__path__': __path__,
+16 -3
View File
@@ -21,6 +21,8 @@ __possible__ = {
'cris', 'i386', 'm68k', 'mips',
'mipsel', 'powerpc', 'thumb'
),
'arch32': ('arm', 'armeb', 'cris', 'i386', 'm68k', 'mips', 'mipsel', 'thumb'),
'arch64': ('alpha', 'amd64'),
'os': ('linux', 'freebsd')
}
@@ -116,9 +118,15 @@ class ContextModule(types.ModuleType):
* ``mipsel``
* ``powerpc``
* ``thumb``
Setting this will also update :data:`pwnlib.context.word_size`.
"""
if value in self.__possible__['arch']:
if value in self.__possible__['arch32']:
self.word_size = 32
elif value in self.__possible__['arch64']:
self.word_size = 64
return value
@_validator
@@ -203,6 +211,12 @@ class ContextModule(types.ModuleType):
'as the value %r did not validate' % value
)
def __dir__(self):
res = set(dir(super(ContextModule, self))) | set(properties().keys())
if self.defaults:
res |= set(dir(self.defaults))
return sorted(res)
class MainModule(types.ModuleType):
'''The module for thread-local context variables.
@@ -356,14 +370,13 @@ dictionary, the global dictionary is queried. If it has no results either,
value from :mod:`pwnlib.context.defaults` "shine through".'''
ctx = self._thread_ctx()
for k in dir(ctx):
if k[0] == '_' and k[:2] != '__':
if k[0] == '_' and k[:2] != '__' and hasattr(ctx, k):
delattr(ctx, k)
def __dir__(self):
res = set(self.__dict__.keys()) | set(dir(self.defaults))
res = set(dir(super(MainModule, self))) | set(dir(self._thread_ctx()))
return sorted(res)
# prevent this scope from being GC'ed
tether = sys.modules[__name__]
context = MainModule()
+5 -2
View File
@@ -24,7 +24,7 @@ def gnu_hash(s):
h = h * 33 + ord(c)
return h & 0xffffffff
class DynELF:
class DynELF(object):
"""DynELF is a tool for finding symbol addresses by leaking data from the .dynsym section.
Args:
@@ -45,7 +45,10 @@ class DynELF:
self.PIE = (self.elf.elftype == 'DYN')
self.base = base
if self.PIE is False and self.base is None:
self.base = filter(lambda x: x['type'] == 'LOAD' and 'E' in x['flg'], self.elf.segments)[0]['virtaddr']
for x in self.elf.segments:
if x['type'] == 'LOAD' and 'E' in x['flg']:
self.base = x['virtaddr']
break
if self.base is None:
log.error('Position independent ELF needs a base address')
+385 -286
View File
@@ -1,320 +1,419 @@
"""Exposes functionality for manipulating ELF files
"""
from . import log
from .util import lists
import re, subprocess, os, types
import mmap, subprocess
from os.path import abspath
from elftools.elf.elffile import ELFFile
from elftools.elf.sections import SymbolTableSection
from elftools.elf.descriptions import describe_e_type
from elftools.elf.constants import P_FLAGS
# readelf/objdump binaries
_READELF = '/usr/bin/readelf'
_OBJDUMP = '/usr/bin/objdump'
def _check(f):
if not (os.access(f, os.X_OK) and os.path.isfile(f)):
log.error('Executable %s needed for readelf.py, please install binutils' % f)
_check(_READELF)
_check(_OBJDUMP)
def load(*args, **kwargs):
"""Compatibility wrapper for pwntools v1"""
return ELF(*args, **kwargs)
def symbols(path):
"""symbols(path) -> dict
class ELF(ELFFile):
"""Encapsulates information about an ELF file.
Returns a dictionary with all symbols in the given file
:ivar path: Path to the binary on disk
:ivar symbols: Dictionary of {name: address} for all symbols in the ELF
:ivar plt: Dictionary of {name: address} for all functions in the PLT
:ivar got: Dictionary of {name: address} for all function pointers in the GOT
:ivar libs: Dictionary of {path: address} for each shared object required to load the ELF
Example:
.. code-block:: python
bash = ELF('/bin/bash')
hex(bash.symbols['read'])
# 0x41dac0
hex(bash.plt['read'])
# 0x41dac0
u32(bash.read(bash.got['read'], 4))
# 0x41dac6
print disasm(bash.read(bash.plt['read'],16), arch='amd64')
# 0: ff 25 1a 18 2d 00 jmp QWORD PTR [rip+0x2d181a] # 0x2d1820
# 6: 68 59 00 00 00 push 0x59
# b: e9 50 fa ff ff jmp 0xfffffffffffffa60
"""
symbols = {}
# -s : symbol table
cmd = [_READELF, '-s', path]
out = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
field = r'\s+(\S+)'
cond_field = r'(\s+\S+)?'
lines = re.findall(r'^\s+\d+:' + field * 7 + cond_field + '$', out, re.MULTILINE)
for addr, size, type, _bind, _vis, _ndx, name, _foo in lines:
addr = int(addr, 16)
size = int(size, 10)
if addr != 0 and name != '':
symbols[name] = {'addr': addr,
'size': size,
'type': type,
}
return symbols
class ELF:
"""A parsed ELF file.
The parser is very much a temporary one, until a better one is written."""
__cache = {}
def __init__(self, path):
path = os.path.realpath(path)
if path in ELF.__cache:
log.warning('Loaded "%s" again; use "elf.load(...)" to avoid this' \
% os.path.basename(path))
log.waitfor('Loading ELF file `%s\'' % os.path.basename(path))
self.segments = []
self.sections = {}
self.symbols = {}
self.plt = {}
self.got = {}
self.libs = {}
self.elfclass = None
self.elftype = None
self._data = None
self.execstack = False
# elftools uses the backing file for all reads and writes
# in order to permit writing without being able to write to disk,
# mmap() the file.
self.file = open(path,'rb')
self.mmap = mmap.mmap(self.file.fileno(), 0, access=mmap.ACCESS_COPY)
if not (os.access(path, os.R_OK) and os.path.isfile(path)):
log.error('File %s is not readable or does not exist' % path)
super(ELF,self).__init__(self.mmap)
self._path = path
self.path = abspath(path)
self._load_elfclass()
self._load_segments()
self._load_sections()
self._load_symbols()
self._load_libs()
# this is a nasty hack until we get our pure python elf parser
# we'll just have to live without PLT and GOT info for PICs until then
if not re.match(r'\.so(\.\d+)?$', path):
try:
self._load_plt_got()
except:
pass
if self.execstack:
log.status('\nStack is executable!')
log.done_success()
ELF.__cache[path] = self
self._populate_got_plt()
self._populate_symbols()
self._populate_libraries()
def _load_elfclass(self):
# -h : ELF header
cmd = [_READELF, '-h', self._path]
out = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
self.elfclass = re.findall(r'Class:\s*(.*$)', out, re.MULTILINE)[0]
self.elftype = re.findall(r'Type:\s*(.*$)', out, re.MULTILINE)[0].split(' ')[0]
self._address = min(filter(bool, (s.header.p_vaddr for s in self.segments)))
def _load_segments(self):
# -W : Wide output
# -l : Program headers
cmd = [_READELF, '-W', '-l', self._path]
out = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
hexint = '(0x[0-9a-f]+)'
numfield = r'\s+' + hexint
flgfield = r'\s+([RWE ]{3})'
lines = re.findall(r'^\s+([A-Z_]+)' + numfield * 5 + flgfield + numfield,
out, re.MULTILINE)
for type, off, vaddr, paddr, filesiz, memsiz, flg, align in lines:
off = int(off, 16)
vaddr = int(vaddr, 16)
paddr = int(paddr, 16)
filesiz = int(filesiz, 16)
memsiz = int(memsiz, 16)
flg = flg.replace(' ', '')
align = int(align, 16)
self.segments.append({'type' : type,
'offset' : off,
'virtaddr': vaddr,
'physaddr': paddr,
'filesiz' : filesiz,
'memsiz' : memsiz,
'flg' : flg,
'align' : align,
})
if type == 'GNU_STACK' and 'E' in flg:
for seg in self.executable_segments:
if seg.header.p_type == 'PT_GNU_STACK':
self.execstack = True
self.segments.sort(key = lambda x: x['virtaddr'])
log.info('Stack is executable!')
def _load_sections(self):
# -W : Wide output
# -S : Section headers
cmd = [_READELF, '-W', '-S', self._path]
out = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
field = r'\s+(\S+)'
posint = r'[123456789]\d*'
flags = r'\s+([WAXMSILGTExOop]*)'
lines = re.findall(r'^\s+\[\s*' + posint + r'\]' + field * 6 + flags,
out, re.MULTILINE)
@property
def elfclass(self):
"""ELF class (32 or 64).
for name, _type, addr, off, size, _es, flgs in lines:
addr = int(addr, 16)
off = int(off, 16)
size = int(size, 16)
self.sections[name] = {'addr' : addr,
'offset': off,
'size' : size,
'flags' : flgs,
}
.. note::
Set during ``ELFFile._identify_file``
"""
return self._elfclass
def _load_symbols(self):
self.symbols = symbols(self._path)
@elfclass.setter
def elfclass(self, newvalue):
self._elfclass = newvalue
def _load_libs(self):
dat = ''
try:
dat = subprocess.check_output(['ldd', self._path])
except subprocess.CalledProcessError:
pass
@property
def elftype(self):
"""ELF type (EXEC, DYN, etc)"""
return describe_e_type(self.header.e_type).split()[0]
self.libs = parse_ldd_output(dat)
@property
def segments(self):
"""A list of all segments in the ELF"""
return list(self.iter_segments())
def extra_libs(self, libs):
for v, k in libs.items():
self.libs[v] = k
@property
def sections(self):
"""A list of all sections in the ELF"""
return list(self.iter_sections())
# this is crazy slow -- include this feature in the all-python ELF parser
def _load_plt_got(self):
cmd = [_OBJDUMP, '-d', self._path]
out = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
got32 = r'[^j]*jmp\s+\*0x(\S+)'
got64 = r'[^#]*#\s+(\S+)'
lines = re.findall(r'([a-fA-F0-9]+)\s+<([^@<]+)@plt>:(%s|%s)' % (got32, got64), out)
@property
def dwarf(self):
"""DWARF info for the elf"""
return self.get_dwarf_info()
for addr, name, _, gotaddr32, gotaddr64 in lines:
addr = int(addr, 16)
gotaddr = int(gotaddr32 or gotaddr64, 16)
self.plt[name] = addr
self.got[name] = gotaddr
@property
def address(self):
"""Address of the lowest segment loaded in the ELF.
When updated, cascades updates to segment vaddrs, section addrs, symbols, plt, and got.
def _load_data(self):
if self._data is None:
with open(self._path, 'r') as f:
self._data = list(f.read())
>>> bash = ELF('/bin/sh')
>>> old = bash.symbols['read']
>>> bash.address += 0x1000
>>> bash.symbols['read'] == old + 0x1000
True
"""
return self._address
def read_symbol(self, name):
if name in self.symbols:
sym = self.symbols[name]
addr = sym['addr']
size = sym['size']
data = self.read(addr, size)
if data is None:
log.error('Symbol %s does not live in any section' % name)
else:
return data
else:
log.error('No symbol named %s' % name)
@address.setter
def address(self, new):
delta = new-self._address
update = lambda x: x+delta
for segment in self.segments:
segment.header.p_vaddr += delta
for section in self.sections:
section.header.sh_addr += delta
self.symbols = {k:update(v) for k,v in self.symbols.items()}
self.plt = {k:update(v) for k,v in self.plt.items()}
self.got = {k:update(v) for k,v in self.got.items()}
self._address = update(self.address)
def section(self, name):
if name in self.sections:
self._load_data()
sec = self.sections[name]
offset = sec['offset']
size = sec['size']
return ''.join(self._data[offset:offset + size])
else:
log.error('No section named %s' % name)
"""Gets data for the named section
def _filter_segments(self, flg, negate = False):
self._load_data()
for seg in self.segments:
if (flg in seg['flg']) ^ negate:
off = seg['offset']
siz = seg['filesiz']
addr = seg['virtaddr']
yield (''.join(self._data[off : off + siz]), addr)
Args:
name(str): Name of the section
Returns:
String containing the bytes for that section
"""
return self.get_section_by_name(name).data()
@property
def executable_segments(self):
return self._filter_segments('E', False)
"""Returns: list of all segments which are executable."""
return [s for s in self.segments if s.header.p_flags & P_FLAGS.PF_X]
@property
def writable_segments(self):
"""Returns: list of all segments which are writeable"""
return [s for s in self.segments if s.header.p_flags & P_FLAGS.PF_W]
@property
def non_writable_segments(self):
return self._filter_segments('W', True)
"""Returns: list of all segments which are NOT writeable"""
return [s for s in self.segments if not s.header.p_flags & P_FLAGS.PF_W]
def read(self, addr, numb):
self._load_data()
out = []
for seg in self.segments:
if seg['virtaddr'] > addr:
return
if seg['virtaddr'] + seg['filesiz'] > addr:
n = min(numb, seg['virtaddr'] + seg['filesiz'] - addr)
off = seg['offset'] + addr - seg['virtaddr']
out += self._data[off:off + n]
numb -= n
addr += n
if numb == 0:
break
return ''.join(out)
def _populate_libraries(self):
"""
>>> from os.path import exists
>>> bash = ELF('/bin/bash')
>>> all(map(exists, bash.libs.keys()))
True
>>> any(map(lambda x: 'libc' in x, bash.libs.keys()))
True
"""
self.libs = ldd(self.path)
def write(self, addr, repl):
self._load_data()
numb = len(repl)
for seg in self.segments:
if seg['virtaddr'] > addr:
return
if seg['virtaddr'] + seg['filesiz'] > addr:
n = min(numb, seg['virtaddr'] + seg['filesiz'] - addr)
off = seg['offset'] + addr - seg['virtaddr']
self._data[off:off + n] = repl[:n]
repl = repl[n:]
numb -= n
addr += n
if numb == 0:
break
def _populate_symbols(self):
"""
>>> bash = ELF('/bin/bash')
>>> bash.symbols['_start'] == bash.header.e_entry
True
"""
# By default, have 'symbols' include everything in the PLT.
#
# This way, elf.symbols['write'] will be a valid address to call
# for write().
self.symbols = dict(self.plt)
def search(self, s, non_writable = False):
self._load_data()
for seg in self.segments:
if 'W' in seg['flg'] and non_writable: continue
off = seg['offset']
siz = seg['filesiz']
dat = self._data[off : off + siz]
yield map(lambda i: i + seg['virtaddr'], lists.findall(dat, list(s)))
def replace(self, s, repl, non_writable = False, padding = '\x90'):
self._load_data()
for seg in self.segments:
if 'W' in seg['flg'] and non_writable: continue
off = seg['offset']
siz = seg['filesiz']
dat = self._data[off : off + siz]
for idx in lists.findall(dat, list(s)):
addr = idx + seg['virtaddr']
if isinstance(repl, types.FunctionType):
rep = repl(addr, s)
else:
rep = repl
if rep is None: continue
rep = rep.ljust(len(s), padding)
if len(rep) > len(s):
log.error('Replacement is larger than the replaced')
self._data[off + idx : off + idx + len(s)] = rep
def save(self, path):
with open(path, 'w') as fd:
fd.write(self.get_data())
def get_data(self):
self._load_data()
return ''.join(self._data)
def load(path):
"""load(path) -> ELF object
Load an ELF file.
"""
path = os.path.realpath(path)
if path in ELF.__cache:
return ELF.__cache[path]
return ELF(path)
def parse_ldd_output(data):
expr = re.compile(r'(?:([^ ]+) => )?([^(]+)?(?: \(0x[0-9a-f]+\))?$')
res = {}
for line in data.strip().split('\n'):
line = line.strip()
if not line:
continue
parsed = expr.search(line)
if not parsed:
log.warning('Could not parse line: "%s"' % line)
name, resolved = parsed.groups()
if resolved and re.search('/ld-[^/]*$', resolved):
if name != None:
resolved = name
name = 'ld'
if name == None:
if re.search('^linux', resolved):
name = 'linux'
else:
log.warning('Could not parse line: "%s"' % line)
for section in self.sections:
if not isinstance(section, SymbolTableSection):
continue
res[name] = resolved
if name.startswith('libc.so.'):
res['libc'] = resolved
return res
for symbol in section.iter_symbols():
if not symbol.entry.st_value:
continue
self.symbols[symbol.name] = symbol.entry.st_value
def _populate_got_plt(self):
"""Loads the GOT and the PLT symbols and addresses.
The following doctest checks the valitidy of the addresses.
This assumes that each GOT entry points to its PLT entry,
usually +6 bytes but could be anywhere within 0-16 bytes.
>>> from pwnlib.util.packing import unpack
>>> bash = ELF('/bin/bash')
>>> def validate_got_plt(sym):
... got = bash.got[sym]
... plt = bash.plt[sym]
... got_addr = unpack(bash.read(got, bash.elfclass/8), bash.elfclass)
... return got_addr in range(plt,plt+0x10)
...
>>> all(map(validate_got_plt, bash.got.keys()))
True
"""
plt = self.get_section_by_name('.plt')
got = self.get_section_by_name('.got')
# Find the relocation section for PLT
rel_plt = next(s for s in self.sections if s.header.sh_info == self.sections.index(plt))
# Find the symbols for the relocation section
sym_rel_plt = self.sections[rel_plt.header.sh_link]
self.got = {}
self.plt = {}
# Populate the GOT
for rel in rel_plt.iter_relocations():
sym_idx = rel.entry.r_info_sym
symbol = sym_rel_plt.get_symbol(sym_idx)
name = symbol.name
self.got[name] = rel.entry.r_offset
# Based on the ordering of the GOT symbols, populate the PLT
for i,(addr,name) in enumerate(sorted((addr,name) for name, addr in self.got.items())):
self.plt[name] = plt.header.sh_addr + (i+1)*plt.header.sh_addralign
def search(self, s, non_writable = False):
"""Search the ELF's virtual address space for the specified string.
Args:
s(str): String to search for
non_writable(bool): Search non-writable sections
Returns:
An iterator for each virtual address that matches
Examples:
>>> bash = ELF('/bin/bash')
>>> bash.address + 1 == next(bash.search('ELF',True))
True
"""
if non_writable:
segments = self.segments
else:
segments = self.writable_segments
for seg in segments:
addr = seg.header.p_vaddr
data = seg.data()
offset = 0
while True:
offset = data.find(s, offset)
if offset == -1:
break
yield addr + offset
offset += 1
def offset_to_vaddr(self, offset):
"""Translates the specified offset to a virtual address.
Args:
offset(int): Offset to translate
Returns:
Virtual address which corresponds to the file offset, or None
Examples:
>>> bash = ELF('/bin/bash')
>>> bash.address == bash.offset_to_vaddr(0)
True
"""
for segment in self.segments:
begin = segment.header.p_offset
size = segment.header.p_filesz
end = begin + size
if begin <= offset and offset <= end:
delta = offset - begin
return segment.header.p_vaddr + delta
return None
def vaddr_to_offset(self, address):
"""Translates the specified virtual address to a file address
Args:
address(int): Virtual address to translate
Returns:
Offset within the ELF file which corresponds to the address,
or None.
Examples:
>>> bash = ELF('/bin/bash')
>>> 0 == bash.vaddr_to_offset(bash.address)
True
"""
for segment in self.segments:
begin = segment.header.p_vaddr
size = segment.header.p_memsz
end = begin + size
if begin <= address and address <= end:
delta = address - begin
return segment.header.p_offset + delta
return None
def read(self, address, count):
"""Read data from the specified virtual address
Args:
address(int): Virtual address to read
count(int): Number of bytes to read
Returns:
A string of bytes, or None
Examples:
>>> bash = ELF('/bin/bash')
>>> bash.read(bash.address+1, 3)
'ELF'
"""
offset = self.vaddr_to_offset(address)
if offset is not None:
old = self.stream.tell()
self.stream.seek(offset)
data = self.stream.read(count)
self.stream.seek(old)
return data
return None
def write(self, address, data):
"""Writes data to the specified virtual address
Args:
address(int): Virtual address to write
data(str): Bytes to write
Note::
This routine does not check the bounds on the write to ensure
that it stays in the same segment.
Examples:
>>> bash = ELF('/bin/bash')
>>> bash.read(bash.address+1, 3)
'ELF'
>>> bash.write(bash.address, "HELO")
>>> bash.read(bash.address, 4)
'HELO'
"""
offset = self.vaddr_to_offset(address)
if offset is not None:
old = self.stream.tell()
self.stream.seek(offset)
self.stream.write(data)
self.stream.seek(old)
return None
def save(self, path):
"""Save the ELF to a file
>>> bash = ELF('/bin/bash')
>>> bash.save('/tmp/bash_copy')
>>> copy = file('/tmp/bash_copy')
>>> bash = file('/bin/bash')
>>> bash.read() == copy.read()
True
"""
old = self.stream.tell()
with open(path,'wb+') as fd:
self.stream.seek(0)
fd.write(self.get_data())
self.stream.seek(old)
def get_data(self):
"""Retrieve the raw data from the ELF file.
>>> bash = ELF('/bin/bash')
>>> fd = open('/bin/bash')
>>> bash.get_data() == fd.read()
True
"""
old = self.stream.tell()
self.stream.seek(0)
data = self.stream.read(self.stream.size())
self.stream.seek(old)
return data
def ldd(path):
"""Effectively runs 'ldd' on the specified binary, captures the output,
and parses it. Returns a dictionary of {path: address} for
each library required by the specified binary.
Args:
path(str): Path to the binary
Example:
>>> ldd('/bin/bash').keys()
['/lib/x86_64-linux-gnu/libc.so.6', '/lib/x86_64-linux-gnu/libtinfo.so.5', '/lib/x86_64-linux-gnu/libdl.so.2']
"""
import re
expr = re.compile(r'\s(\S?/\S+)\s+\((0x.+)\)')
libs = {}
output = subprocess.check_output([path], env={'LD_TRACE_LOADED_OBJECTS':'1'}).strip().splitlines()
output = map(str.strip, output)
output = map(expr.search, output)
for match in filter(None, output):
lib, addr = match.groups()
libs[lib] = int(addr,16)
return libs
+156
View File
@@ -0,0 +1,156 @@
import os, tempfile
from . import log
from .util import misc, proc
from . import tubes
def attach(target, execute = None, exe = None, arch = None):
"""attach(target, execute = None, exe = None, arch = None) -> None
Start GDB in a new terminal and attach to `target`.
:func:`pwnlib.util.proc.pidof` is used to find the PID of `target` except
when `target` is a ``(host, port)``-pair. In that case `target` is assumed
to be a GDB server.
If it is running locally and `exe` is not given we will try to find the path
of the target binary from parsing the command line of the program running
the GDB server (e.g. qemu or gdbserver). Notice that if the PID is known
(when `target` is not a GDB server) `exe` will be read from
``/proc/<pid>/exe``.
If `gdb-multiarch` is installed we use that or 'gdb' otherwise.
Args:
target: The target to attach to.
execute (str or file): GDB script to run after attaching.
exe (str): The path of the target binary.
arch (str): Architechture of the target binary. If `exe` known GDB will
detect the architechture automatically (if it is supported).
Returns:
:const:`None`
"""
# if execute is a file object, then read it; we probably need to run some
# more gdb script anyway
if execute:
if isinstance(execute, file):
fd = execute
execute = fd.read()
fd.close()
# gdb script to run before `execute`
pre = ''
if arch:
pre += 'set architechture %s\n' % arch
# let's see if we can find a pid to attach to
pid = None
if isinstance(target, (int, long)):
# target is a pid, easy peasy
pid = target
elif isinstance(target, str):
# pidof picks the youngest process
pids = proc.pidof(target)
if not pids:
log.error('no such process: %s' % target)
pid = pids[0]
log.info('attaching you youngest process "%s" (PID = %d)' %
(target, pid))
elif isinstance(target, tubes.sock.sock):
pids = proc.pidof(target)
if not pids:
log.error('could not find remote process (%s:%d) on this machine' %
target.sock.getpeername())
pid = pids[0]
elif isinstance(target, tubes.process.process):
pid = proc.pidof(target)[0]
elif isinstance(target, tuple) and len(target) == 2:
host, port = target
pre += 'target remote %s:%d\n' % (host, port)
def findexe():
# hm no PID then, but wait! we might not be totally out of luck yet: if
# the gdbserver is running locally and we know the program who is
# hosting it (e.g qemu, gdbserver) we can figure out the `exe` from the
# command line
# find inode of the listen socket
inode = None
# XXX: do a proper check to see if we're hosting the server
if host not in ('localhost', '127.0.0.1', '0.0.0.0',
'::1', 'ip6-localhost', '::'):
return
for f in ['tcp', 'tcp6']:
with open('/proc/net/%s' % f) as fd:
# skip the first line with the column names
fd.readline()
for line in fd:
line = line.split()
loc = line[1]
lport = int(loc.split(':')[1], 16)
st = int(line[3], 16)
if st != 10: # TCP_LISTEN, see include/net/tcp_states.h
continue
if lport == port:
inode = int(line[9])
break
if inode:
break
# if we didn't find the inode, there's nothing we can do about it
if not inode:
return
# find the process who owns the socket
spid = proc.pid_by_inode(inode)
if not spid:
return
# let's have a look at the server exe
sexe = proc.exe(spid)
name = os.path.basename(sexe)
# XXX: parse cmdline
if name.startswith('qemu-') or name.startswith('gdbserver'):
exe = proc.cmdline(spid)[-1]
return os.path.join(proc.cwd(spid), exe)
exe = exe or findexe()
else:
log.error("don't know how to attach to target: %r" % target)
# if we have a pid but no exe, just look it up in /proc/
if pid and not exe:
exe = proc.exe(pid)
if not pid and not exe:
log.error('could not find target process')
cmd = None
for p in ('gdb-multiarch', 'gdb'):
if misc.which(p):
cmd = p
break
if not cmd:
log.error('no gdb installed')
if exe:
if not os.path.isfile(exe):
log.error('no such file: %s' % exe)
cmd += ' "%s"' % exe
if pid:
cmd += ' %d' % pid
execute = pre + (execute or '')
if execute:
tmp = tempfile.NamedTemporaryFile(prefix = 'pwn', suffix = '.gdb',
delete = False)
tmp.write(execute)
tmp.close()
cmd += ' -x "%s" ; rm "%s"' % (tmp.name, tmp.name)
log.info('running in new terminal: %s' % cmd)
misc.run_in_new_terminal(cmd)
+1 -1
View File
@@ -303,7 +303,7 @@ def stub(string = '', exit_code = -1, log_level = log_levels.ERROR):
sys.exit(exit_code)
class _DummyHandle:
class _DummyHandle(object):
def update(self, _string):
pass
+1 -1
View File
@@ -1,6 +1,6 @@
from . import log
class MemLeak:
class MemLeak(object):
"""MemLeak is a caching and heuristic tool for exploiting memory leaks.
It can be used as a decorator, around functions of the form:
+9 -4
View File
@@ -17,18 +17,23 @@ class module(ModuleType):
# Save the shellcode directory
self._dir = directory
# Find the absolute path of the directory
self._absdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', self._dir)
# Get the docstring
with open(os.path.join(self._absdir, "__doc__")) as fd:
self.__doc__ = fd.read()
# Insert into the module list
sys.modules[self.__name__] = self
def __lazyinit__(self):
# Find the absolute path of the directory
absdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates', self._dir)
# Create a dictionary of submodules
self._submodules = {}
self._shellcodes = {}
for name in os.listdir(absdir):
path = os.path.join(absdir, name)
for name in os.listdir(self._absdir):
path = os.path.join(self._absdir, name)
if os.path.isdir(path):
self._submodules[name] = module(self.__name__ + '.' + name, os.path.join(self._dir, name))
elif os.path.isfile(path) and name != '__doc__':
+1 -4
View File
@@ -1,10 +1,7 @@
import re, os.path
import os
__all__ = ['make_function']
def relpath(path):
return
loaded = {}
lookup = None
def init_mako():
+15 -2
View File
@@ -1,2 +1,15 @@
Shellcraft is a module structure containing functions for generating
shellcode (in assembler format) for various architectures.
The shellcode module.
This module contains functions for generating shellcode.
It is organized first by architecture and then by operating system.
Example:
>>> print shellcraft.i386.nop()
nop
>>> print shellcraft.i386.linux.sh()
;; Clear eax, ecx, edx
xor ecx, ecx
imul ecx
...
@@ -1,19 +1,16 @@
<% from pwnlib.shellcraft import common %>
<% from socket import htons %>
<%page args="port, os = None"/>
<%page args="port"/>
<%docstring>
Args: port
Waits for a connection. Leaves socket in EBP.
os is either linux or freebsd. ipv4 only
ipv4 only
</%docstring>
<% acceptloop = common.label("acceptloop")
<% acceptloop = common.label("acceptloop")
accept = common.label("accept")
parent = common.label("parent")
%>
${acceptloop}:
;; Listens for and accepts a connection on ${int(port)}d forever
;; Socket file descriptor is placed in EBP
@@ -72,9 +69,3 @@ ${parent}:
pop ecx
test ecx, ecx
jnz ${accept}
@@ -1,18 +1,15 @@
<% from pwnlib.shellcraft import common %>
<% from socket import htons %>
<%page args="port, os = None"/>
<%page args="port"/>
<%docstring>
Args: port
Waits for a connection. Leaves socket in EBP.
os is either linux or freebsd. ipv4 only
ipv4 only
</%docstring>
<% acceptloop = common.label("acceptloop")
<% acceptloop = common.label("acceptloop")
looplabel = common.label("loop")
%>
${acceptloop}:
;; Listens for and accepts a connection on ${int(port)}d forever
;; Socket file descriptor is placed in EBP
@@ -75,6 +72,3 @@ ${looplabel}:
test edi, edi
jnz ${looplabel}
@@ -1,16 +1,13 @@
<% from pwnlib.shellcraft import common %>
<%page args="sock = None, os=None"/>
<%page args="sock = 'ebp'"/>
<%docstring>
Args: [sock (imm/reg) = ebp]
Duplicates sock to stdin, stdout and stderr
</%docstring>
<% dup = common.label("dup")
looplabel = common.label("loop")
<%
dup = common.label("dup")
looplabel = common.label("loop")
%>
${dup}:
mov ebx, ${sock}
push 3
@@ -22,4 +19,3 @@ ${looplabel}:
pop eax
int 0x80
jnz ${looplabel}
@@ -1,20 +1,15 @@
<% from pwnlib.shellcraft import common %>
<% from socket import htons %>
<%page args="port = None, os = None"/>
<%page args="port = None"/>
<%docstring>
Args: port (defaults to any port)
Finds a socket, which is connected to the specified port.
Leaves socket in ESI.
</%docstring>
<%
findpeer = common.label("findpeer")
looplabel = common.label("loop")
<%
findpeer = common.label("findpeer")
looplabel = common.label("loop")
%>
${findpeer}:
push -1
push SYS_socketcall_getpeername
@@ -22,7 +17,6 @@ ${findpeer}:
pop ebx
pop esi
${looplabel}:
push SYS_socketcall
pop eax
@@ -34,7 +28,7 @@ ${looplabel}:
pushad
int 0x80
% if port==None:
% if port == None:
test eax, eax
popad
pop edx
@@ -45,9 +39,4 @@ ${looplabel}:
shr eax, 16
cmp ax, ${htons(int(port))}
jne ${looplabel}
%endif
@@ -1,9 +1,9 @@
<% from pwnlib.shellcraft import i386 %>
<%page args="in_fd=0, size=255, allocate_stack=True"/>
<%page args="in_fd = 'ebp', size = 255, allocate_stack = True"/>
<%docstring> Reads to the stack from a directory.
Args:
in_fd (int/str): Directory to be read from. Defaults to `STDIN_FILENO`.
in_fd (int/str): File descriptor to be read from.
size (int): Buffer size.
allocate_stack (bool): allocate 'size' bytes on the stack.
@@ -1,6 +1,8 @@
<% from pwnlib.shellcraft import common %>
<%page args="cookie = 0x7afceb58"/>
<%docstring>
stackhunter(cookie = 0x7afceb58)
Returns an an egghunter, which searches from esp and upwards
for a cookie. However to save bytes, it only looks at a single
4-byte alignment. Use the function stackhunter_helper to
@@ -9,29 +11,16 @@
The default cookie has been chosen, because it makes it possible
to shave a single byte, but other cookies can be used too.
</%docstring>
<% stackhunter = common.label("stackhunter") %>
%if (cookie & 0xffffff) == 0xfceb58:
${stackhunter}:
cmp dword eax, ${hex(cookie)}
jne ${stackhunter}+1
jmp esp
%else:
${stackhunter}:
pop eax
cmp dword eax, ${hex(cookie)}
jne ${stackhunter}
jmp esp
%endif
+1
View File
@@ -64,6 +64,7 @@ def init():
global height, width
height = term.height
width = term.width
update_geometry()
term.on_winch.append(update_geometry)
readline.init()
term_mode = True
+5 -1
View File
@@ -348,9 +348,13 @@ keymap = km.Keymap({
'<any>' : handle_keypress,
})
def readline(size = None, prompt = '', float = False, priority = 10):
def readline(_size = None, prompt = '', float = False, priority = 10):
# The argument _size is unused, but is there for compatibility
# with the existing readline
global buffer_handle, prompt_handle, suggest_handle, eof, \
show_suggestions
show_suggestions = False
eof = False
if prompt:
+17 -4
View File
@@ -17,6 +17,12 @@ _graphics_mode = False
fd = sys.stdout
def show_cursor():
do('cnorm')
def hide_cursor():
do('civis')
def update_geometry():
global width, height
hw = fcntl.ioctl(fd.fileno(), termios.TIOCGWINSZ, '1234')
@@ -39,7 +45,7 @@ def handler_sigcont(signum, stack):
def setupterm():
global settings
update_geometry()
do('civis') # disable cursor
hide_cursor()
do('smkx') # keypad mode
if not settings:
settings = termios.tcgetattr(fd.fileno())
@@ -63,7 +69,7 @@ def setupterm():
def resetterm():
if settings:
termios.tcsetattr(fd.fileno(), termios.TCSADRAIN, settings)
do('cnorm')
show_cursor()
do('rmkx')
fd.write(' \x08') # XXX: i don't know why this is needed...
# only necessary when suspending the process
@@ -150,7 +156,7 @@ class Handle:
def delete(self):
delete(self.h)
STR, CSI, CRLF, BS, CR, SOH, STX = range(7)
STR, CSI, CRLF, BS, CR, SOH, STX, OOB = range(8)
def parse_csi(buf, offset):
i = offset
while i < len(buf):
@@ -232,6 +238,11 @@ def parse(s):
cmd, args, j = ret
x = (CSI, (cmd, args, ''.join(map(chr, buf[i : j]))))
i = j
elif c1 == ord(']'):
j = s.index('\x07', i)
if j > 0:
x = (OOB, s[i:j + 1])
i = j + 1
elif c1 in map(ord, '()'): # select G0 or G1
i += 3
continue
@@ -339,7 +350,7 @@ def render_cell(cell, clear_after = False):
col = 0
elif c == ord('G'):
n = n or 1
col = min(width - 1, n)
col = min(width - 1, n - 1)
elif c == ord('H') or c == ord('f'):
n = n or 1
m = m or 1
@@ -375,6 +386,8 @@ def render_cell(cell, clear_after = False):
put('\x01')
elif t == STX:
put('\x02')
elif t == OOB:
put(x)
if row >= height:
d = row - height + 1
scroll += d
+3 -3
View File
@@ -1,4 +1,4 @@
"""The pwnlib is not a big truck! It's a serious of tubes!
"""The pwnlib is not a big truck! It's a series of tubes!
This is our library for talking to sockets, processes, ssh connections etc.
Our goal is to be able to use the same API for e.g. remote TCP servers, local
@@ -10,6 +10,6 @@ just enough for the class to work and possibly code pertaining only to
that specific kind of tube.
"""
__all__ = ['tube', 'sock', 'remote', 'listen', 'process']
__all__ = ['tube', 'sock', 'remote', 'listen', 'process', 'ssh']
from . import tube, sock, remote, listen, process
from . import tube, sock, remote, listen, process, ssh
+14 -23
View File
@@ -38,37 +38,18 @@ class process(tube.tube):
self.close()
def shutdown(self, direction = "out"):
"""shutdown(direction = "out")
Closes one of the file descriptors to the process.
Args:
direction(str): Either the string "in" or "out".
"""
if direction == "out":
self.proc.stdin.close()
if direction == "in":
self.proc.stdout.close()
if False not in [self.proc.stdin.closed, self.proc.stdout.closed]:
self.close()
def poll(self):
"""poll() -> int
Poll the exit code of the process. Will return None, if the
process has not yet finished and the exit code otherwise.
"""
poll = self.proc.poll()
if poll != None and not self.stop_noticed:
self.proc.poll()
if self.proc.returncode != None and not self.stop_noticed:
self.stop_noticed = True
log.info("Program %r stopped with exit code %d" % (self.program, poll), log_level = self.log_level)
log.info("Program %r stopped with exit code %d" % (self.program, self.proc.returncode), log_level = self.log_level)
return poll
return self.proc.returncode
def communicate(self, stdin = None):
"""communicate(stdin = None) -> str
@@ -145,3 +126,13 @@ class process(tube.tube):
log.error("A stopped program does not have a file number")
return self.proc.stdout.fileno()
def shutdown(self, direction = "out"):
if direction == "out":
self.proc.stdin.close()
if direction == "in":
self.proc.stdout.close()
if False not in [self.proc.stdin.closed, self.proc.stdout.closed]:
self.close()
+57 -53
View File
@@ -9,41 +9,6 @@ class sock(tube.tube):
super(sock, self).__init__(timeout, log_level)
self.closed = {"in": False, "out": False}
# Functions only available on sock
def shutdown(self, direction = "out"):
"""shutdown(direction = "out")
Calls shutdown on the socket, and thus closing it for either reading or writing.
Args:
direction(str): Either the string "in" or "out".
"""
if direction == "out":
if not self.closed["out"]:
self.closed["out"] = True
try:
self.sock.shutdown(socket.SHUT_WR)
except socket.error as e:
if e.errno == errno.ENOTCONN:
pass
else:
raise
if direction == "in":
if not self.closed["in"]:
self.closed["in"] = True
try:
self.sock.shutdown(socket.SHUT_RD)
except socket.error as e:
if e.errno == errno.ENOTCONN:
pass
else:
raise
if False not in self.closed.values():
self.close()
# Overwritten for better usability
def recvall(self):
"""recvall() -> str
@@ -51,30 +16,33 @@ class sock(tube.tube):
Receives data until the socket is closed.
"""
if self.type == socket.SOCK_STREAM:
return super(sock, self).recvall()
else:
if hasattr(self, 'type') and self.type == socket.SOCK_DGRAM:
log.error("UDP sockets does not supports recvall")
else:
return super(sock, self).recvall()
# Implementation of the methods required for tube
def recv_raw(self, numb):
if self.closed["in"]:
raise EOFError
try:
data = self.sock.recv(numb)
except socket.timeout:
return None
except socket.error as e:
if e.errno == errno.EAGAIN:
go = True
while go:
go = False
try:
data = self.sock.recv(numb)
except socket.timeout:
return None
elif e.errno == errno.ECONNREFUSED:
self.shutdown("in")
raise EOFError
elif e.errno == errno.EINTR:
return ''
else:
raise
except IOError as e:
if e.errno == errno.EAGAIN:
return None
elif e.errno == errno.ECONNREFUSED:
self.shutdown("in")
raise EOFError
elif e.errno == errno.EINTR:
go = True
else:
raise
if data == '':
self.shutdown("in")
@@ -89,6 +57,12 @@ class sock(tube.tube):
try:
self.sock.sendall(data)
except socket.error as e:
if e.message == 'Socket is closed':
self.shutdown("out")
raise EOFError
else:
raise
except IOError as e:
if e.errno in [errno.EPIPE, errno.ECONNRESET, errno.ECONNREFUSED]:
self.shutdown("out")
raise EOFError
@@ -122,10 +96,40 @@ class sock(tube.tube):
self.sock = None
self.closed["in"] = True
self.closed["out"] = True
log.info('Closed connection to %s on port %d' % (self.rhost, self.rport), log_level = self.log_level)
self._close_msg()
def _close_msg(self):
log.info('Closed connection to %s port %d' % (self.rhost, self.rport), log_level = self.log_level)
def fileno(self):
if not self.sock:
log.error("A closed socket does not have a file number")
return self.sock.fileno()
def shutdown(self, direction = "out"):
if self.closed[direction]:
return
self.closed[direction] = True
if direction == "out":
try:
self.sock.shutdown(socket.SHUT_WR)
except IOError as e:
if e.errno == errno.ENOTCONN:
pass
else:
raise
if direction == "in":
try:
self.sock.shutdown(socket.SHUT_RD)
except IOError as e:
if e.errno == errno.ENOTCONN:
pass
else:
raise
if False not in self.closed.values():
self.close()
+459
View File
@@ -0,0 +1,459 @@
import os, string, base64, paramiko, time, tempfile, threading, sys, shutil
from .. import term, log_levels, log, context
from ..util import hashes, misc
from . import sock, tube
def _raw_sh_string(s):
very_good = set(string.ascii_letters + string.digits)
good = (very_good | set(string.punctuation + ' ')) - set("'\\")
if all(c in very_good for c in s):
return s
elif all(c in good for c in s):
return "'%s'" % s
else:
fixed = ''
for c in s:
if c in good:
fixed += c
else:
fixed += '\\x%02x' % ord(c)
return '"$((echo %s|(base64 -d||openssl enc -d -base64)||echo -en \'%s\') 2>/dev/null)"' % (base64.b64encode(s), fixed)
class ssh_channel(sock.sock):
def __init__(self, parent, process = None, tty = False, timeout = 'default', log_level = log_levels.INFO):
super(ssh_channel, self).__init__(timeout, log_level)
self.returncode = None
self.host = parent.host
h = log.waitfor('Opening new channel: %r' % (process or 'shell'), log_level = self.log_level)
self.sock = parent.transport.open_session()
if tty:
self.sock.get_pty('xterm', term.width, term.height)
def resizer():
if self.sock:
self.sock.resize_pty(term.width, term.height)
self.resizer = resizer
term.term.on_winch.append(self.resizer)
else:
self.resizer = None
# Put stderr on stdout. This might not always be desirable,
# but our API does not support multiple streams
self.sock.set_combine_stderr(True)
self.settimeout(self.timeout)
if process:
self.sock.exec_command(process)
else:
self.sock.invoke_shell()
h.success()
def kill(self):
"""kill()
Kills the process.
"""
self.close()
def poll(self):
"""poll() -> int
Poll the exit code of the process. Will return None, if the
process has not yet finished and the exit code otherwise.
"""
if self.returncode == None:
if self.sock and self.sock.exit_status_ready():
self.returncode = self.sock.recv_exit_status()
return self.returncode
def can_recv_raw(self, timeout):
end = time.time() + timeout
while time.time() < end:
if self.sock.recv_ready():
return True
time.sleep(0.05)
return False
def interactive(self, prompt = None):
"""interactive()
Does mostly the same as :meth:`pwnlib.tubes.tube.tube.interactive`,
however SSH will typically supply it's own prompt. We also
have a few SSH-specific hacks that will ideally be removed
once the :mod:`pwnlib.term` is more mature.
The prompt argument is ignored.
"""
if not term.term_mode:
log.error("interactive() is not possible outside term_mode")
log.info('Switching to interactive mode', log_level = self.log_level)
# Save this to restore later
debug_log_level = self.debug_log_level
self.debug_log_level = 0
# We would like a cursor, please!
term.term.show_cursor()
go = [True]
def recv_thread(go):
while go[0]:
try:
cur = self.recv(timeout = 0.05)
if cur == None:
continue
elif cur == '\a':
# Ugly hack until term unstands bell characters
continue
sys.stdout.write(cur)
sys.stdout.flush()
except EOFError:
log.info('Got EOF while reading in interactive', log_level = self.log_level)
go[0] = False
break
t = threading.Thread(target = recv_thread, args = (go,))
t.daemon = True
t.start()
while go[0]:
try:
data = term.key.getraw(0.1)
except KeyboardInterrupt:
data = [3] # This is ctrl-c
except IOError:
if go[0]:
raise
if data:
try:
self.send(''.join(chr(c) for c in data))
except EOFError:
go[0] = False
log.info('Got EOF while sending in interactive',
log_level = self.log_level)
t.join()
# Restore
self.debug_log_level = debug_log_level
term.term.hide_cursor()
def close(self):
self.poll()
while self.resizer in term.term.on_winch:
term.term.on_winch.remove(self.resizer)
super(ssh_channel, self).close()
def _close_msg(self):
log.info('Closed SSH channel with %s' % self.host, log_level = self.log_level)
class ssh(object):
def __init__(self, user, host, port = 22, password = None, key = None, keyfile = None, proxy_command = None, proxy_sock = None, timeout = 'default', log_level = log_levels.INFO):
"""Creates a new ssh connection.
Args:
user(str): The username to log in with
host(str): The hostname to connect to
port(int): The port to connect to
password(str): Try to authenticate using this password
key(str): Try to authenticate using this private key. The string should be the actual private key.
keyfile(str): Try to authenticate using this private key. The string should be a filename.
proxy_command(str): Use this as a proxy command. It has approximately the same semantics as ProxyCommand from ssh(1).
proxy_sock(str): Use this socket instead of connecting to the host.
NOTE: The proxy_command and proxy_sock arguments is only available if a
fairly new version of paramiko is used."""
self.host = host
self.port = port
self.timeout = tube._fix_timeout(timeout, context.timeout)
self.log_level = log_level
self._cachedir = os.path.join(tempfile.gettempdir(), 'pwntools-ssh-cache')
misc.mkdir_p(self._cachedir)
keyfiles = [keyfile] if keyfile else []
h = log.waitfor('Connecting to %s on port %d' % (host, port), log_level = self.log_level)
self.client = paramiko.SSHClient()
class IgnorePolicy(paramiko.MissingHostKeyPolicy):
"""Policy for what happens when an unknown ssh-fingerprint is encountered"""
def __init__(self):
self.do_warning = False
self.client.set_missing_host_key_policy(IgnorePolicy())
has_proxy = (proxy_sock or proxy_command) and True
if has_proxy:
if 'ProxyCommand' not in dir(paramiko):
log.error('This version of paramiko does not support proxies.')
if proxy_sock and proxy_command:
log.error('Cannot have both a proxy command and a proxy sock')
if proxy_command:
proxy_sock = paramiko.ProxyCommand(proxy_command)
self.client.connect(host, port, user, password, key, keyfiles, self.timeout, compress = True, sock = proxy_sock)
else:
self.client.connect(host, port, user, password, key, keyfiles, self.timeout, compress = True)
self.transport = self.client.get_transport()
h.success()
def shell(self, tty = True, timeout = 'default', log_level = 'default'):
"""shell(tty = False, timeout = 'default', log_level = 'default') -> ssh_channel
Open a new channel with a shell inside. If `tty` is True, then a TTY
is requested on the remote server.
Return a :class:`pwnlib.tubes.ssh.ssh_channel` object.
"""
return self.run(None, tty, timeout, log_level)
def run(self, process, tty = False, timeout = 'default', log_level = 'default'):
"""run(process, tty = False, timeout = 'default', log_level = 'default') -> ssh_channel
Open a new channel with a specific process inside. If `tty` is True,
then a TTY is requested on the remote server.
Return a :class:`pwnlib.tubes.ssh.ssh_channel` object."""
if log_level == 'default':
log_level = self.log_level
timeout = tube._fix_timeout(timeout, self.timeout)
return ssh_channel(self, process, tty, timeout, log_level)
def run_to_end(self, process, tty = False):
"""run_to_end(self, process, tty = False, timeout = 'default') -> str
Run a command on the remote server and return a tuple with
(data, exit_status). If `tty` is True, then the command is run inside
a TTY on the remote server."""
c = self.run(process, tty, None, 0)
data = c.recvall()
retcode = c.poll()
c.close()
return data, retcode
def connected(self):
"""Returns True if we are connected."""
return self.client != None
def close(self):
"""Close the connection."""
if self.client:
self.client.close()
self.client = None
log.info("Closed connection to %r" % self.host, log_level = self.log_level)
def _libs_remote(self, remote):
"""Return a dictionary of the libraries used by a remote file."""
data, status = self.run_to_end('ldd ' + _raw_sh_string(remote))
if status != 0:
log.failure('Unable to find libraries for %r' % remote)
return {}
return misc.parse_ldd_output(data)
def _get_fingerprint(self, remote):
arg = _raw_sh_string(remote)
cmd = '(sha256sum %s||sha1sum %s||md5sum %s) 2>/dev/null' % (arg, arg, arg)
data, status = self.run_to_end(cmd)
if status == 0:
return data.split()[0]
else:
return None
def _get_cachefile(self, fingerprint):
return os.path.join(self._cachedir, fingerprint)
def _verify_local_fingerprint(self, fingerprint):
if not isinstance(fingerprint, str) or \
len(fingerprint) not in [32, 40, 64] or \
not set(fingerprint).issubset('abcdef0123456789'):
log.failure('Invalid fingerprint %r' % fingerprint)
return False
local = self._get_cachefile(fingerprint)
if not os.path.isfile(local):
return False
func = {32: hashes.md5filehex, 40: hashes.sha1filehex, 64: hashes.sha256filehex}[len(fingerprint)]
if func(local) == fingerprint:
return True
else:
os.unlink(local)
return False
def _download_raw(self, remote, local):
total, _ = self.run_to_end('wc -c ' + _raw_sh_string(remote))
total = misc.size(int(total.split()[0]))
h = log.waitfor('Downloading %r' % remote, log_level = self.log_level)
def update(has):
h.status("%s/%s" % (misc.size(has), total))
c = self.run('cat ' + _raw_sh_string(remote), log_level = 0)
data = ''
while True:
try:
data += c.recv()
except EOFError:
break
update(len(data))
if c.poll() != 0:
h.failure('Could not download file %r' % remote)
else:
with open(local, 'w') as fd:
fd.write(data)
h.success()
def _download_to_cache(self, remote):
fingerprint = self._get_fingerprint(remote)
if fingerprint == None:
local = os.path.normpath(remote)
local = os.path.basename(local)
local += time.strftime('-%Y-%m-d-%H:%M:%S')
local = os.path.join(self._cachedir, local)
self._download_raw(remote, local)
return local
local = self._get_cachefile(fingerprint)
if self._verify_local_fingerprint(fingerprint):
log.success('Found %r in ssh cache' % remote, log_level = self.log_level)
else:
self._download_raw(remote, local)
if not self._verify_local_fingerprint(fingerprint):
log.error('Could not download file %r' % remote)
return local
def download_data(self, remote):
"""Downloads a file from the remote server and returns it as a string.
Args:
remote(str): The remote filename to download."""
with open(self._download_to_cache(remote)) as fd:
return fd.read()
def download_file(self, remote, local = None):
"""Downloads a file from the remote server.
The file is cached in /tmp/pwntools-ssh-cache using a hash of the file, so
calling the function twice has little overhead.
Args:
remote(str): The remote filename to download
local(str): The local filename to save it to. Default is to infer it from the remote filename."""
if not local:
local = os.path.basename(os.path.normpath(remote))
local_tmp = self._download_to_cache(remote)
shutil.copy2(local_tmp, local)
def upload_data(self, data, remote):
"""Uploads some data into a file on the remote server.
Args:
data(str): The data to upload.
remote(str): The filename to upload it to."""
s = self.run('cat>' + _raw_sh_string(remote), log_level = 0)
s.send(data)
s.shutdown('out')
s.recvall()
if s.poll() != 0:
log.error("Could not upload file %r" % remote)
def upload_file(self, filename, remote = None):
"""Uploads a file to the remote server.
Args:
remote(str): The local filename to download
local(str): The remote filename to save it to. Default is to infer it from the local filename."""
if remote == None:
remote = os.path.normpath(filename)
remote = os.path.basename(remote)
with open(filename) as fd:
data = fd.read()
self.upload_data(data, remote)
def libs(self, remote, directory = None):
"""Downloads the libraries referred to by a file.
This is done by running ldd on the remote server, parsing the output
and downloading the relevant files.
The directory argument specified where to download the files. This defaults
to './$HOSTNAME' where $HOSTNAME is the hostname of the remote server."""
libs = self._libs_remote(remote)
if directory == None:
directory = self.host
directory = os.path.realpath(directory)
res = {}
seen = set()
for lib, remote in libs.items():
if not remote or lib == 'linux':
continue
local = os.path.realpath(os.path.join(directory, '.' + os.path.sep + remote))
if not local.startswith(directory):
log.warning('This seems fishy: %r' % remote)
continue
misc.mkdir_p(os.path.dirname(local))
if remote not in seen:
self.download_file(remote, local)
seen.add(remote)
res[lib] = local
return res
def interactive(self):
"""Create an interactive session.
This is a simple wrapper for creating a new
:class:`pwnlib.tubes.ssh.ssh_channel` object and calling
:meth:`pwnlib.tubes.ssh.ssh_channel.interactive` on it."""
s = self.shell()
s.interactive()
s.close()
+14 -2
View File
@@ -48,9 +48,10 @@ class tube(object):
return res
timeout = _fix_timeout(timeout, self.timeout)
self.settimeout_raw(timeout)
old_timeout = self.timeout
self.settimeout(timeout)
data = self.recv_raw(numb)
self.settimeout_raw(self.timeout)
self.settimeout(old_timeout)
if data:
for line in re.findall('(?:.*\n)|(?:.+$)', data):
@@ -379,3 +380,14 @@ class tube(object):
"""
log.bug('Should be implemented by a subclass.')
def shutdown(self, direction = "out"):
"""shutdown(direction = "out")
Calls shutdown on the socket, and thus closing it for either reading or writing.
Args:
direction(str): Either the string "in" or "out".
"""
log.bug('Should be implemented by a subclass.')
+1 -1
View File
@@ -51,4 +51,4 @@ def pause(n = None):
time.sleep(1)
log.done_success('Done', h)
else:
raise ValueError('options(): n must be a number or None')
raise ValueError('pause(): n must be a number or None')
+2 -2
View File
@@ -1,7 +1,7 @@
__all__ = [
'crc', 'cyclic', 'fiddling', 'hashes', 'lists',
'misc', 'packing', 'proc', 'safeeval', 'net'
'misc', 'net', 'packing', 'proc', 'safeeval'
]
from . import crc, cyclic, fiddling, hashes, lists
from . import misc, packing, proc, safeeval, net
from . import misc, net, packing, proc, safeeval
+1 -1
View File
@@ -15,7 +15,7 @@ An obvious optimization would be to actually generate some lookup-tables.
from . import fiddling, packing
import os, re, sys, types
class BitPolynom:
class BitPolynom(object):
def __init__(self, n):
if not isinstance(n, (int, long)):
raise TypeError("Polynomial must be called with an integer or a list")
+1 -1
View File
@@ -496,7 +496,7 @@ def hexdump(s, width = 16, skip = True, hexii = False):
if len(hexbytes) > middle:
hexbytes = hexbytes[:middle] + column_sep + hexbytes[middle:]
lines.append(line_fmt % locals())
lines.append(line_fmt % {'offset': offset, 'hexbytes': hexbytes, 'printable': printable})
lines.append("%08x" % len(s))
return '\n'.join(lines)
+77 -11
View File
@@ -1,4 +1,4 @@
import socket, subprocess, re, os, stat
import socket, re, os, stat, errno
from .. import log
def align(alignment, x):
@@ -78,7 +78,7 @@ def read(path):
Examples:
>>> read('pwnlib/util/misc.py').split('\\n')[0]
'import socket, subprocess, re, os'
'import socket, re, os, stat, errno'
"""
path = os.path.expanduser(os.path.expandvars(path))
with open(path) as fd:
@@ -90,14 +90,7 @@ def write(path, data = '', create_dir = False):
path = os.path.expanduser(os.path.expandvars(path))
if create_dir:
path = os.path.realpath(path)
ds = path.split('/')
f = ds.pop()
p = '/'
while d:
d = ds.pop(0)
p = os.path.join(p, d)
if not os.path.exists(p):
os.mkdir(p)
mkdir_p(os.path.dirname(path))
with open(path, 'w') as f:
f.write(data)
@@ -137,7 +130,7 @@ def which(name, all = False):
# work around this issue: http://bugs.python.org/issue9311
if isroot and not \
st.st_mode & (stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH):
continue
continue
if all:
out.add(p)
else:
@@ -146,3 +139,76 @@ def which(name, all = False):
return out
else:
return None
def run_in_new_terminal(command, terminal = None):
"""run_in_new_terminal(command, terminal = None) -> None
Run a command in a new terminal.
Args:
command (str): The command to run.
terminal (str): Which terminal to use, if set to :const:`None` pick from
``$TERM``, ``$COLORTERM`` or ``x-terminal-emulator`` in that order.
Returns:
None
"""
if terminal:
term = which(terminal)
else:
terminal = os.getenv('TERM') or os.getenv('COLORTERM')
if not terminal:
term = which('x-terminal-emulator')
if not term:
log.error('could not find a terminal, make sure $TERM or $COLORTERM is set or that "x-terminal-emulator" is in your $PATH')
term = which(terminal)
if not term:
log.error('could not find terminal: %s' % terminal)
termpid = os.fork()
if termpid == 0:
argv = [term, '-e', command]
os.execv(argv[0], argv)
os._exit(1)
def parse_ldd_output(data):
"""Parses the output from the command ldd into a dictionary of
``libary_name => path``."""
expr = re.compile(r'(?:([^ ]+) => )?([^(]+)?(?: \(0x[0-9a-f]+\))?$')
res = {}
for line in data.strip().split('\n'):
line = line.strip()
if not line:
continue
parsed = expr.search(line)
if not parsed:
log.warning('Could not parse line: "%s"' % line)
name, resolved = parsed.groups()
if resolved and re.search('/ld-[^/]*$', resolved):
if name != None:
resolved = name
name = 'ld'
if name == None:
if re.search('^linux', resolved):
name = 'linux'
else:
log.warning('Could not parse line: "%s"' % line)
continue
res[name] = resolved
if name.startswith('libc.so.'):
res['libc'] = resolved
return res
def mkdir_p(path):
"""Emulates the behavior of ``mkdir -p``."""
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else:
raise
+87 -37
View File
@@ -1,5 +1,4 @@
import os, time, socket, re, struct
from . import packing
import os, time, socket, re, struct, errno
from .. import tubes
def pidof(target):
@@ -11,9 +10,7 @@ def pidof(target):
- :class:`pwnlib.tubes.process.process`: singleton list of the PID of `target`.
- :class:`pwnlib.tubes.remote.remote`: singleton list of the PID at the remote end of `target` if it is running on the host. Otherwise an empty list.
*WARNING*: At this time only IPv4 is supported.
- :class:`pwnlib.tubes.sock.sock`: singleton list of the PID at the remote end of `target` if it is running on the host. Otherwise an empty list.
Args:
target(object): The target whoose PID(s) to find.
@@ -21,42 +18,44 @@ def pidof(target):
Returns:
A list of found PIDs.
"""
if isinstance(target, tubes.remote.remote):
# XXX: handle IPv6
def toaddr((host, port)):
return '%08X:%04X' % \
(struct.unpack('<I', socket.inet_aton(host))[0], port)
sock = toaddr(target.sock.getsockname())
peer = toaddr(target.sock.getpeername())
if isinstance(target, tubes.sock.sock):
def toaddr(sockaddr, family):
host = sockaddr[0]
port = sockaddr[1]
host = socket.inet_pton(family, host)
if family == socket.AF_INET:
host = '%08X' % struct.unpack('<I', host)
elif family == socket.AF_INET6:
host = '%08X%08X%08X%08X' % struct.unpack('<IIII', host)
return '%s:%04X' % (host, port)
sock = toaddr(target.sock.getsockname(), target.sock.family)
peer = toaddr(target.sock.getpeername(), target.sock.family)
# find inode of 'local addr -> remote addr' socket
inode = None
with open('/proc/net/tcp') as fd:
for line in fd:
line = line.split()
loc = line[1]
rem = line[2]
if loc == peer and rem == sock:
inode = line[9]
break
for f in ['tcp', 'tcp6']:
with open('/proc/net/%s' % f) as fd:
# skip the first line with the column names
fd.readline()
for line in fd:
line = line.split()
loc = line[1]
rem = line[2]
st = int(line[3], 16)
if st != 1: # TCP_ESTABLISHED, see include/net/tcp_states.h
continue
if loc == peer and rem == sock:
inode = int(line[9])
break
if inode:
break
if not inode:
return []
# find the process who owns this socket
for pid in all_pids():
try:
for fd in os.listdir('/proc/%d/fd' % pid):
try:
fd = os.readlink('/proc/%d/fd/%s' % (pid, fd))
except OSError:
continue
m = re.match('socket:\[(\d+)\]', fd)
if m and m.group(1) == inode:
return [pid]
except OSError:
pass
return [pid]
pid = pid_by_inode(inode)
return [pid] if pid else None
elif isinstance(target, tubes.process.process):
return [target.proc.pid]
@@ -64,6 +63,32 @@ def pidof(target):
else:
return pid_by_name(target)
def pid_by_inode(inode):
"""pid_by_inode(inode) -> int or None
Find the process who owns a given inode
Args:
inode (int): The inode to look for.
Returns:
The PID of the process who owns `inode`, or :const:`None` if it wasn't
found.
"""
inode = str(inode)
for pid in all_pids():
try:
for fd in os.listdir('/proc/%d/fd' % pid):
try:
fd = os.readlink('/proc/%d/fd/%s' % (pid, fd))
except OSError:
continue
m = re.match(r'socket:\[(\d+)\]', fd)
if m and m.group(1) == inode:
return pid
except OSError:
pass
def all_pids():
"""all_pids() -> int list
@@ -95,7 +120,7 @@ def status(pid):
val = line[i + 2:-1] # initial :\t and trailing \n
out[key] = val
except OSError as e:
if e.errno == errno.NOENT:
if e.errno == errno.ENOENT:
raise ValueError('No process with PID %d' % pid)
else:
raise
@@ -108,13 +133,14 @@ def pid_by_name(name):
name (str): Name of program.
Returns:
List of PIDs matching `name`.
List of PIDs matching `name` sorted by lifetime, youngest to oldest.
Example:
>>> pid_by_name('init')
[1]
"""
return [pid for pid in all_pids() if status(pid)['Name'] == name]
return sorted([pid for pid in all_pids() if status(pid)['Name'] == name],
key = starttime, reverse = True)
def name(pid):
"""name(pid) -> str
@@ -231,6 +257,30 @@ def exe(pid):
"""
return os.readlink('/proc/%d/exe' % pid)
def cwd(pid):
"""cwd(pid) -> str
Args:
pid (int): PID of the process.
Returns:
The path of the process's current working directory. I.e. what
``/proc/<pid>/cwd`` points to.
"""
return os.readlink('/proc/%d/cwd' % pid)
def cmdline(pid):
"""cmdline(pid) -> str list
Args:
pid (int): PID of the process.
Returns:
A list of the fields in ``/proc/<pid>/cmdline``.
"""
with open('/proc/%d/cmdline' % pid, 'r') as fd:
return fd.read().rstrip('\x00').split('\x00')
def stat(pid):
"""stat(pid) -> str list
+28 -8
View File
@@ -2,7 +2,7 @@
from .. import term, log
from ..term import text
import sys, time, threading
import time, threading
_banner = r'''
.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:._.:*~*:.
@@ -28,16 +28,36 @@ def splash():
def updater():
colors = [
text.blue, text.magenta, text.red,
text.yellow, text.green, text.cyan
text.blue , text.bold_blue ,
text.magenta, text.bold_magenta,
text.red , text.bold_red ,
text.yellow , text.bold_yellow ,
text.green , text.bold_green ,
text.cyan , text.bold_cyan ,
]
def getcolor(n):
return colors[(n / 4) % len(colors)]
h = log.indented('\n', frozen = False)
lines = _banner.lstrip('\n')
lines = _banner.strip('\n').split('\n')
hs = [log.indented('', frozen = False) for _ in range(len(lines))]
ndx = 0
while sys:
h.update(colors[ndx](lines))
ndx = (ndx + 1) % len(colors)
import sys as _sys
while _sys:
for i, (l, h) in enumerate(zip(lines, hs)):
cur = ''
buf = ''
col = getcolor(ndx + i)
for j in range(len(l)):
buf += l[j]
ncol = getcolor(ndx + i + j)
if col != ncol:
cur += buf if buf.isspace() else col(buf)
col = ncol
buf = ''
cur += col(buf)
h.update(cur)
ndx += 1
time.sleep(0.15)
if term.term_mode:
+1
View File
@@ -0,0 +1 @@
__version__ = '2.1.2'
+1
View File
@@ -1,3 +1,4 @@
paramiko
argparse
mako
pyelftools
+21 -7
View File
@@ -1,32 +1,46 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
from distutils.util import convert_path
from distutils.command.install import INSTALL_SCHEMES
import os, sys, glob
# Get all template files
templates = []
for dirpath, dirnames, filenames in os.walk(os.path.join('pwnlib', 'shellcraft', 'templates')):
for dirpath, dirnames, filenames in os.walk(convert_path('pwnlib/shellcraft/templates')):
for f in filenames:
templates.append(os.path.relpath(os.path.join(dirpath, f), 'pwnlib'))
# Get the version
ns = {}
with open(convert_path('pwnlib/version.py')) as fd:
exec fd.read() in ns
version = ns['__version__']
# This makes pwntools-LICENSE.txt appear with the package folders
for scheme in INSTALL_SCHEMES.values():
scheme['data'] = scheme['purelib']
setup(
name = 'pwntools',
packages = find_packages(),
version = '2.1.0',
version = version,
data_files = [('', ['LICENSE-pwntools.txt'])],
package_data = {
'pwnlib': [
'data/crcsums.txt',
'data/binutils/*',
'data/includes/*.h',
'data/includes/*/*.h',
] + templates
] + templates,
},
scripts = glob.glob("bin/*"),
description = "This is the CTF framework used by Gallopsled in every CTF.",
author = "Gallopsled et al.",
author_email = "#gallopsled @ freenode.net",
url = 'https://github.com/pwnies/pwntools/', # use the URL to the github repo
download_url = "https://github.com/pwnies/pwntools/tarball/master",
install_requires = ['paramiko','argparse', 'mako'],
license = "MIT",
url = 'https://github.com/Gallopsled/pwntools/',
download_url = "https://github.com/Gallopsled/pwntools/tarball/%s" % version,
install_requires = ['paramiko','argparse', 'mako', 'pyelftools'],
license = "Mostly MIT, some GPL/BSD, see LICENSE-pwntools.txt",
classifiers = [
'Topic :: Security',
'Environment :: Console',