mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-13 04:31:15 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c45e92d78b | ||
|
|
a76c7b0ca1 | ||
|
|
b8fad78d3e | ||
|
|
8f42bef2b1 | ||
|
|
020143f0f9 | ||
|
|
416c33ed00 | ||
|
|
3884d606b7 | ||
|
|
4628627943 | ||
|
|
d86ae5794a | ||
|
|
67d49a693d | ||
|
|
a7cb54931c | ||
|
|
e450f6c247 | ||
|
|
3e4d4a2115 | ||
|
|
b3b6d52bdd | ||
|
|
6ba46d4eec | ||
|
|
5af2d92100 | ||
|
|
3fadc67327 | ||
|
|
91c1b20c7b | ||
|
|
0351219903 | ||
|
|
4b067d2ec5 | ||
|
|
d9f9e7ca6f | ||
|
|
9e55d8f4c8 | ||
|
|
97a760a0dd | ||
|
|
d7e9d313a2 | ||
|
|
7a246e7887 | ||
|
|
c04dbdb9e9 | ||
|
|
fc372d17a2 | ||
|
|
79986d8262 | ||
|
|
ef698d4562 | ||
|
|
1238fc359e | ||
|
|
79d150061c | ||
|
|
bf37947a99 | ||
|
|
1fe9b3e693 | ||
|
|
762591bf35 | ||
|
|
04f401c70e | ||
|
|
84b51fc1a5 | ||
|
|
1573957cc8 | ||
|
|
038e50fa0c | ||
|
|
58d6aea217 | ||
|
|
4c78e188ce |
No files matched your search
@@ -27,7 +27,7 @@ jobs:
|
||||
key: ${{ matrix.os }}-cache-pip
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v1
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -59,33 +59,9 @@ jobs:
|
||||
binutils-s390x-linux-gnu \
|
||||
binutils-sparc64-linux-gnu \
|
||||
gcc-multilib \
|
||||
libc6-dbg
|
||||
libc6-dbg \
|
||||
elfutils
|
||||
|
||||
- name: Cache for elfutils
|
||||
uses: actions/cache@v1
|
||||
id: cache-elfutils
|
||||
with:
|
||||
path: ~/.cache/elfutils
|
||||
key: ${{ matrix.os }}-cache-elfutils
|
||||
|
||||
# Install newer elfutils version due to regression in 0.176 available in focal.
|
||||
- name: Install newer elfutils
|
||||
env:
|
||||
ELFUTILS_VERSION: 0.181
|
||||
run: |
|
||||
if [[ ! -d ~/.cache/elfutils/elfutils-${ELFUTILS_VERSION} ]]
|
||||
then
|
||||
wget -O /tmp/elfutils-${ELFUTILS_VERSION}.tar.bz2 https://sourceware.org/elfutils/ftp/${ELFUTILS_VERSION}/elfutils-${ELFUTILS_VERSION}.tar.bz2
|
||||
mkdir -p ~/.cache/elfutils && cd ~/.cache/elfutils
|
||||
tar -xf /tmp/elfutils-${ELFUTILS_VERSION}.tar.bz2
|
||||
cd elfutils-${ELFUTILS_VERSION}
|
||||
LDFLAGS=-Wl,-rpath=/usr/local/lib,--enable-new-dtags ./configure --disable-libdebuginfod --disable-debuginfod && make && sudo make install
|
||||
else
|
||||
cd ~/.cache/elfutils/elfutils-${ELFUTILS_VERSION}
|
||||
sudo make install
|
||||
fi
|
||||
eu-unstrip --version
|
||||
|
||||
- name: Install RPyC for GDB
|
||||
run: |
|
||||
sudo apt-get install -y python3-pip
|
||||
@@ -115,7 +91,7 @@ jobs:
|
||||
run: PWNLIB_NOTERM=1 python -bb -c 'from pwn import *; print(pwnlib.term.term_mode)'
|
||||
|
||||
- name: Install documentation dependencies
|
||||
run: pip install -r docs/requirements.txt
|
||||
run: pip install -r docs/requirements.txt
|
||||
|
||||
- name: Manually install non-broken Unicorn
|
||||
run: pip install unicorn==1.0.2rc3
|
||||
@@ -182,6 +158,10 @@ jobs:
|
||||
pwn constgrep ^MAP_ 0
|
||||
pwn constgrep -e O_RDWR
|
||||
|
||||
pwn libcdb file /lib/x86_64-linux-gnu/libc.so.6
|
||||
pwn libcdb lookup puts 5f0 __libc_start_main_ret d0a
|
||||
pwn libcdb hash b229d1da1e161f95e839cf90cded5f719e5de308
|
||||
|
||||
- name: Build source and wheel distributions
|
||||
run: |
|
||||
python setup.py sdist
|
||||
|
||||
+25
-6
@@ -9,9 +9,10 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
|
||||
| Version | Branch | Release Date |
|
||||
| ---------------- | -------- | ---------------------- |
|
||||
| [4.10.0](#4100) | `dev` |
|
||||
| [4.9.0](#490) | `beta` |
|
||||
| [4.8.0](#480) | `stable` | Apr 21, 2022
|
||||
| [4.11.0](#4110) | `dev` |
|
||||
| [4.10.0](#4100) | `beta` |
|
||||
| [4.9.0](#490) | `stable` | Dec 29, 2022
|
||||
| [4.8.0](#480) | | Apr 21, 2022
|
||||
| [4.7.1](#471) | | Apr 20, 2022
|
||||
| [4.7.0](#470) | | Nov 15, 2021
|
||||
| [4.6.0](#460) | | Jul 12, 2021
|
||||
@@ -64,11 +65,25 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
| [3.0.0](#300) | | Aug 20, 2016
|
||||
| [2.2.0](#220) | | Jan 5, 2015
|
||||
|
||||
## 4.10.0 (`dev`)
|
||||
## 4.11.0 (`dev`)
|
||||
|
||||
|
||||
|
||||
## 4.9.0 (`beta`)
|
||||
## 4.10.0 (`beta`)
|
||||
|
||||
- [#2062][2062] make pwn cyclic -l work with entry larger than 4 bytes
|
||||
- [#2092][2092] shellcraft: dup() is now called dupio() consistently across all supported arches
|
||||
- [#2093][2093] setresuid() in shellcraft uses current euid by default
|
||||
- [#2125][2125] Allow tube.recvregex to return capture groups
|
||||
- [#2144][2144] Removes `p2align 2` `asm()` headers from `x86-32`, `x86-64` and `mips` architectures to avoid inconsistent instruction length when patching binaries
|
||||
|
||||
[2062]: https://github.com/Gallopsled/pwntools/pull/2062
|
||||
[2092]: https://github.com/Gallopsled/pwntools/pull/2092
|
||||
[2093]: https://github.com/Gallopsled/pwntools/pull/2093
|
||||
[2125]: https://github.com/Gallopsled/pwntools/pull/2125
|
||||
[2144]: https://github.com/Gallopsled/pwntools/pull/2144
|
||||
|
||||
## 4.9.0 (`stable`)
|
||||
|
||||
- [#1975][1975] Add libcdb commandline tool
|
||||
- [#1979][1979] Add `js_escape()` and `js_unescape()` to `util.fiddling`
|
||||
@@ -78,6 +93,8 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
- [#2033][2033] Quote file and core path in generated GDB script
|
||||
- [#2035][2035] Change Buffer's parent class to object
|
||||
- [#2037][2037] Allow SSH tunnel to be treated like a TCP socket (with 'raw=True')
|
||||
- [#2123][2123] Fix ROP without a writeable cache directory
|
||||
- [#2124][2124] Fix `tube.recvpred()` timeout argument
|
||||
|
||||
[1975]: https://github.com/Gallopsled/pwntools/pull/1975
|
||||
[1979]: https://github.com/Gallopsled/pwntools/pull/1979
|
||||
@@ -87,8 +104,10 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
[2033]: https://github.com/Gallopsled/pwntools/pull/2033
|
||||
[2035]: https://github.com/Gallopsled/pwntools/pull/2035
|
||||
[2037]: https://github.com/Gallopsled/pwntools/pull/2037
|
||||
[2123]: https://github.com/Gallopsled/pwntools/pull/2123
|
||||
[2124]: https://github.com/Gallopsled/pwntools/pull/2124
|
||||
|
||||
## 4.8.0 (`stable`)
|
||||
## 4.8.0
|
||||
|
||||
- [#1922][1922] Fix logic in `wait_for_debugger`
|
||||
- [#1828][1828] libcdb: Load debug info and unstrip libc binary
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
[](https://github.com/Gallopsled/pwntools/actions?query=workflow%3A%22Continuous+Integration%22+branch%3Adev)
|
||||
[](https://coveralls.io/github/Gallopsled/pwntools?branch=dev)
|
||||
[](http://choosealicense.com/licenses/mit/)
|
||||
[](https://repology.org/project/python:pwntools/versions)
|
||||
[](https://discord.gg/96VA2zvjCB)
|
||||
[](https://twitter.com/pwntools)
|
||||
|
||||
|
||||
@@ -12,8 +12,7 @@ pypandoc
|
||||
pyserial>=2.7
|
||||
requests>=2.5.1
|
||||
ropgadget>=5.3
|
||||
sphinx==1.8.6; python_version < '3.0.0'
|
||||
sphinx==4.5.0; python_version >= '3.0.0'
|
||||
sphinx==1.8.6; python_version<'3'
|
||||
sphinx>=4.5.0; python_version>='3'
|
||||
sphinx_rtd_theme
|
||||
sphinxcontrib-napoleon
|
||||
sphinxcontrib-autoprogram<=0.1.5
|
||||
+1
-1
@@ -42,8 +42,8 @@ extensions = [
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.intersphinx',
|
||||
'sphinx.ext.napoleon',
|
||||
'sphinxcontrib.autoprogram',
|
||||
'sphinxcontrib.napoleon'
|
||||
]
|
||||
|
||||
# Disable "info" logging directly to stdout by Sphinx
|
||||
|
||||
+4
-1
@@ -384,6 +384,9 @@ class AdbDevice(Device):
|
||||
>>> adb.getprop(property) == device.getprop(property)
|
||||
True
|
||||
"""
|
||||
if name.startswith('_'):
|
||||
raise AttributeError(name)
|
||||
|
||||
with context.local(device=self):
|
||||
g = globals()
|
||||
|
||||
@@ -1513,7 +1516,7 @@ class Partitions(object):
|
||||
return iter(names)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith("_"):
|
||||
if attr.startswith('_'):
|
||||
raise AttributeError(attr)
|
||||
|
||||
for name in self:
|
||||
|
||||
@@ -46,7 +46,7 @@ class BootImage(object):
|
||||
self.kernel = self.data[PAGE:PAGE+self.kernel_size]
|
||||
|
||||
def __getattr__(self, name):
|
||||
value = getattr(self.header, name, None)
|
||||
if value is not None:
|
||||
return value
|
||||
return getattr(super(BootImage, self), name)
|
||||
if name.startswith('_'):
|
||||
raise AttributeError(name)
|
||||
|
||||
return getattr(self.header, name)
|
||||
@@ -114,10 +114,11 @@ class BootloaderImage(object):
|
||||
return '\n'.join(rv)
|
||||
|
||||
def __getattr__(self, name):
|
||||
value = getattr(self.header, name, None)
|
||||
if value is not None:
|
||||
return value
|
||||
return getattr(super(BootImage, self), name)
|
||||
if name.startswith('_'):
|
||||
raise AttributeError(name)
|
||||
|
||||
return getattr(self.header, name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Easy sanity checking
|
||||
|
||||
@@ -62,6 +62,8 @@ from pwnlib.context import context
|
||||
|
||||
class PwnlibArgs(collections.defaultdict):
|
||||
def __getattr__(self, attr):
|
||||
if attr.startswith('_'):
|
||||
raise AttributeError(attr)
|
||||
return self[attr]
|
||||
|
||||
args = PwnlibArgs(str)
|
||||
|
||||
+41
-26
@@ -136,8 +136,23 @@ Try installing binutils for this architecture:
|
||||
%(instructions)s
|
||||
""".strip() % locals())
|
||||
|
||||
|
||||
def check_binutils_version(util):
|
||||
if util_versions[util]:
|
||||
return util_versions[util]
|
||||
result = subprocess.check_output([util, '--version','/dev/null'],
|
||||
stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
if 'clang' in result:
|
||||
log.warn_once('Your binutils is clang-based and may not work!\n'
|
||||
'Try installing with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
|
||||
'Reported version: %r', result.strip())
|
||||
version = re.search(r' (\d+\.\d+)', result).group(1)
|
||||
util_versions[util] = version = tuple(map(int, version.split('.')))
|
||||
return version
|
||||
|
||||
|
||||
@LocalContext
|
||||
def which_binutils(util):
|
||||
def which_binutils(util, check_version=False):
|
||||
"""
|
||||
Finds a binutils in the PATH somewhere.
|
||||
Expects that the utility is prefixed with the architecture name.
|
||||
@@ -204,17 +219,23 @@ def which_binutils(util):
|
||||
|
||||
for pattern in patterns:
|
||||
for dir in environ['PATH'].split(':'):
|
||||
res = sorted(glob(path.join(dir, pattern)))
|
||||
if res:
|
||||
return res[0]
|
||||
for res in sorted(glob(path.join(dir, pattern))):
|
||||
if check_version:
|
||||
ver = check_binutils_version(res)
|
||||
return res, ver
|
||||
return res
|
||||
|
||||
# No dice!
|
||||
print_binutils_instructions(util, context)
|
||||
|
||||
checked_assembler_version = defaultdict(lambda: False)
|
||||
util_versions = defaultdict(tuple)
|
||||
|
||||
def _assembler():
|
||||
gas = which_binutils('as')
|
||||
gas, version = which_binutils('as', check_version=True)
|
||||
if version < (2, 19):
|
||||
log.warn_once('Your binutils version is too old and may not work!\n'
|
||||
'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
|
||||
'Reported version: %r', version)
|
||||
|
||||
E = {
|
||||
'big': '-EB',
|
||||
@@ -246,25 +267,10 @@ def _assembler():
|
||||
|
||||
assembler = assemblers.get(context.arch, [gas])
|
||||
|
||||
if not checked_assembler_version[gas]:
|
||||
checked_assembler_version[gas] = True
|
||||
result = subprocess.check_output([gas, '--version','/dev/null'],
|
||||
stderr=subprocess.STDOUT, universal_newlines=True)
|
||||
version = re.search(r' (\d+\.\d+)', result).group(1)
|
||||
if 'clang' in result:
|
||||
log.warn_once('Your binutils is clang version and may not work!\n'
|
||||
'Try install with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
|
||||
'Reported Version: %r', result.strip())
|
||||
elif version < '2.19':
|
||||
log.warn_once('Your binutils version is too old and may not work!\n'
|
||||
'Try updating with: https://docs.pwntools.com/en/stable/install/binutils.html\n'
|
||||
'Reported Version: %r', result.strip())
|
||||
|
||||
|
||||
return assembler
|
||||
|
||||
def _linker():
|
||||
ld = [which_binutils('ld')]
|
||||
ld, _ = which_binutils('ld', check_version=True)
|
||||
bfd = ['--oformat=' + _bfdname()]
|
||||
|
||||
E = {
|
||||
@@ -276,7 +282,16 @@ def _linker():
|
||||
'i386': ['-m', 'elf_i386'],
|
||||
}.get(context.arch, [])
|
||||
|
||||
return ld + bfd + [E] + arguments
|
||||
return [ld] + bfd + [E] + arguments
|
||||
|
||||
|
||||
def _execstack(linker):
|
||||
ldflags = ['-z', 'execstack']
|
||||
version = util_versions[linker[0]]
|
||||
if version >= (2, 39):
|
||||
return ldflags + ['--no-warn-execstack', '--no-warn-rwx-segments']
|
||||
return ldflags
|
||||
|
||||
|
||||
def _objcopy():
|
||||
return [which_binutils('objcopy')]
|
||||
@@ -595,7 +610,7 @@ def make_elf(data,
|
||||
|
||||
_run(assembler + ['-o', step2, step1])
|
||||
|
||||
linker_options = ['-z', 'execstack']
|
||||
linker_options = _execstack(linker)
|
||||
if vma is not None:
|
||||
linker_options += ['--section-start=.shellcode=%#x' % vma,
|
||||
'--entry=%#x' % vma]
|
||||
@@ -689,7 +704,7 @@ def asm(shellcode, vma = 0, extract = True, shared = False):
|
||||
shutil.copy(step2, step3)
|
||||
|
||||
if vma or not extract:
|
||||
ldflags = ['-z', 'execstack', '-o', step3, step2]
|
||||
ldflags = _execstack(linker) + ['-o', step3, step2]
|
||||
if vma:
|
||||
ldflags += ['--section-start=.shellcode=%#x' % vma,
|
||||
'--entry=%#x' % vma]
|
||||
@@ -771,7 +786,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
|
||||
>>> print(disasm(unhex('4ff00500'), arch = 'thumb', bits=32))
|
||||
0: f04f 0005 mov.w r0, #5
|
||||
>>> print(disasm(unhex('656664676665400F18A4000000000051'), byte=0, arch='amd64'))
|
||||
0: gs data16 fs data16 rex nop/reserved BYTE PTR gs:[eax+eax*1+0x0]
|
||||
0: gs data16 fs rex nop WORD PTR gs:[eax+eax*1+0x0]
|
||||
f: push rcx
|
||||
>>> print(disasm(unhex('01000000'), arch='sparc64'))
|
||||
0: 01 00 00 00 nop
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
import pwnlib.args
|
||||
pwnlib.args.free_form = False
|
||||
|
||||
from pwn import *
|
||||
from pwnlib.commandline import common
|
||||
|
||||
parser = common.parser_commands.add_parser(
|
||||
'libcdb',
|
||||
help = 'Print various information about a libc binary',
|
||||
description = 'Print various information about a libc binary'
|
||||
)
|
||||
|
||||
libc_commands = parser.add_subparsers(
|
||||
dest = 'libc_command'
|
||||
)
|
||||
|
||||
lookup_parser = libc_commands.add_parser(
|
||||
'lookup',
|
||||
help = 'Lookup a libc version by function offsets',
|
||||
description = 'Lookup a libc version by function offsets'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'symbol_offset_pairs',
|
||||
metavar = 'symbol_offset_pairs',
|
||||
nargs = '+',
|
||||
help = 'Symbol and offset pairs to lookup matching libc version. Can be any number of pairs to narrow the search. Example: "read 3e0 write 520"'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--download-libc',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
help = 'Attempt to download the matching libc.so'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--unstrip',
|
||||
action = 'store_true',
|
||||
default = True,
|
||||
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
lookup_parser.add_argument(
|
||||
'--no-unstrip',
|
||||
action = 'store_false',
|
||||
dest = 'unstrip',
|
||||
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
hash_parser = libc_commands.add_parser(
|
||||
'hash',
|
||||
help = 'Display information of a libc version given an unique hash',
|
||||
description = 'Display information of a libc version given an unique hash'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'hash_value',
|
||||
metavar = 'hash_value',
|
||||
nargs = '+',
|
||||
help = 'Hex encoded hash value'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'-t', '--hash_type',
|
||||
nargs = '?',
|
||||
type = str,
|
||||
choices = ['id', 'buildid', 'md5', 'sha1', 'sha256'],
|
||||
default = 'buildid',
|
||||
help = 'The type of the provided hash value. Supported hashtypes: id, buildid, md5, sha1, sha256'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--download-libc',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
help = 'Attempt to download the matching libc.so'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--unstrip',
|
||||
action = 'store_true',
|
||||
default = True,
|
||||
help = 'Attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
hash_parser.add_argument(
|
||||
'--no-unstrip',
|
||||
action = 'store_false',
|
||||
dest = 'unstrip',
|
||||
help = 'Do NOT attempt to unstrip the libc binary with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
file_parser = libc_commands.add_parser(
|
||||
'file',
|
||||
help = 'Dump information about a libc binary',
|
||||
description = 'Dump information about a libc binary'
|
||||
)
|
||||
|
||||
file_parser.add_argument(
|
||||
'files',
|
||||
metavar = 'files',
|
||||
nargs = '+',
|
||||
help = 'Libc binary to dump'
|
||||
)
|
||||
|
||||
file_parser.add_argument(
|
||||
'-s', '--symbols',
|
||||
metavar = 'symbols',
|
||||
nargs = '*',
|
||||
help = 'List of symbol offsets to dump in addition to the common ones'
|
||||
)
|
||||
|
||||
file_parser.add_argument(
|
||||
'-o', '--offset',
|
||||
metavar = 'offset',
|
||||
type = str,
|
||||
help = 'Display all offsets relative to this symbol'
|
||||
)
|
||||
|
||||
file_parser.add_argument(
|
||||
'--unstrip',
|
||||
action = 'store_true',
|
||||
default = False,
|
||||
help = 'Attempt to unstrip the libc binary inplace with debug symbols from a debuginfod server'
|
||||
)
|
||||
|
||||
common_symbols = ['dup2', 'printf', 'puts', 'read', 'system', 'write']
|
||||
|
||||
def find_libc(params):
|
||||
import requests
|
||||
url = "https://libc.rip/api/find"
|
||||
result = requests.post(url, json=params, timeout=20)
|
||||
log.debug('Request: %s', params)
|
||||
log.debug('Result: %s', result.json())
|
||||
if result.status_code != 200 or len(result.json()) == 0:
|
||||
log.failure("Could not find libc for %s on libc.rip", params)
|
||||
return []
|
||||
|
||||
return result.json()
|
||||
|
||||
def print_libc(libc):
|
||||
log.info('%s', text.red(libc['id']))
|
||||
log.indented('\t%-20s %s', text.green('BuildID:'), libc['buildid'])
|
||||
log.indented('\t%-20s %s', text.green('MD5:'), libc['md5'])
|
||||
log.indented('\t%-20s %s', text.green('SHA1:'), libc['sha1'])
|
||||
log.indented('\t%-20s %s', text.green('SHA256:'), libc['sha256'])
|
||||
log.indented('\t%s', text.green('Symbols:'))
|
||||
for symbol in libc['symbols'].items():
|
||||
log.indented('\t%25s = %s', symbol[0], symbol[1])
|
||||
|
||||
def handle_remote_libc(args, libc):
|
||||
print_libc(libc)
|
||||
if args.download_libc:
|
||||
path = libcdb.search_by_build_id(libc['buildid'], args.unstrip)
|
||||
if path:
|
||||
if args.unstrip:
|
||||
libcdb.unstrip_libc(path)
|
||||
shutil.copy(path, './{}.so'.format(libc['id']))
|
||||
|
||||
def translate_offset(offs, args, exe):
|
||||
if args.offset:
|
||||
if args.offset not in exe.symbols:
|
||||
log.info_once('offset symbol %s not found. ignoring.', args.offset)
|
||||
return offs
|
||||
return offs - exe.symbols[args.offset]
|
||||
return offs
|
||||
|
||||
def collect_synthetic_symbols(exe):
|
||||
available_symbols = ['str_bin_sh']
|
||||
exe.symbols['str_bin_sh'] = next(exe.search(b'/bin/sh\x00'))
|
||||
|
||||
libc_start_main_return = exe.libc_start_main_return
|
||||
if libc_start_main_return > 0:
|
||||
exe.symbols['__libc_start_main_ret'] = libc_start_main_return
|
||||
available_symbols.append('__libc_start_main_ret')
|
||||
|
||||
return available_symbols
|
||||
|
||||
def main(args):
|
||||
if len(sys.argv) < 3:
|
||||
parser.print_usage()
|
||||
sys.exit()
|
||||
|
||||
if args.libc_command == 'lookup':
|
||||
pairs = args.symbol_offset_pairs
|
||||
if len(pairs) % 2 != 0:
|
||||
log.failure('Uneven number of arguments. Please provide "symbol offset" pairs')
|
||||
return
|
||||
|
||||
symbols = {pairs[i]:pairs[i+1] for i in range(0, len(pairs), 2)}
|
||||
matched_libcs = find_libc({'symbols': symbols})
|
||||
for libc in matched_libcs:
|
||||
handle_remote_libc(args, libc)
|
||||
|
||||
elif args.libc_command == 'hash':
|
||||
for hash_value in args.hash_value:
|
||||
matched_libcs = find_libc({args.hash_type: hash_value})
|
||||
for libc in matched_libcs:
|
||||
handle_remote_libc(args, libc)
|
||||
|
||||
elif args.libc_command == 'file':
|
||||
from hashlib import md5, sha1, sha256
|
||||
for file in args.files:
|
||||
if not os.path.exists(file) or not os.path.isfile(file):
|
||||
log.failure('File does not exist %s', args.file)
|
||||
continue
|
||||
|
||||
if args.unstrip:
|
||||
libcdb.unstrip_libc(file)
|
||||
|
||||
exe = ELF(file, checksec=False)
|
||||
log.info('%s', text.red(os.path.basename(file)))
|
||||
|
||||
libc_version = re.search(b'libc[ -](\d+\.\d+)', exe.data)
|
||||
if libc_version:
|
||||
log.indented('%-20s %s', text.green('Version:'), libc_version.group(1).decode())
|
||||
|
||||
if exe.buildid:
|
||||
log.indented('%-20s %s', text.green('BuildID:'), enhex(exe.buildid))
|
||||
log.indented('%-20s %s', text.green('MD5:'), md5(exe.data).hexdigest())
|
||||
log.indented('%-20s %s', text.green('SHA1:'), sha1(exe.data).hexdigest())
|
||||
log.indented('%-20s %s', text.green('SHA256:'), sha256(exe.data).hexdigest())
|
||||
|
||||
# Always dump the basic list of common symbols
|
||||
log.indented('%s', text.green('Symbols:'))
|
||||
synthetic_symbols = collect_synthetic_symbols(exe)
|
||||
|
||||
symbols = common_symbols + (args.symbols or []) + synthetic_symbols
|
||||
symbols.sort()
|
||||
for symbol in symbols:
|
||||
if symbol not in exe.symbols:
|
||||
log.indented('%25s = %s', symbol, text.red('not found'))
|
||||
else:
|
||||
log.indented('%25s = %#x', symbol, translate_offset(exe.symbols[symbol], args, exe))
|
||||
|
||||
if __name__ == '__main__':
|
||||
pwnlib.commandline.common.main(__file__)
|
||||
@@ -14,6 +14,7 @@ from pwnlib.commandline import elfdiff
|
||||
from pwnlib.commandline import elfpatch
|
||||
from pwnlib.commandline import errno
|
||||
from pwnlib.commandline import hex
|
||||
from pwnlib.commandline import libcdb
|
||||
from pwnlib.commandline import phd
|
||||
from pwnlib.commandline import pwnstrip
|
||||
from pwnlib.commandline import scramble
|
||||
@@ -37,6 +38,7 @@ commands = {
|
||||
'elfpatch': elfpatch.main,
|
||||
'errno': errno.main,
|
||||
'hex': hex.main,
|
||||
'libcdb': libcdb.main,
|
||||
'phd': phd.main,
|
||||
'pwnstrip': pwnstrip.main,
|
||||
'scramble': scramble.main,
|
||||
|
||||
@@ -769,6 +769,7 @@ class ContextType(object):
|
||||
# We have to make sure that x86_64 appears before x86 for this to work correctly.
|
||||
transform = [('ppc64', 'powerpc64'),
|
||||
('ppc', 'powerpc'),
|
||||
('x86-64', 'amd64'),
|
||||
('x86_64', 'amd64'),
|
||||
('x86', 'i386'),
|
||||
('i686', 'i386'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import division
|
||||
import argparse
|
||||
import keyword
|
||||
@@ -137,6 +137,7 @@ def can_be_string(arg):
|
||||
if arg.type == 'void' and arg.derefcnt == 1:
|
||||
return True
|
||||
|
||||
|
||||
def can_be_array(arg):
|
||||
if arg.type == 'char' and arg.derefcnt == 2:
|
||||
return True
|
||||
@@ -160,11 +161,13 @@ def fix_bad_arg_names(func, arg):
|
||||
def get_arg_default(arg):
|
||||
return 0
|
||||
|
||||
|
||||
def fix_rt_syscall_name(name):
|
||||
if name.startswith('rt_'):
|
||||
return name[3:]
|
||||
return name
|
||||
|
||||
|
||||
def fix_syscall_names(name):
|
||||
# Do not use old_mmap
|
||||
if name == 'SYS_mmap':
|
||||
@@ -180,6 +183,7 @@ def main(target):
|
||||
with context.local(arch=arch):
|
||||
generate_one(target)
|
||||
|
||||
|
||||
def generate_one(target):
|
||||
SYSCALL_NAMES = [c for c in dir(constants) if c.startswith('SYS_')]
|
||||
|
||||
@@ -214,46 +218,50 @@ def generate_one(target):
|
||||
|
||||
# Set up the argument string for Mako
|
||||
argument_names = []
|
||||
argument_names_ = []
|
||||
argument_defaults = []
|
||||
|
||||
string_arguments = []
|
||||
array_arguments = []
|
||||
arg_docs = []
|
||||
|
||||
#
|
||||
|
||||
for arg in function.args:
|
||||
argname = fix_bad_arg_names(function, arg)
|
||||
argname_ = fix_bad_arg_names(function, arg)
|
||||
argname = argname_.rstrip('_')
|
||||
default = get_arg_default(arg)
|
||||
|
||||
if can_be_array(arg):
|
||||
array_arguments.append(argname)
|
||||
|
||||
if can_be_string(arg):
|
||||
string_arguments.append(argname)
|
||||
|
||||
argtype = str(arg.type) + ('*' * arg.derefcnt)
|
||||
arg_docs.append(
|
||||
' {argname_}({argtype}): {argname}'.format(
|
||||
argname_=argname_,
|
||||
argname=argname,
|
||||
argtype=argtype,
|
||||
))
|
||||
|
||||
# Mako is unable to use *vararg and *kwarg, so we just stub in
|
||||
# a whole bunch of additional arguments.
|
||||
if argname == 'vararg':
|
||||
for j in range(5):
|
||||
argname = 'vararg_%i' % j
|
||||
argument_names.append(argname)
|
||||
argument_names_.append(argname)
|
||||
argument_defaults.append('%s=%s' % (argname, None))
|
||||
break
|
||||
|
||||
argument_names.append(argname)
|
||||
argument_defaults.append('%s=%s' % (argname, default))
|
||||
argument_names_.append(argname_)
|
||||
argument_defaults.append('%s=%s' % (argname_, default))
|
||||
|
||||
arguments_default_values = ', '.join(argument_defaults)
|
||||
arguments_comma_separated = ', '.join(argument_names)
|
||||
|
||||
string_arguments = []
|
||||
array_arguments = []
|
||||
arg_docs = []
|
||||
|
||||
for arg in function.args:
|
||||
|
||||
if can_be_array(arg):
|
||||
array_arguments.append(arg.name)
|
||||
|
||||
if can_be_string(arg):
|
||||
string_arguments.append(arg.name)
|
||||
|
||||
argname = arg.name
|
||||
argtype = str(arg.type) + ('*' * arg.derefcnt)
|
||||
arg_docs.append(
|
||||
' {argname}({argtype}): {argname}'.format(argname=argname,
|
||||
argtype=argtype))
|
||||
arguments_comma_separated = ', '.join(argument_names_)
|
||||
|
||||
return_type = str(function.type) + ('*' * function.derefcnt)
|
||||
arg_docs = '\n'.join(arg_docs)
|
||||
@@ -282,6 +290,7 @@ def generate_one(target):
|
||||
with open(os.path.join(target, name + '.asm'), 'wt') as f:
|
||||
f.write('\n'.join(map(str.strip, lines)) + '\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('target_directory')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
"""Script for downloading lists of user agent strings."""
|
||||
from __future__ import division
|
||||
import os
|
||||
@@ -43,7 +43,7 @@ with log.waitfor('Fetching from from http://www.user-agents.org') as l:
|
||||
l.success()
|
||||
|
||||
with log.waitfor('Parsing list') as l:
|
||||
for item in soup.__getattr__('user-agents'):
|
||||
for item in getattr(soup, 'user-agents'):
|
||||
if item.name == 'user-agent':
|
||||
ua = item.select('string')[0].string.strip()
|
||||
try:
|
||||
|
||||
@@ -445,7 +445,7 @@ class Corefile(ELF):
|
||||
will be loaded.
|
||||
|
||||
>>> process('bash').corefile.libc # doctest: +ELLIPSIS
|
||||
Mapping('/.../libc-....so', start=0x..., stop=0x..., size=0x..., flags=..., page_offset=...)
|
||||
Mapping('.../libc...so...', start=0x..., stop=0x..., size=0x..., flags=..., page_offset=...)
|
||||
|
||||
The corefile also contains a :attr:`.stack` property, which gives
|
||||
us direct access to the stack contents. On Linux, the very top of the stack
|
||||
@@ -759,7 +759,7 @@ class Corefile(ELF):
|
||||
@property
|
||||
def libc(self):
|
||||
""":class:`Mapping`: First mapping for ``libc.so``"""
|
||||
expr = r'libc\b.*so$'
|
||||
expr = r'^libc\b.*so(?:\.6)?$'
|
||||
|
||||
for m in self.mappings:
|
||||
if not m.name:
|
||||
@@ -1184,14 +1184,13 @@ class Corefile(ELF):
|
||||
pwnlib.gdb.attach(self, exe=self.exe.path)
|
||||
|
||||
def __getattr__(self, attribute):
|
||||
if self.prstatus:
|
||||
if hasattr(self.prstatus, attribute):
|
||||
return getattr(self.prstatus, attribute)
|
||||
if attribute.startswith('_') or not self.prstatus:
|
||||
raise AttributeError(attribute)
|
||||
|
||||
if hasattr(self.prstatus.pr_reg, attribute):
|
||||
return getattr(self.prstatus.pr_reg, attribute)
|
||||
if hasattr(self.prstatus, attribute):
|
||||
return getattr(self.prstatus, attribute)
|
||||
|
||||
return super(Corefile, self).__getattribute__(attribute)
|
||||
return getattr(self.prstatus.pr_reg, attribute)
|
||||
|
||||
# Override routines which don't make sense for Corefiles
|
||||
def _populate_got(*a): pass
|
||||
|
||||
@@ -604,7 +604,8 @@ class user_regs_struct_aarch64(ctypes.Structure):
|
||||
def __getattr__(self, name):
|
||||
if name.startswith('r'):
|
||||
name = 'x' + name[1:]
|
||||
return getattr(name) & 0xffffffff
|
||||
return getattr(self, name) & 0xffffffff
|
||||
raise AttributeError(name)
|
||||
|
||||
class elf_prstatus_i386(ctypes.Structure):
|
||||
_fields_ = generate_prstatus_common(32, user_regs_struct_i386)
|
||||
|
||||
+47
-17
@@ -173,8 +173,7 @@ class dotdict(dict):
|
||||
|
||||
if subkeys:
|
||||
return dotdict(subkeys)
|
||||
|
||||
return getattr(super(dotdict, self), name)
|
||||
raise AttributeError(name)
|
||||
|
||||
class ELF(ELFFile):
|
||||
"""Encapsulates information about an ELF file.
|
||||
@@ -1082,11 +1081,17 @@ class ELF(ELFFile):
|
||||
|
||||
@property
|
||||
def libc_start_main_return(self):
|
||||
"""
|
||||
Try to find the return address from main into __libc_start_main.
|
||||
The heuristic to find the call to the function pointer of main is
|
||||
to list all calls inside __libc_start_main, find the call to exit
|
||||
after the call to main and select the previous call.
|
||||
""":class:`int`: Address of the return address into __libc_start_main from main.
|
||||
|
||||
>>> bash = ELF(which('bash'))
|
||||
>>> libc = bash.libc
|
||||
>>> libc.libc_start_main_return > 0
|
||||
True
|
||||
|
||||
Try to find the return address from main into __libc_start_main.
|
||||
The heuristic to find the call to the function pointer of main is
|
||||
to list all calls inside __libc_start_main, find the call to exit
|
||||
after the call to main and select the previous call.
|
||||
"""
|
||||
if '__libc_start_main' not in self.functions:
|
||||
return 0
|
||||
@@ -1110,18 +1115,43 @@ class ELF(ELFFile):
|
||||
log.error('Unsupported architecture %s in ELF.libc_start_main_return', self.arch)
|
||||
return 0
|
||||
|
||||
code = self.disasm(self.symbols['__libc_start_main'], self.functions['__libc_start_main'].size)
|
||||
lines = self.functions['__libc_start_main'].disasm().split('\n')
|
||||
exit_addr = hex(self.symbols['exit'])
|
||||
lines = code.split('\n')
|
||||
calls = [(index, line) for index, line in enumerate(lines) if set(line.split()) & call_instructions]
|
||||
exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
|
||||
if len(exit_calls) != 1:
|
||||
return 0
|
||||
|
||||
call_to_main = calls[exit_calls[0] - 1]
|
||||
return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
|
||||
return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
|
||||
return return_from_main
|
||||
def find_ret_main_addr(lines, calls):
|
||||
exit_calls = [index for index, line in enumerate(calls) if exit_addr in line[1]]
|
||||
if len(exit_calls) != 1:
|
||||
return 0
|
||||
|
||||
call_to_main = calls[exit_calls[0] - 1]
|
||||
return_from_main = lines[call_to_main[0] + call_return_offset].lstrip()
|
||||
return_from_main = int(return_from_main[ : return_from_main.index(':') ], 16)
|
||||
return return_from_main
|
||||
|
||||
# Starting with glibc-2.34 calling `main` is split out into `__libc_start_call_main`
|
||||
ret_addr = find_ret_main_addr(lines, calls)
|
||||
# Pre glibc-2.34 case - `main` is called directly
|
||||
if ret_addr:
|
||||
return ret_addr
|
||||
|
||||
# `__libc_start_main` -> `__libc_start_call_main` -> `main`
|
||||
# Find a direct call which calls `exit` once. That's probably `__libc_start_call_main`.
|
||||
direct_call_pattern = re.compile(r'['+r'|'.join(call_instructions)+r']\s+(0x[0-9a-zA-Z]+)')
|
||||
for line in calls:
|
||||
match = direct_call_pattern.search(line[1])
|
||||
if not match:
|
||||
continue
|
||||
|
||||
target_addr = int(match.group(1), 0)
|
||||
# `__libc_start_call_main` is usually smaller than `__libc_start_main`, so
|
||||
# we might disassemble a bit too much, but it's a good dynamic estimate.
|
||||
callee_lines = self.disasm(target_addr, self.functions['__libc_start_main'].size).split('\n')
|
||||
callee_calls = [(index, line) for index, line in enumerate(callee_lines) if set(line.split()) & call_instructions]
|
||||
ret_addr = find_ret_main_addr(callee_lines, callee_calls)
|
||||
if ret_addr:
|
||||
return ret_addr
|
||||
return 0
|
||||
|
||||
def search(self, needle, writable = False, executable = False):
|
||||
"""search(needle, writable = False, executable = False) -> generator
|
||||
@@ -1759,7 +1789,7 @@ class ELF(ELFFile):
|
||||
@property
|
||||
def packed(self):
|
||||
""":class:`bool`: Whether the current binary is packed with UPX."""
|
||||
return b'UPX!' in self.get_data()
|
||||
return b'UPX!' in self.get_data()[:0xFF]
|
||||
|
||||
@property
|
||||
def pie(self):
|
||||
|
||||
+8
-13
@@ -125,7 +125,7 @@ class FileStructure(object):
|
||||
>>> len(fileStr)
|
||||
224
|
||||
|
||||
The defination for __repr__ orders the structure members and displays then in a dictionary format. It's useful when viewing a structure objet in python/IPython shell
|
||||
The definition for __repr__ orders the structure members and displays then in a dictionary format. It's useful when viewing a structure objet in python/IPython shell
|
||||
|
||||
>>> q=FileStructure(0xdeadbeef)
|
||||
>>> q
|
||||
@@ -176,25 +176,20 @@ class FileStructure(object):
|
||||
def __repr__(self):
|
||||
structure=[]
|
||||
for i in self.vars_:
|
||||
structure.append(" %s: %s" % (i,hex(self.__getattr__(i))))
|
||||
structure.append(" %s: %s" % (i,hex(getattr(self, i))))
|
||||
return "{"+ "\n".join(structure)+"}"
|
||||
|
||||
def __getattr__(self,item):
|
||||
if item in FileStructure.__dict__ or item in self.vars_:
|
||||
return object.__getattribute__(self,item)
|
||||
log.error("Unknown variable %r" % item)
|
||||
|
||||
def __len__(self):
|
||||
return len(bytes(self))
|
||||
|
||||
def __bytes__(self):
|
||||
structure = b''
|
||||
for val in self.vars_:
|
||||
if isinstance(self.__getattr__(val), bytes):
|
||||
structure += self.__getattr__(val).ljust(context.bytes, b'\x00')
|
||||
if isinstance(getattr(self, val), bytes):
|
||||
structure += getattr(self, val).ljust(context.bytes, b'\x00')
|
||||
else:
|
||||
if self.length[val] > 0:
|
||||
structure += pack(self.__getattr__(val), self.length[val]*8)
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
return structure
|
||||
|
||||
def struntil(self,v):
|
||||
@@ -219,10 +214,10 @@ class FileStructure(object):
|
||||
return b''
|
||||
structure = b''
|
||||
for val in self.vars_:
|
||||
if isinstance(self.__getattr__(val), bytes):
|
||||
structure += self.__getattr__(val).ljust(context.bytes, b'\x00')
|
||||
if isinstance(getattr(self, val), bytes):
|
||||
structure += getattr(self, val).ljust(context.bytes, b'\x00')
|
||||
else:
|
||||
structure += pack(self.__getattr__(val), self.length[val]*8)
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
if val == v:
|
||||
break
|
||||
return structure[:-1]
|
||||
|
||||
+3
-3
@@ -878,7 +878,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
pre += 'set gnutarget ' + _bfdname() + '\n'
|
||||
|
||||
if exe and context.os != 'baremetal':
|
||||
pre += 'file %s\n' % exe
|
||||
pre += 'file "%s"\n' % exe
|
||||
|
||||
# let's see if we can find a pid to attach to
|
||||
pid = None
|
||||
@@ -967,7 +967,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
|
||||
exe = exe or findexe()
|
||||
elif isinstance(target, elf.corefile.Corefile):
|
||||
pre += 'target core %s\n' % target.path
|
||||
pre += 'target core "%s"\n' % target.path
|
||||
else:
|
||||
log.error("don't know how to attach to target: %r", target)
|
||||
|
||||
@@ -1329,7 +1329,7 @@ def version(program='gdb'):
|
||||
|
||||
Example:
|
||||
|
||||
>>> (7,0) <= gdb.version() <= (12,0)
|
||||
>>> (7,0) <= gdb.version() <= (19,0)
|
||||
True
|
||||
"""
|
||||
program = misc.which(program)
|
||||
|
||||
@@ -99,6 +99,7 @@ import os
|
||||
import random
|
||||
import re
|
||||
import six
|
||||
import string
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
@@ -401,6 +402,20 @@ class Logger(object):
|
||||
|
||||
self.info(pwnlib.util.fiddling.hexdump(message, *args, **kwargs))
|
||||
|
||||
def maybe_hexdump(self, message, *args, **kwargs):
|
||||
"""maybe_hexdump(self, message, *args, **kwargs)
|
||||
|
||||
Logs a message using indented. Repeated single byte is compressed, and
|
||||
unprintable message is hexdumped.
|
||||
"""
|
||||
if len(set(message)) == 1 and len(message) > 1:
|
||||
self.indented('%r * %#x' % (message[:1], len(message)), *args, **kwargs)
|
||||
elif len(message) == 1 or all(c in string.printable.encode() for c in message):
|
||||
for line in message.splitlines(True):
|
||||
self.indented(repr(line), *args, **kwargs)
|
||||
else:
|
||||
import pwnlib.util.fiddling
|
||||
self.indented(pwnlib.util.fiddling.hexdump(message), *args, **kwargs)
|
||||
|
||||
def warning(self, message, *args, **kwargs):
|
||||
"""warning(message, *args, **kwargs)
|
||||
|
||||
@@ -246,6 +246,13 @@ class Call(object):
|
||||
fmt % self.target,
|
||||
self.args)
|
||||
|
||||
def is_flat(self):
|
||||
if isinstance(self, six.integer_types + (Unresolved,)):
|
||||
return True
|
||||
if not isinstance(self, Call):
|
||||
return False
|
||||
return bool(self.args or self.stack_arguments_before)
|
||||
|
||||
@property
|
||||
def register_arguments(self):
|
||||
return dict(zip(self.abi.register_arguments, self.args))
|
||||
|
||||
+55
-12
@@ -30,6 +30,7 @@ Other, more complicated gdagets also happen magically
|
||||
Gadget(0x10000006, ['pop ecx', 'pop ebx', 'ret'], ['ecx', 'ebx'], 0xc)
|
||||
|
||||
The easiest way to set up individual registers is to invoke the ``ROP`` object as a callable, with the registers as arguments.
|
||||
This has the benefit of using multi-pop gadgets to set multiple registers with one gadget.
|
||||
|
||||
>>> rop(eax=0x11111111, ecx=0x22222222)
|
||||
|
||||
@@ -44,6 +45,37 @@ which corresponds to their offset, which is useful when debuggging your exploit.
|
||||
0x000c: 0x10000004 pop eax; ret
|
||||
0x0010: 0x11111111
|
||||
|
||||
|
||||
If you really want to set one register at a time, you can also use the assignment form.
|
||||
It's generally advised to use the rop(eax=..., ecx=...) form, since there may be an
|
||||
e.g. ``pop eax; pop ecx; ret`` gadget that can be taken advantage of.
|
||||
|
||||
>>> rop = ROP(binary)
|
||||
>>> rop.eax = 0xdeadf00d
|
||||
>>> rop.ecx = 0xc01dbeef
|
||||
>>> rop.raw(0xffffffff)
|
||||
>>> print(rop.dump())
|
||||
0x0000: 0x10000004 pop eax; ret
|
||||
0x0004: 0xdeadf00d
|
||||
0x0008: 0x10000006 pop ecx; pop ebx; ret
|
||||
0x000c: 0xc01dbeef
|
||||
0x0010: b'eaaa' <pad ebx>
|
||||
0x0014: 0xffffffff
|
||||
|
||||
If you just want to FIND a ROP gadget, you can access them as a property on the ``ROP``
|
||||
object by register name.
|
||||
|
||||
>>> rop = ROP(binary)
|
||||
>>> rop.eax
|
||||
Gadget(0x10000004, ['pop eax', 'ret'], ['eax'], 0x8)
|
||||
>>> hex(rop.eax.address)
|
||||
'0x10000004'
|
||||
>>> rop.raw(rop.eax)
|
||||
>>> rop.raw(0x12345678)
|
||||
>>> print(rop.dump())
|
||||
0x0000: 0x10000004 pop eax; ret
|
||||
0x0004: 0x12345678
|
||||
|
||||
Let's re-create our ROP object now to show for some other examples.:
|
||||
|
||||
>>> rop = ROP(binary)
|
||||
@@ -92,12 +124,10 @@ The stack is automatically adjusted for the next frame
|
||||
0x001c: 0x6 arg2
|
||||
0x0020: b'iaaa' <pad>
|
||||
0x0024: 0xdecafbad write(7, 8, 9)
|
||||
0x0028: 0x10000000 <adjust @0x3c> add esp, 0x10; ret
|
||||
0x0028: 0xfeedface exit()
|
||||
0x002c: 0x7 arg0
|
||||
0x0030: 0x8 arg1
|
||||
0x0034: 0x9 arg2
|
||||
0x0038: b'oaaa' <pad>
|
||||
0x003c: 0xfeedface exit()
|
||||
|
||||
You can also append complex arguments onto stack when the stack pointer is known.
|
||||
|
||||
@@ -188,18 +218,16 @@ Finally, let's build our ROP stack
|
||||
>>> rop.exit()
|
||||
>>> print(rop.dump())
|
||||
0x0000: 0x10000012 write(STDOUT_FILENO, 0x10000026, 8)
|
||||
0x0004: 0x1000000e <adjust @0x18> add esp, 0x10; ret
|
||||
0x0004: 0x1000002f exit()
|
||||
0x0008: 0x1 STDOUT_FILENO
|
||||
0x000c: 0x10000026 flag
|
||||
0x0010: 0x8 arg2
|
||||
0x0014: b'faaa' <pad>
|
||||
0x0018: 0x1000002f exit()
|
||||
|
||||
The raw data from the ROP stack is available via `str`.
|
||||
The raw data from the ROP stack is available via `r.chain()` (or `bytes(r)`).
|
||||
|
||||
>>> raw_rop = rop.chain()
|
||||
>>> print(enhex(raw_rop))
|
||||
120000100e000010010000002600001008000000666161612f000010
|
||||
120000102f000010010000002600001008000000
|
||||
|
||||
Let's try it out!
|
||||
|
||||
@@ -927,7 +955,7 @@ class ROP(object):
|
||||
# If there were arguments on the stack, we need to stick something
|
||||
# in the slot where the return address goes.
|
||||
if len(stackArguments) > 0:
|
||||
if remaining:
|
||||
if remaining and (remaining > 1 or Call.is_flat(chain[-1])):
|
||||
fix_size = (1 + len(stackArguments))
|
||||
fix_bytes = fix_size * context.bytes
|
||||
adjust = self.search(move = fix_bytes)
|
||||
@@ -944,6 +972,13 @@ class ROP(object):
|
||||
stackArguments.append(Padding())
|
||||
|
||||
# We could not find a proper "adjust" gadget, but also didn't need one.
|
||||
elif remaining:
|
||||
_, nxslot = next(iterable)
|
||||
stack.describe(self.describe(nxslot))
|
||||
if isinstance(nxslot, Call):
|
||||
stack.append(nxslot.target)
|
||||
else:
|
||||
stack.append(nxslot)
|
||||
else:
|
||||
stack.append(Padding("<return address>"))
|
||||
|
||||
@@ -1189,6 +1224,9 @@ class ROP(object):
|
||||
|
||||
def __get_cachefile_name(self, files):
|
||||
"""Given an ELF or list of ELF objects, return a cache file for the set of files"""
|
||||
if context.cache_dir is None:
|
||||
return None
|
||||
|
||||
cachedir = os.path.join(context.cache_dir, 'rop-cache')
|
||||
if not os.path.exists(cachedir):
|
||||
os.mkdir(cachedir)
|
||||
@@ -1205,12 +1243,14 @@ class ROP(object):
|
||||
@staticmethod
|
||||
def clear_cache():
|
||||
"""Clears the ROP gadget cache"""
|
||||
if context.cache_dir is None:
|
||||
return
|
||||
cachedir = os.path.join(context.cache_dir, 'rop-cache')
|
||||
shutil.rmtree(cachedir)
|
||||
|
||||
def __cache_load(self, elf):
|
||||
filename = self.__get_cachefile_name(elf)
|
||||
if not os.path.exists(filename):
|
||||
if filename is None or not os.path.exists(filename):
|
||||
return None
|
||||
gadgets = eval(open(filename).read())
|
||||
gadgets = {k - elf.load_addr + elf.address:v for k, v in gadgets.items()}
|
||||
@@ -1218,8 +1258,11 @@ class ROP(object):
|
||||
return gadgets
|
||||
|
||||
def __cache_save(self, elf, data):
|
||||
filename = self.__get_cachefile_name(elf)
|
||||
if filename is None:
|
||||
return
|
||||
data = {k + elf.load_addr - elf.address:v for k, v in data.items()}
|
||||
open(self.__get_cachefile_name(elf), 'w+').write(repr(data))
|
||||
open(filename, 'w+').write(repr(data))
|
||||
|
||||
def __load(self):
|
||||
"""Load all ROP gadgets for the selected ELF files"""
|
||||
@@ -1274,7 +1317,7 @@ class ROP(object):
|
||||
pass
|
||||
|
||||
def __getattr__(self, k):
|
||||
return self._fd.__getattribute__(k)
|
||||
return getattr(self._fd, k)
|
||||
|
||||
gadgets = {}
|
||||
for elf in self.elfs:
|
||||
|
||||
+3
-1
@@ -391,7 +391,9 @@ class SigreturnFrame(dict):
|
||||
self.set_regvalue(attr, value)
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return self[attr]
|
||||
if attr in self:
|
||||
return self[attr]
|
||||
raise AttributeError(attr)
|
||||
|
||||
def __bytes__(self):
|
||||
frame = b""
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from pwnlib.context import context
|
||||
|
||||
@@ -109,47 +110,17 @@ def get_context_from_dirpath(directory):
|
||||
return {'os': osys, 'arch': arch}
|
||||
|
||||
def make_function(funcname, filename, directory):
|
||||
import functools
|
||||
import inspect
|
||||
path = os.path.join(directory, filename)
|
||||
template = lookup_template(path)
|
||||
|
||||
args, varargs, keywords, defaults = inspect.getargspec(template.module.render_body)
|
||||
|
||||
defaults = defaults or []
|
||||
|
||||
if len(defaults) < len(args) and args[0] == 'context':
|
||||
args.pop(0)
|
||||
|
||||
args_used = args[:]
|
||||
|
||||
for n, default in enumerate(defaults, len(args) - len(defaults)):
|
||||
args[n] = '%s = %r' % (args[n], default)
|
||||
|
||||
if varargs:
|
||||
args.append('*' + varargs)
|
||||
args_used.append('*' + varargs)
|
||||
|
||||
if keywords not in ['pageargs', None]:
|
||||
args.append('**' + keywords)
|
||||
args_used.append('**' + keywords)
|
||||
|
||||
docstring = inspect.cleandoc(template.module.__doc__ or '')
|
||||
args = ', '.join(args)
|
||||
args_used = ', '.join(args_used)
|
||||
local_ctx = get_context_from_dirpath(directory)
|
||||
|
||||
# This is a slight hack to get the right signature for the function
|
||||
# It would be possible to simply create an (*args, **kwargs) wrapper,
|
||||
# but what would not have the right signature.
|
||||
# While we are at it, we insert the docstring too
|
||||
T = r'''
|
||||
def wrap(template, render_global):
|
||||
import pwnlib
|
||||
def %(funcname)s(%(args)s):
|
||||
%(docstring)r
|
||||
def res(*args, **kwargs):
|
||||
with render_global.go_inside() as was_inside:
|
||||
with pwnlib.context.context.local(**%(local_ctx)s):
|
||||
lines = template.render(%(args_used)s).split('\n')
|
||||
with context.local(**local_ctx):
|
||||
lines = template.render(*args, **kwargs).split('\n')
|
||||
for i, line in enumerate(lines):
|
||||
def islabelchar(c):
|
||||
return c.isalnum() or c == '.' or c == '_'
|
||||
@@ -168,19 +139,16 @@ def wrap(template, render_global):
|
||||
return s
|
||||
else:
|
||||
return s + '\n'
|
||||
return %(funcname)s
|
||||
''' % locals()
|
||||
|
||||
g = {}
|
||||
exec(T, g, g)
|
||||
wrap = g['wrap']
|
||||
|
||||
# Setting _relpath is a slight hack only used to get better documentation
|
||||
res = wrap(template, render_global)
|
||||
res._relpath = path
|
||||
res.__module__ = 'pwnlib.shellcraft.' + os.path.dirname(path).replace('/','.')
|
||||
|
||||
import sys, functools
|
||||
res.__name__ = res.__qualname__ = funcname
|
||||
res.__doc__ = inspect.cleandoc(template.module.__doc__ or '')
|
||||
if hasattr(inspect, 'signature'):
|
||||
sig = inspect.signature(template.module.render_body)
|
||||
sig = sig.replace(parameters=list(sig.parameters.values())[1:-1])
|
||||
res.__signature__ = sig
|
||||
|
||||
@functools.wraps(res)
|
||||
def function(*a):
|
||||
|
||||
@@ -15,7 +15,7 @@ Arguments:
|
||||
fd(int): fd
|
||||
advice(int): advice
|
||||
offset(loff_t): offset
|
||||
len(loff_t): len
|
||||
length(loff_t): length
|
||||
Returns:
|
||||
long
|
||||
</%docstring>
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 bind' for more information.
|
||||
Arguments:
|
||||
fd(int): fd
|
||||
addr(CONST_SOCKADDR_ARG): addr
|
||||
len(socklen_t): len
|
||||
length(socklen_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 connect' for more information.
|
||||
Arguments:
|
||||
fd(int): fd
|
||||
addr(CONST_SOCKADDR_ARG): addr
|
||||
len(socklen_t): len
|
||||
length(socklen_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -15,7 +15,7 @@ Arguments:
|
||||
fd(int): fd
|
||||
mode(int): mode
|
||||
offset(off_t): offset
|
||||
len(off_t): len
|
||||
length(off_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 getpeername' for more information.
|
||||
Arguments:
|
||||
fd(int): fd
|
||||
addr(SOCKADDR_ARG): addr
|
||||
len(socklen_t*): len
|
||||
length(socklen_t*): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 getsockname' for more information.
|
||||
Arguments:
|
||||
fd(int): fd
|
||||
addr(SOCKADDR_ARG): addr
|
||||
len(socklen_t*): len
|
||||
length(socklen_t*): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -12,7 +12,7 @@ Invokes the syscall ioperm.
|
||||
See 'man 2 ioperm' for more information.
|
||||
|
||||
Arguments:
|
||||
from(unsigned): from
|
||||
from_(unsigned): from
|
||||
num(unsigned): num
|
||||
turn_on(int): turn_on
|
||||
Returns:
|
||||
@@ -28,7 +28,7 @@ Returns:
|
||||
can_pushstr = []
|
||||
can_pushstr_array = []
|
||||
|
||||
argument_names = ['from_', 'num', 'turn_on']
|
||||
argument_names = ['from', 'num', 'turn_on']
|
||||
argument_values = [from_, num, turn_on]
|
||||
|
||||
# Load all of the arguments into their destination registers / stack slots.
|
||||
|
||||
@@ -12,7 +12,7 @@ Invokes the syscall link.
|
||||
See 'man 2 link' for more information.
|
||||
|
||||
Arguments:
|
||||
from(char*): from
|
||||
from_(char*): from
|
||||
to(char*): to
|
||||
Returns:
|
||||
int
|
||||
@@ -27,7 +27,7 @@ Returns:
|
||||
can_pushstr = ['from', 'to']
|
||||
can_pushstr_array = []
|
||||
|
||||
argument_names = ['from_', 'to']
|
||||
argument_names = ['from', 'to']
|
||||
argument_values = [from_, to]
|
||||
|
||||
# Load all of the arguments into their destination registers / stack slots.
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 linkat' for more information.
|
||||
|
||||
Arguments:
|
||||
fromfd(int): fromfd
|
||||
from(char*): from
|
||||
from_(char*): from
|
||||
tofd(int): tofd
|
||||
to(char*): to
|
||||
flags(int): flags
|
||||
@@ -30,7 +30,7 @@ Returns:
|
||||
can_pushstr = ['from', 'to']
|
||||
can_pushstr_array = []
|
||||
|
||||
argument_names = ['fromfd', 'from_', 'tofd', 'to', 'flags']
|
||||
argument_names = ['fromfd', 'from', 'tofd', 'to', 'flags']
|
||||
argument_values = [fromfd, from_, tofd, to, flags]
|
||||
|
||||
# Load all of the arguments into their destination registers / stack slots.
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 lookup_dcookie' for more information.
|
||||
Arguments:
|
||||
cookie(u64): cookie
|
||||
buffer(char*): buffer
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 madvise' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
advice(int): advice
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 mbind' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(unsigned): len
|
||||
length(unsigned): length
|
||||
mode(int): mode
|
||||
nodemask(unsigned*): nodemask
|
||||
maxnode(unsigned): maxnode
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 mincore' for more information.
|
||||
|
||||
Arguments:
|
||||
start(void*): start
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
vec(unsigned*): vec
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 mlock' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 mlock2' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
flags(int): flags
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 mmap' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
prot(int): prot
|
||||
flags(int): flags
|
||||
fd(int): fd
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 mprotect' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
prot(int): prot
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 msync' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
flags(int): flags
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 munlock' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 munmap' for more information.
|
||||
|
||||
Arguments:
|
||||
addr(void*): addr
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 open' for more information.
|
||||
Arguments:
|
||||
file(char*): file
|
||||
oflag(int): oflag
|
||||
vararg(int): vararg
|
||||
mode(int): mode
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -15,7 +15,7 @@ Arguments:
|
||||
bus(unsigned): bus
|
||||
dfn(unsigned): dfn
|
||||
off(unsigned): off
|
||||
len(unsigned): len
|
||||
length(unsigned): length
|
||||
buf(void*): buf
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -15,7 +15,7 @@ Arguments:
|
||||
bus(unsigned): bus
|
||||
dfn(unsigned): dfn
|
||||
off(unsigned): off
|
||||
len(unsigned): len
|
||||
length(unsigned): length
|
||||
buf(void*): buf
|
||||
Returns:
|
||||
int
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 readlink' for more information.
|
||||
Arguments:
|
||||
path(char*): path
|
||||
buf(char*): buf
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
ssize_t
|
||||
</%docstring>
|
||||
|
||||
@@ -15,7 +15,7 @@ Arguments:
|
||||
fd(int): fd
|
||||
path(char*): path
|
||||
buf(char*): buf
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
ssize_t
|
||||
</%docstring>
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 set_robust_list' for more information.
|
||||
|
||||
Arguments:
|
||||
head(robust_list_head*): head
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
long
|
||||
</%docstring>
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 setdomainname' for more information.
|
||||
|
||||
Arguments:
|
||||
name(char*): name
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -13,7 +13,7 @@ See 'man 2 sethostname' for more information.
|
||||
|
||||
Arguments:
|
||||
name(char*): name
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
Returns:
|
||||
int
|
||||
</%docstring>
|
||||
|
||||
@@ -16,7 +16,7 @@ Arguments:
|
||||
offin(off64_t*): offin
|
||||
fdout(int): fdout
|
||||
offout(off64_t*): offout
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
flags(unsigned): flags
|
||||
Returns:
|
||||
ssize_t
|
||||
|
||||
@@ -12,7 +12,7 @@ Invokes the syscall symlink.
|
||||
See 'man 2 symlink' for more information.
|
||||
|
||||
Arguments:
|
||||
from(char*): from
|
||||
from_(char*): from
|
||||
to(char*): to
|
||||
Returns:
|
||||
int
|
||||
@@ -27,7 +27,7 @@ Returns:
|
||||
can_pushstr = ['from', 'to']
|
||||
can_pushstr_array = []
|
||||
|
||||
argument_names = ['from_', 'to']
|
||||
argument_names = ['from', 'to']
|
||||
argument_values = [from_, to]
|
||||
|
||||
# Load all of the arguments into their destination registers / stack slots.
|
||||
|
||||
@@ -12,7 +12,7 @@ Invokes the syscall symlinkat.
|
||||
See 'man 2 symlinkat' for more information.
|
||||
|
||||
Arguments:
|
||||
from(char*): from
|
||||
from_(char*): from
|
||||
tofd(int): tofd
|
||||
to(char*): to
|
||||
Returns:
|
||||
@@ -28,7 +28,7 @@ Returns:
|
||||
can_pushstr = ['from', 'to']
|
||||
can_pushstr_array = []
|
||||
|
||||
argument_names = ['from_', 'tofd', 'to']
|
||||
argument_names = ['from', 'tofd', 'to']
|
||||
argument_values = [from_, tofd, to]
|
||||
|
||||
# Load all of the arguments into their destination registers / stack slots.
|
||||
|
||||
@@ -14,7 +14,7 @@ See 'man 2 tee' for more information.
|
||||
Arguments:
|
||||
fdin(int): fdin
|
||||
fdout(int): fdout
|
||||
len(size_t): len
|
||||
length(size_t): length
|
||||
flags(unsigned): flags
|
||||
Returns:
|
||||
ssize_t
|
||||
|
||||
@@ -486,7 +486,7 @@ def init():
|
||||
def readline(self, size = None):
|
||||
return readline(size)
|
||||
def __getattr__(self, k):
|
||||
return self._fd.__getattribute__(k)
|
||||
return getattr(self._fd, k)
|
||||
sys.stdin = Wrapper(sys.stdin)
|
||||
|
||||
if six.PY2:
|
||||
|
||||
+1
-1
@@ -139,7 +139,7 @@ def init():
|
||||
def write(self, s):
|
||||
output(s, frozen = True)
|
||||
def __getattr__(self, k):
|
||||
return self._fd.__getattribute__(k)
|
||||
return getattr(self._fd, k)
|
||||
if sys.stdout.isatty():
|
||||
sys.stdout = Wrapper(sys.stdout)
|
||||
if sys.stderr.isatty():
|
||||
|
||||
@@ -89,6 +89,9 @@ class Module(types.ModuleType):
|
||||
return functools.partial(f, self)
|
||||
|
||||
def __getattr__(self, desc):
|
||||
if desc.startswith('_'):
|
||||
raise AttributeError(desc)
|
||||
|
||||
try:
|
||||
ds = desc.replace('gray', 'bright_black').split('_')
|
||||
init = ''
|
||||
|
||||
@@ -4,7 +4,7 @@ from __future__ import division
|
||||
from pwnlib.context import context
|
||||
|
||||
|
||||
class Buffer(Exception):
|
||||
class Buffer(object):
|
||||
"""
|
||||
List of strings with some helper routines.
|
||||
|
||||
|
||||
+14
-13
@@ -78,15 +78,13 @@ class listen(sock):
|
||||
fam='any', typ='tcp', *args, **kwargs):
|
||||
super(listen, self).__init__(*args, **kwargs)
|
||||
|
||||
port = int(port)
|
||||
|
||||
fam = self._get_family(fam)
|
||||
typ = self._get_type(typ)
|
||||
|
||||
if fam == socket.AF_INET and bindaddr == '::':
|
||||
bindaddr = '0.0.0.0'
|
||||
|
||||
h = self.waitfor('Trying to bind to %s on port %d' % (bindaddr, port))
|
||||
h = self.waitfor('Trying to bind to %s on port %s' % (bindaddr, port))
|
||||
|
||||
for res in socket.getaddrinfo(bindaddr, port, fam, typ, 0, socket.AI_PASSIVE):
|
||||
self.family, self.type, self.proto, self.canonname, self.sockaddr = res
|
||||
@@ -109,7 +107,7 @@ class listen(sock):
|
||||
break
|
||||
else:
|
||||
h.failure()
|
||||
self.error("Could not bind to %s on port %d" % (bindaddr, port))
|
||||
self.error("Could not bind to %s on port %s" % (bindaddr, port))
|
||||
|
||||
h.success()
|
||||
|
||||
@@ -159,15 +157,18 @@ class listen(sock):
|
||||
self.sock
|
||||
return self
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key == 'sock':
|
||||
self._accepter.join(timeout = self.timeout)
|
||||
if 'sock' in self.__dict__:
|
||||
return self.sock
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return getattr(super(listen, self), key)
|
||||
@property
|
||||
def sock(self):
|
||||
try:
|
||||
return self.__dict__['sock']
|
||||
except KeyError:
|
||||
pass
|
||||
self._accepter.join(timeout=self.timeout)
|
||||
return self.__dict__.get('sock')
|
||||
|
||||
@sock.setter
|
||||
def sock(self, s):
|
||||
self.__dict__['sock'] = s
|
||||
|
||||
def close(self):
|
||||
# since `close` is scheduled to run on exit we must check that we got
|
||||
|
||||
@@ -608,7 +608,7 @@ class process(tube):
|
||||
"""Permit pass-through access to the underlying process object for
|
||||
fields like ``pid`` and ``stdin``.
|
||||
"""
|
||||
if hasattr(self.proc, attr):
|
||||
if not attr.startswith('_') and hasattr(self.proc, attr):
|
||||
return getattr(self.proc, attr)
|
||||
raise AttributeError("'process' object has no attribute '%s'" % attr)
|
||||
|
||||
@@ -740,10 +740,14 @@ class process(tube):
|
||||
# First check if we are already dead
|
||||
self.poll()
|
||||
|
||||
#close file descriptors
|
||||
# close file descriptors
|
||||
for fd in [self.proc.stdin, self.proc.stdout, self.proc.stderr]:
|
||||
if fd is not None:
|
||||
fd.close()
|
||||
try:
|
||||
fd.close()
|
||||
except IOError as e:
|
||||
if e.errno != errno.EPIPE:
|
||||
raise
|
||||
|
||||
if not self._stop_noticed:
|
||||
try:
|
||||
|
||||
@@ -61,7 +61,7 @@ class remote(sock):
|
||||
*args, **kwargs):
|
||||
super(remote, self).__init__(*args, **kwargs)
|
||||
|
||||
self.rport = int(port)
|
||||
self.rport = port
|
||||
self.rhost = host
|
||||
|
||||
if sock:
|
||||
@@ -99,7 +99,7 @@ class remote(sock):
|
||||
sock = None
|
||||
timeout = self.timeout
|
||||
|
||||
with self.waitfor('Opening connection to %s on port %d' % (self.rhost, self.rport)) as h:
|
||||
with self.waitfor('Opening connection to %s on port %s' % (self.rhost, self.rport)) as h:
|
||||
for res in socket.getaddrinfo(self.rhost, self.rport, fam, typ, 0, socket.AI_PASSIVE):
|
||||
self.family, self.type, self.proto, _canonname, sockaddr = res
|
||||
|
||||
@@ -123,7 +123,7 @@ class remote(sock):
|
||||
raise
|
||||
except socket.error:
|
||||
pass
|
||||
self.error("Could not connect to %s on port %d", self.rhost, self.rport)
|
||||
self.error("Could not connect to %s on port %s", self.rhost, self.rport)
|
||||
|
||||
@classmethod
|
||||
def fromsocket(cls, socket):
|
||||
|
||||
@@ -72,8 +72,9 @@ class sock(tube):
|
||||
raise
|
||||
|
||||
def settimeout_raw(self, timeout):
|
||||
if getattr(self, 'sock', None):
|
||||
self.sock.settimeout(timeout)
|
||||
sock = getattr(self, 'sock', None)
|
||||
if sock:
|
||||
sock.settimeout(timeout)
|
||||
|
||||
def can_recv_raw(self, timeout):
|
||||
"""
|
||||
@@ -161,19 +162,20 @@ class sock(tube):
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
if not getattr(self, 'sock', None):
|
||||
sock = getattr(self, 'sock', None)
|
||||
if not sock:
|
||||
return
|
||||
|
||||
# Mark as closed in both directions
|
||||
self.closed['send'] = True
|
||||
self.closed['recv'] = True
|
||||
|
||||
self.sock.close()
|
||||
sock.close()
|
||||
self.sock = None
|
||||
self._close_msg()
|
||||
|
||||
def _close_msg(self):
|
||||
self.info('Closed connection to %s port %d', self.rhost, self.rport)
|
||||
self.info('Closed connection to %s port %s', self.rhost, self.rport)
|
||||
|
||||
def fileno(self):
|
||||
if not self.sock:
|
||||
|
||||
+20
-9
@@ -519,13 +519,19 @@ class ssh_listener(sock):
|
||||
_ = self.sock
|
||||
return self
|
||||
|
||||
def __getattr__(self, key):
|
||||
if key == 'sock':
|
||||
while self._accepter.is_alive():
|
||||
self._accepter.join(timeout = 0.1)
|
||||
return self.sock
|
||||
else:
|
||||
return getattr(super(ssh_listener, self), key)
|
||||
@property
|
||||
def sock(self):
|
||||
try:
|
||||
return self.__dict__['sock']
|
||||
except KeyError:
|
||||
pass
|
||||
while self._accepter.is_alive():
|
||||
self._accepter.join(timeout=0.1)
|
||||
return self.__dict__.get('sock')
|
||||
|
||||
@sock.setter
|
||||
def sock(self, s):
|
||||
self.__dict__['sock'] = s
|
||||
|
||||
|
||||
class ssh(Timeout, Logger):
|
||||
@@ -552,8 +558,8 @@ class ssh(Timeout, Logger):
|
||||
_cwd = '.'
|
||||
|
||||
def __init__(self, user=None, host=None, port=22, password=None, key=None,
|
||||
keyfile=None, proxy_command=None, proxy_sock=None,
|
||||
level=None, cache=True, ssh_agent=False, ignore_config=False, *a, **kw):
|
||||
keyfile=None, proxy_command=None, proxy_sock=None, level=None,
|
||||
cache=True, ssh_agent=False, ignore_config=False, raw=False, *a, **kw):
|
||||
"""Creates a new ssh connection.
|
||||
|
||||
Arguments:
|
||||
@@ -570,6 +576,7 @@ class ssh(Timeout, Logger):
|
||||
cache: Cache downloaded files (by hash/size/timestamp)
|
||||
ssh_agent: If :const:`True`, enable usage of keys via ssh-agent
|
||||
ignore_config: If :const:`True`, disable usage of ~/.ssh/config and ~/.ssh/authorized_keys
|
||||
raw: If :const:`True`, assume a non-standard shell and don't probe the environment
|
||||
|
||||
NOTE: The proxy_command and proxy_sock arguments is only available if a
|
||||
fairly new version of paramiko is used.
|
||||
@@ -600,6 +607,7 @@ class ssh(Timeout, Logger):
|
||||
self.keyfile = keyfile
|
||||
self._cachedir = os.path.join(tempfile.gettempdir(), 'pwntools-ssh-cache')
|
||||
self.cache = cache
|
||||
self.raw = raw
|
||||
|
||||
# Deferred attributes
|
||||
self._platform_info = {}
|
||||
@@ -667,6 +675,9 @@ class ssh(Timeout, Logger):
|
||||
|
||||
h.success()
|
||||
|
||||
if self.raw:
|
||||
return
|
||||
|
||||
self._tried_sftp = False
|
||||
|
||||
if self.sftp:
|
||||
|
||||
+20
-19
@@ -19,7 +19,6 @@ from pwnlib.context import context
|
||||
from pwnlib.log import Logger
|
||||
from pwnlib.timeout import Timeout
|
||||
from pwnlib.tubes.buffer import Buffer
|
||||
from pwnlib.util import fiddling
|
||||
from pwnlib.util import misc
|
||||
from pwnlib.util import packing
|
||||
|
||||
@@ -155,15 +154,7 @@ class tube(Timeout, Logger):
|
||||
|
||||
if data and self.isEnabledFor(logging.DEBUG):
|
||||
self.debug('Received %#x bytes:' % len(data))
|
||||
|
||||
if len(set(data)) == 1 and len(data) > 1:
|
||||
self.indented('%r * %#x' % (data[0], len(data)), level = logging.DEBUG)
|
||||
elif all(c in string.printable.encode() for c in data):
|
||||
for line in data.splitlines(True):
|
||||
self.indented(repr(line), level = logging.DEBUG)
|
||||
else:
|
||||
self.indented(fiddling.hexdump(data), level = logging.DEBUG)
|
||||
|
||||
self.maybe_hexdump(data, level=logging.DEBUG)
|
||||
if data:
|
||||
self.buffer.add(data)
|
||||
|
||||
@@ -204,14 +195,29 @@ class tube(Timeout, Logger):
|
||||
Returns:
|
||||
A bytes object containing bytes received from the socket,
|
||||
or ``''`` if a timeout occurred while waiting.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> t = tube()
|
||||
>>> t.recv_raw = lambda n: b'abbbaccc'
|
||||
>>> pred = lambda p: p.count(b'a') == 2
|
||||
>>> t.recvpred(pred)
|
||||
b'abbba'
|
||||
>>> pred = lambda p: p.count(b'd') > 0
|
||||
>>> t.recvpred(pred, timeout=0.05)
|
||||
b''
|
||||
"""
|
||||
|
||||
data = b''
|
||||
|
||||
with self.countdown(timeout):
|
||||
while not pred(data):
|
||||
if not self.countdown_active():
|
||||
self.unrecv(data)
|
||||
return b''
|
||||
|
||||
try:
|
||||
res = self.recv(1)
|
||||
res = self.recv(1, timeout=timeout)
|
||||
except Exception:
|
||||
self.unrecv(data)
|
||||
return b''
|
||||
@@ -225,7 +231,7 @@ class tube(Timeout, Logger):
|
||||
return data
|
||||
|
||||
def recvn(self, numb, timeout = default):
|
||||
"""recvn(numb, timeout = default) -> str
|
||||
"""recvn(numb, timeout = default) -> bytes
|
||||
|
||||
Receives exactly `n` bytes.
|
||||
|
||||
@@ -767,13 +773,8 @@ class tube(Timeout, Logger):
|
||||
|
||||
if self.isEnabledFor(logging.DEBUG):
|
||||
self.debug('Sent %#x bytes:' % len(data))
|
||||
if len(set(data)) == 1:
|
||||
self.indented('%r * %#x' % (data[0], len(data)))
|
||||
elif all(c in string.printable.encode() for c in data):
|
||||
for line in data.splitlines(True):
|
||||
self.indented(repr(line), level = logging.DEBUG)
|
||||
else:
|
||||
self.indented(fiddling.hexdump(data), level = logging.DEBUG)
|
||||
self.maybe_hexdump(data, level=logging.DEBUG)
|
||||
|
||||
self.send_raw(data)
|
||||
|
||||
def sendline(self, line=b''):
|
||||
|
||||
+114
-1
@@ -13,9 +13,11 @@ import string
|
||||
from six import BytesIO
|
||||
from six.moves import range
|
||||
|
||||
from pwnlib.context import LocalNoarchContext
|
||||
from pwnlib.context import context
|
||||
from pwnlib.log import getLogger
|
||||
from pwnlib.term import text
|
||||
from pwnlib.util import iters
|
||||
from pwnlib.util import lists
|
||||
from pwnlib.util import packing
|
||||
from pwnlib.util.cyclic import cyclic
|
||||
@@ -765,7 +767,7 @@ def hexdump(s, width=16, skip=True, hexii=False, begin=0, style=None,
|
||||
Return a hexdump-dump of a string.
|
||||
|
||||
Arguments:
|
||||
s(str): The data to hexdump.
|
||||
s(bytes): The data to hexdump.
|
||||
width(int): The number of characters per line
|
||||
groupsize(int): The number of characters per group
|
||||
skip(bool): Set to True, if repeated lines should be replaced by a "*"
|
||||
@@ -971,3 +973,114 @@ def bnot(value, width=None):
|
||||
width = context.bits
|
||||
mask = ((1<<width)-1)
|
||||
return mask ^ value
|
||||
|
||||
@LocalNoarchContext
|
||||
def js_escape(data, padding=context.cyclic_alphabet[0:1], **kwargs):
|
||||
r"""js_escape(data, padding=context.cyclic_alphabet[0:1], endian = None, **kwargs) -> str
|
||||
|
||||
Pack data as an escaped Unicode string for use in JavaScript's `unescape()` function
|
||||
|
||||
Arguments:
|
||||
data (bytes): Bytes to pack
|
||||
padding (bytes): A single byte to use as padding if data is of uneven length
|
||||
endian (str): Endianness with which to pack the string ("little"/"big")
|
||||
|
||||
Returns:
|
||||
A string representation of the packed data
|
||||
|
||||
>>> js_escape(b'\xde\xad\xbe\xef')
|
||||
'%uadde%uefbe'
|
||||
|
||||
>>> js_escape(b'\xde\xad\xbe\xef', endian='big')
|
||||
'%udead%ubeef'
|
||||
|
||||
>>> js_escape(b'\xde\xad\xbe')
|
||||
'%uadde%u61be'
|
||||
|
||||
>>> js_escape(b'aaaa')
|
||||
'%u6161%u6161'
|
||||
"""
|
||||
data = packing._need_bytes(data)
|
||||
|
||||
padding = packing._need_bytes(padding)
|
||||
if len(padding) != 1:
|
||||
raise ValueError("Padding must be a single byte")
|
||||
|
||||
if len(data) % 2:
|
||||
data += padding[0:1]
|
||||
|
||||
data = bytearray(data)
|
||||
|
||||
if context.endian == 'little':
|
||||
return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for b, a in iters.group(2, data))
|
||||
else:
|
||||
return ''.join('%u{a:02x}{b:02x}'.format(a=a, b=b) for a, b in iters.group(2, data))
|
||||
|
||||
@LocalNoarchContext
|
||||
def js_unescape(s, **kwargs):
|
||||
r"""js_unescape(s, endian = None, **kwargs) -> bytes
|
||||
|
||||
Unpack an escaped Unicode string from JavaScript's `escape()` function
|
||||
|
||||
Arguments:
|
||||
s (str): Escaped string to unpack
|
||||
endian (str): Endianness with which to unpack the string ("little"/"big")
|
||||
|
||||
Returns:
|
||||
A bytes representation of the unpacked data
|
||||
|
||||
>>> js_unescape('%uadde%uefbe')
|
||||
b'\xde\xad\xbe\xef'
|
||||
|
||||
>>> js_unescape('%udead%ubeef', endian='big')
|
||||
b'\xde\xad\xbe\xef'
|
||||
|
||||
>>> js_unescape('abc%u4141123')
|
||||
b'a\x00b\x00c\x00AA1\x002\x003\x00'
|
||||
|
||||
>>> data = b'abcdABCD1234!@#$\x00\x01\x02\x03\x80\x81\x82\x83'
|
||||
>>> js_unescape(js_escape(data)) == data
|
||||
True
|
||||
|
||||
>>> js_unescape('%u4141%u42')
|
||||
Traceback (most recent call last):
|
||||
ValueError: Incomplete Unicode token: %u42
|
||||
|
||||
>>> js_unescape('%u4141%uwoot%4141')
|
||||
Traceback (most recent call last):
|
||||
ValueError: Failed to decode token: %uwoot
|
||||
|
||||
>>> js_unescape('%u4141%E4%F6%FC%u4141')
|
||||
Traceback (most recent call last):
|
||||
NotImplementedError: Non-Unicode % tokens are not supported: %E4
|
||||
|
||||
>>> js_unescape('%u4141%zz%u4141')
|
||||
Traceback (most recent call last):
|
||||
ValueError: Bad % token: %zz
|
||||
"""
|
||||
s = packing._decode(s)
|
||||
res = []
|
||||
p = 0
|
||||
while p < len(s):
|
||||
if s[p] == '%':
|
||||
if s[p+1] == "u":
|
||||
# Decode Unicode token e.g. %u4142
|
||||
n = s[p+2:p+6]
|
||||
if len(n) < 4:
|
||||
raise ValueError('Incomplete Unicode token: %s' % s[p:])
|
||||
try:
|
||||
n = int(n, 16)
|
||||
except ValueError:
|
||||
raise ValueError('Failed to decode token: %s' % s[p:p+6])
|
||||
res.append(packing.p16(n))
|
||||
p += 6
|
||||
elif s[p+1] in string.hexdigits and s[p+2] in string.hexdigits:
|
||||
# Decode Non-Unicode token e.g. %E4
|
||||
raise NotImplementedError('Non-Unicode %% tokens are not supported: %s' % s[p:p+3])
|
||||
else:
|
||||
raise ValueError('Bad %% token: %s' % s[p:p+3])
|
||||
else:
|
||||
res.append(packing.p16(ord(s[p])))
|
||||
p += 1
|
||||
|
||||
return b''.join(res)
|
||||
+58
-3
@@ -258,6 +258,8 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr
|
||||
- If ``$TERM_PROGRAM`` is set, that is used.
|
||||
- If X11 is detected (by the presence of the ``$DISPLAY`` environment
|
||||
variable), ``x-terminal-emulator`` is used.
|
||||
- If KDE Konsole is detected (by the presence of the ``$KONSOLE_VERSION``
|
||||
environment variable), a terminal will be split.
|
||||
- If WSL (Windows Subsystem for Linux) is detected (by the presence of
|
||||
a ``wsl.exe`` binary in the ``$PATH`` and ``/proc/sys/kernel/osrelease``
|
||||
containing ``Microsoft``), a new ``cmd.exe`` window will be opened.
|
||||
@@ -297,14 +299,60 @@ def run_in_new_terminal(command, terminal=None, args=None, kill_at_exit=True, pr
|
||||
elif 'DISPLAY' in os.environ and which('x-terminal-emulator'):
|
||||
terminal = 'x-terminal-emulator'
|
||||
args = ['-e']
|
||||
elif 'KONSOLE_VERSION' in os.environ and which('qdbus'):
|
||||
qdbus = which('qdbus')
|
||||
window_id = os.environ['WINDOWID']
|
||||
konsole_dbus_service = os.environ['KONSOLE_DBUS_SERVICE']
|
||||
|
||||
with subprocess.Popen((qdbus, konsole_dbus_service), stdout=subprocess.PIPE) as proc:
|
||||
lines = proc.communicate()[0].decode().split('\n')
|
||||
|
||||
# Iterate over all MainWindows
|
||||
for line in lines:
|
||||
parts = line.split('/')
|
||||
if len(parts) == 3 and parts[2].startswith('MainWindow_'):
|
||||
name = parts[2]
|
||||
with subprocess.Popen((qdbus, konsole_dbus_service, '/konsole/' + name,
|
||||
'org.kde.KMainWindow.winId'), stdout=subprocess.PIPE) as proc:
|
||||
target_window_id = proc.communicate()[0].decode().strip()
|
||||
if target_window_id == window_id:
|
||||
break
|
||||
else:
|
||||
log.error('MainWindow not found')
|
||||
|
||||
# Split
|
||||
subprocess.run((qdbus, konsole_dbus_service, '/konsole/' + name,
|
||||
'org.kde.KMainWindow.activateAction', 'split-view-left-right'), stdout=subprocess.DEVNULL)
|
||||
|
||||
# Find new session
|
||||
with subprocess.Popen((qdbus, konsole_dbus_service, os.environ['KONSOLE_DBUS_WINDOW'],
|
||||
'org.kde.konsole.Window.sessionList'), stdout=subprocess.PIPE) as proc:
|
||||
session_list = map(int, proc.communicate()[0].decode().split())
|
||||
last_konsole_session = max(session_list)
|
||||
|
||||
terminal = 'qdbus'
|
||||
args = [konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
|
||||
'org.kde.konsole.Session.runCommand']
|
||||
|
||||
else:
|
||||
is_wsl = False
|
||||
if os.path.exists('/proc/sys/kernel/osrelease'):
|
||||
with open('/proc/sys/kernel/osrelease', 'rb') as f:
|
||||
is_wsl = b'icrosoft' in f.read()
|
||||
if is_wsl and which('cmd.exe') and which('wsl.exe') and which('bash.exe'):
|
||||
terminal = 'cmd.exe'
|
||||
args = ['/c', 'start', 'bash.exe', '-c']
|
||||
terminal = 'cmd.exe'
|
||||
args = ['/c', 'start']
|
||||
distro_name = os.getenv('WSL_DISTRO_NAME')
|
||||
|
||||
# Split pane in Windows Terminal
|
||||
if 'WT_SESSION' in os.environ and which('wt.exe'):
|
||||
args.extend(['wt.exe', '-w', '0', 'split-pane', '-d', '.'])
|
||||
|
||||
if distro_name:
|
||||
args.extend(['wsl.exe', '-d', distro_name, 'bash', '-c'])
|
||||
else:
|
||||
args.extend(['bash.exe', '-c'])
|
||||
|
||||
|
||||
if not terminal:
|
||||
log.error('Could not find a terminal binary to use. Set context.terminal to your terminal.')
|
||||
@@ -359,13 +407,20 @@ os.execve({argv0!r}, {argv!r}, os.environ)
|
||||
if terminal == 'tmux':
|
||||
out, _ = p.communicate()
|
||||
pid = int(out)
|
||||
elif terminal == 'qdbus':
|
||||
with subprocess.Popen((qdbus, konsole_dbus_service, '/Sessions/{}'.format(last_konsole_session),
|
||||
'org.kde.konsole.Session.processId'), stdout=subprocess.PIPE) as proc:
|
||||
pid = int(proc.communicate()[0].decode())
|
||||
else:
|
||||
pid = p.pid
|
||||
|
||||
if kill_at_exit:
|
||||
def kill():
|
||||
try:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
if terminal == 'qdbus':
|
||||
os.kill(pid, signal.SIGHUP)
|
||||
else:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ _const_codes = [
|
||||
'BUILD_CONST_KEY_MAP', 'BUILD_STRING',
|
||||
'LOAD_CONST','RETURN_VALUE','STORE_SUBSCR', 'STORE_MAP',
|
||||
'LIST_TO_TUPLE', 'LIST_EXTEND', 'SET_UPDATE', 'DICT_UPDATE', 'DICT_MERGE',
|
||||
'COPY', 'RESUME',
|
||||
]
|
||||
|
||||
_expr_codes = _const_codes + [
|
||||
@@ -15,6 +16,7 @@ _expr_codes = _const_codes + [
|
||||
'BINARY_MODULO','BINARY_ADD','BINARY_SUBTRACT',
|
||||
'BINARY_LSHIFT','BINARY_RSHIFT','BINARY_AND','BINARY_XOR',
|
||||
'BINARY_OR',
|
||||
'BINARY_OP',
|
||||
]
|
||||
|
||||
_values_codes = _expr_codes + ['LOAD_NAME']
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = '4.8.0'
|
||||
__version__ = '4.9.0'
|
||||
Reference in new issue
Block a user