mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-08 01:58:16 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb38292c31 | ||
|
|
8e90ed06c8 | ||
|
|
9807f0e120 | ||
|
|
1ed91a3667 | ||
|
|
88d22014b5 | ||
|
|
d55f10569b | ||
|
|
66db6eb3b9 | ||
|
|
bb142c754f | ||
|
|
ae06ea7d56 | ||
|
|
6d62a1b979 | ||
|
|
f38e8c7934 | ||
|
|
a4305e5928 | ||
|
|
e4a6aac594 | ||
|
|
fffa749e28 | ||
|
|
d6bc933759 | ||
|
|
d80c0fc329 | ||
|
|
33065ff17a | ||
|
|
9a0783e3db | ||
|
|
528e2db39b | ||
|
|
b310e452fa | ||
|
|
5b11898b8d | ||
|
|
9653c20300 | ||
|
|
15a6540bca | ||
|
|
a0cc2d2304 | ||
|
|
c3125c7d15 | ||
|
|
d97034f67b | ||
|
|
6ad0319ad7 | ||
|
|
5d63390b0f | ||
|
|
e77f6e8e04 | ||
|
|
fa8b8cc7c1 | ||
|
|
a828642ce6 | ||
|
|
3a27bfa23c | ||
|
|
683e94b447 | ||
|
|
ed5dec5397 | ||
|
|
208e4ac1d4 | ||
|
|
0e1fa51ad4 | ||
|
|
086972fc05 | ||
|
|
63a1762430 | ||
|
|
a5bc72f179 | ||
|
|
ce934a26e7 | ||
|
|
b6a0cbb641 | ||
|
|
1083c4682c | ||
|
|
e3d7ddac9d | ||
|
|
e12be9308b | ||
|
|
a0c2b06e44 | ||
|
|
307386f1a8 | ||
|
|
df76e03850 | ||
|
|
8e9be618fc |
No files matched your search
@@ -8,3 +8,4 @@
|
||||
build
|
||||
MANIFEST
|
||||
dist
|
||||
pwntools.egg-info
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
include *.txt
|
||||
@@ -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
@@ -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)
|
||||
@@ -0,0 +1,8 @@
|
||||
.. testsetup:: *
|
||||
|
||||
from pwnlib import context, constants
|
||||
|
||||
:mod:`pwnlib.constants` --- Easy access to header file constants
|
||||
================================================================
|
||||
|
||||
.. automodule:: pwnlib.constants
|
||||
@@ -0,0 +1,5 @@
|
||||
:mod:`pwnlib.dynelf` --- Resolving remote functions using leaks
|
||||
===============================================================
|
||||
|
||||
.. automodule:: pwnlib.dynelf
|
||||
:members:
|
||||
@@ -0,0 +1,9 @@
|
||||
.. testsetup:: *
|
||||
|
||||
from pwnlib.elf import *
|
||||
|
||||
:mod:`pwnlib.elf` --- Working with ELF binaries
|
||||
===============================================
|
||||
|
||||
.. automodule:: pwnlib.elf
|
||||
:members:
|
||||
@@ -0,0 +1,5 @@
|
||||
:mod:`pwnlib.gdb` --- Working with GDB
|
||||
======================================
|
||||
|
||||
.. automodule:: pwnlib.gdb
|
||||
:members:
|
||||
@@ -9,8 +9,12 @@ Contents:
|
||||
|
||||
intro
|
||||
asm
|
||||
constants
|
||||
context
|
||||
dynelf
|
||||
elf
|
||||
exception
|
||||
gdb
|
||||
log
|
||||
memleak
|
||||
tubes
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
@@ -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
@@ -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__
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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,10 +1,7 @@
|
||||
import re, os.path
|
||||
import os
|
||||
|
||||
__all__ = ['make_function']
|
||||
|
||||
def relpath(path):
|
||||
return
|
||||
|
||||
loaded = {}
|
||||
lookup = None
|
||||
def init_mako():
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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')
|
||||
@@ -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
@@ -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")
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__version__ = '2.1.2'
|
||||
@@ -1,3 +1,4 @@
|
||||
paramiko
|
||||
argparse
|
||||
mako
|
||||
pyelftools
|
||||
@@ -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',
|
||||
|
||||
Reference in new issue
Block a user