mirror of
https://github.com/Gallopsled/pwntools.git
synced 2026-11-10 17:12:49 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c45e92d78b | ||
|
|
a76c7b0ca1 | ||
|
|
b8fad78d3e | ||
|
|
8f42bef2b1 | ||
|
|
020143f0f9 | ||
|
|
416c33ed00 | ||
|
|
3884d606b7 | ||
|
|
4628627943 | ||
|
|
d86ae5794a | ||
|
|
67d49a693d | ||
|
|
a7cb54931c | ||
|
|
e450f6c247 | ||
|
|
493a3e3d92 | ||
|
|
3e4d4a2115 | ||
|
|
b3b6d52bdd | ||
|
|
6ba46d4eec | ||
|
|
5af2d92100 | ||
|
|
3fadc67327 | ||
|
|
91c1b20c7b | ||
|
|
0351219903 | ||
|
|
4b067d2ec5 | ||
|
|
d9f9e7ca6f | ||
|
|
9e55d8f4c8 | ||
|
|
97a760a0dd | ||
|
|
f491bc6a22 | ||
|
|
cde1b89615 | ||
|
|
17fc0aeaad | ||
|
|
c93bdfeed8 | ||
|
|
4fd08b0b00 | ||
|
|
8dd566b9e9 | ||
|
|
fca2929de0 | ||
|
|
d67bdbc550 | ||
|
|
c3894c24de | ||
|
|
208b11df69 | ||
|
|
0a860bd964 | ||
|
|
15efc3f1dc | ||
|
|
2d0f03b66c | ||
|
|
d7e9d313a2 | ||
|
|
7a246e7887 | ||
|
|
c04dbdb9e9 | ||
|
|
fc372d17a2 | ||
|
|
79986d8262 | ||
|
|
ef698d4562 | ||
|
|
1238fc359e | ||
|
|
79d150061c | ||
|
|
2198b74df9 | ||
|
|
bf37947a99 | ||
|
|
1fe9b3e693 | ||
|
|
762591bf35 | ||
|
|
04f401c70e | ||
|
|
84b51fc1a5 | ||
|
|
1573957cc8 | ||
|
|
038e50fa0c | ||
|
|
58d6aea217 | ||
|
|
4c78e188ce | ||
|
|
9d8060496c | ||
|
|
7ea1f1d492 | ||
|
|
da8bf2667d | ||
|
|
a37bce2bdc | ||
|
|
3dd0b24cc3 | ||
|
|
a8c8c28f00 | ||
|
|
315a462603 | ||
|
|
ddeddd455e | ||
|
|
d3140ed983 | ||
|
|
fc250a33a1 | ||
|
|
92911cbabd | ||
|
|
a9715d8c74 | ||
|
|
bd12d1874f | ||
|
|
11b8537d17 | ||
|
|
f7283bf2eb | ||
|
|
d5d7b8385c |
No files matched your search
@@ -3,7 +3,7 @@ name: Android Tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
android-test:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: [3.8]
|
||||
|
||||
@@ -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,8 +59,9 @@ jobs:
|
||||
binutils-s390x-linux-gnu \
|
||||
binutils-sparc64-linux-gnu \
|
||||
gcc-multilib \
|
||||
libc6-dbg
|
||||
|
||||
libc6-dbg \
|
||||
elfutils
|
||||
|
||||
- name: Install RPyC for GDB
|
||||
run: |
|
||||
sudo apt-get install -y python3-pip
|
||||
@@ -81,6 +82,7 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install --upgrade wheel
|
||||
pip install --upgrade flake8 appdirs
|
||||
python setup.py egg_info
|
||||
pip install --upgrade --editable .
|
||||
@@ -89,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
|
||||
@@ -156,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 +25,7 @@ addons:
|
||||
- binutils-sparc64-linux-gnu
|
||||
- bash-static
|
||||
- python3-pip
|
||||
- elfutils
|
||||
cache:
|
||||
- pip
|
||||
- directories:
|
||||
|
||||
+64
-6
@@ -9,9 +9,12 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
|
||||
| Version | Branch | Release Date |
|
||||
| ---------------- | -------- | ---------------------- |
|
||||
| [4.9.0](#490) | `dev` |
|
||||
| [4.8.0](#480) | `beta` |
|
||||
| [4.7.0](#470) | `stable` | Nov 15, 2021
|
||||
| [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
|
||||
| [4.5.1](#451) | | May 30, 2021
|
||||
| [4.5.0](#450) | | Apr 30, 2021
|
||||
@@ -62,11 +65,49 @@ 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.9.0 (`dev`)
|
||||
## 4.11.0 (`dev`)
|
||||
|
||||
|
||||
|
||||
## 4.8.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`
|
||||
- [#2011][2011] Fix tube's debug output of same byte compression
|
||||
- [#2023][2023] Support KDE Konsole in run_in_new_terminal function
|
||||
- [#2027][2027] Fix ELF.libc_start_main_return with glibc 2.34
|
||||
- [#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
|
||||
[2011]: https://github.com/Gallopsled/pwntools/pull/2011
|
||||
[2023]: https://github.com/Gallopsled/pwntools/pull/2023
|
||||
[2027]: https://github.com/Gallopsled/pwntools/pull/2027
|
||||
[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
|
||||
|
||||
- [#1922][1922] Fix logic in `wait_for_debugger`
|
||||
- [#1828][1828] libcdb: Load debug info and unstrip libc binary
|
||||
@@ -78,7 +119,24 @@ The table below shows which release corresponds to each branch, and what date th
|
||||
[1939]: https://github.com/Gallopsled/pwntools/pull/1939
|
||||
[1981]: https://github.com/Gallopsled/pwntools/pull/1981
|
||||
|
||||
## 4.7.0 (`stable`)
|
||||
## 4.7.1
|
||||
|
||||
- [#1784][1784] Use temporary cache directory when persistent cache cannot be used
|
||||
- [#1973][1973] ELF symbols can be looked up by bytes values
|
||||
- several bugfixes ([#2012][2012], [#2031][2031], [#1912][1912], [#1961][1961],
|
||||
[#2007][2007], [#2040][2040], [#2051][2051])
|
||||
|
||||
[1784]: https://github.com/Gallopsled/pwntools/pull/1784
|
||||
[1912]: https://github.com/Gallopsled/pwntools/pull/1912
|
||||
[1961]: https://github.com/Gallopsled/pwntools/pull/1961
|
||||
[1973]: https://github.com/Gallopsled/pwntools/pull/1973
|
||||
[2007]: https://github.com/Gallopsled/pwntools/pull/2007
|
||||
[2012]: https://github.com/Gallopsled/pwntools/pull/2012
|
||||
[2031]: https://github.com/Gallopsled/pwntools/pull/2031
|
||||
[2040]: https://github.com/Gallopsled/pwntools/pull/2040
|
||||
[2051]: https://github.com/Gallopsled/pwntools/pull/2051
|
||||
|
||||
## 4.7.0
|
||||
|
||||
- [#1733][1733] Update libc headers -> more syscalls available!
|
||||
- [#1876][1876] add `self.message` and change `sys.exc_type` to `sys.exec_info()` in PwnlibException
|
||||
|
||||
@@ -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,7 +12,7 @@ pypandoc
|
||||
pyserial>=2.7
|
||||
requests>=2.5.1
|
||||
ropgadget>=5.3
|
||||
sphinx<3.4
|
||||
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
|
||||
|
||||
@@ -16,6 +16,7 @@ RUN apt-get update \
|
||||
&& locale-gen en_US.UTF-8 \
|
||||
&& apt-get install -y \
|
||||
build-essential \
|
||||
elfutils \
|
||||
git \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
|
||||
+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)
|
||||
|
||||
+48
-28
@@ -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
|
||||
@@ -779,6 +794,11 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
|
||||
0: 60 00 00 00 nop
|
||||
>>> print(disasm(unhex('00000000'), arch='mips64'))
|
||||
0: 00000000 nop
|
||||
>>> print(disasm(unhex('48b84141414141414100c3'), arch='amd64'))
|
||||
0: 48 b8 41 41 41 41 41 41 41 00 movabs rax, 0x41414141414141
|
||||
a: c3 ret
|
||||
>>> print(disasm(unhex('00000000'), vma=0x80000000, arch='mips'))
|
||||
80000000: 00000000 nop
|
||||
"""
|
||||
result = ''
|
||||
|
||||
@@ -790,7 +810,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
|
||||
|
||||
bfdarch = _bfdarch()
|
||||
bfdname = _bfdname()
|
||||
objdump = _objdump() + ['-d', '--adjust-vma', str(vma), '-b', bfdname]
|
||||
objdump = _objdump() + ['-w', '-d', '--adjust-vma', str(vma), '-b', bfdname]
|
||||
objcopy = _objcopy() + [
|
||||
'-I', 'binary',
|
||||
'-O', bfdname,
|
||||
@@ -815,7 +835,7 @@ def disasm(data, vma = 0, byte = True, offset = True, instructions = True):
|
||||
_run(objcopy + [step1, step2])
|
||||
|
||||
output0 = _run(objdump + [step2])
|
||||
output1 = output0.split('<.text>:\n')
|
||||
output1 = re.split(r'<\.text(?:\+0x0)?>:\n', output0, flags=re.MULTILINE)
|
||||
|
||||
if len(output1) != 2:
|
||||
log.error('Could not find .text in objdump output:\n%s' % output0)
|
||||
|
||||
@@ -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,
|
||||
|
||||
+3
-3
@@ -55,8 +55,8 @@ def initialize():
|
||||
from pwnlib.log import getLogger
|
||||
log = getLogger(__name__)
|
||||
|
||||
xdg_config_home = (os.environ.get('XDG_CONFIG_HOME') or
|
||||
os.path.expanduser("~/.config"))
|
||||
xdg_config_home = os.environ.get('XDG_CONFIG_HOME',
|
||||
os.path.expanduser("~/.config"))
|
||||
|
||||
c = configparser.ConfigParser()
|
||||
c.read(['/etc/pwn.conf',
|
||||
@@ -65,7 +65,7 @@ def initialize():
|
||||
|
||||
for section in c.sections():
|
||||
if section not in registered_configs:
|
||||
log.warn("Unknown configuration section %r" % section)
|
||||
log.warn("Unknown configuration section %r", section)
|
||||
continue
|
||||
settings = dict(c.items(section))
|
||||
registered_configs[section](settings)
|
||||
+73
-20
@@ -6,17 +6,20 @@ contexts work properly and as expected.
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
import atexit
|
||||
import collections
|
||||
import errno
|
||||
import functools
|
||||
import logging
|
||||
import os
|
||||
import os.path
|
||||
import platform
|
||||
import shutil
|
||||
import six
|
||||
import socket
|
||||
import stat
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import threading
|
||||
import time
|
||||
|
||||
@@ -345,6 +348,10 @@ class ContextType(object):
|
||||
'binary': None,
|
||||
'bits': 32,
|
||||
'buffer_size': 4096,
|
||||
'cache_dir_base': os.environ.get(
|
||||
'XDG_CACHE_HOME',
|
||||
os.path.join(os.path.expanduser('~'), '.cache')
|
||||
),
|
||||
'cyclic_alphabet': string.ascii_lowercase.encode(),
|
||||
'cyclic_size': 4,
|
||||
'delete_corefiles': False,
|
||||
@@ -762,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'),
|
||||
@@ -972,7 +980,10 @@ class ContextType(object):
|
||||
except AttributeError: pass
|
||||
|
||||
# Otherwise, fail
|
||||
level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
|
||||
try:
|
||||
level_names = logging._levelToName.values()
|
||||
except AttributeError:
|
||||
level_names = filter(lambda x: isinstance(x,str), logging._levelNames)
|
||||
permitted = sorted(level_names)
|
||||
raise AttributeError('log_level must be an integer or one of %r' % permitted)
|
||||
|
||||
@@ -1269,6 +1280,21 @@ class ContextType(object):
|
||||
"""
|
||||
return int(size)
|
||||
|
||||
@_validator
|
||||
def cache_dir_base(self, new_base):
|
||||
"""Base directory to use for caching content.
|
||||
|
||||
Changing this to a different value will clear the `cache_dir` path
|
||||
stored in TLS since a new path will need to be generated to respect the
|
||||
new `cache_dir_base` value.
|
||||
"""
|
||||
|
||||
if new_base != self.cache_dir_base:
|
||||
del self._tls["cache_dir"]
|
||||
if os.access(new_base, os.F_OK) and not os.access(new_base, os.W_OK):
|
||||
raise OSError(errno.EPERM, "Cache base dir is not writable")
|
||||
return new_base
|
||||
|
||||
@property
|
||||
def cache_dir(self):
|
||||
"""Directory used for caching data.
|
||||
@@ -1282,32 +1308,59 @@ class ContextType(object):
|
||||
>>> cache_dir is not None
|
||||
True
|
||||
>>> os.chmod(cache_dir, 0o000)
|
||||
>>> del context._tls['cache_dir']
|
||||
>>> context.cache_dir is None
|
||||
True
|
||||
>>> os.chmod(cache_dir, 0o755)
|
||||
>>> cache_dir == context.cache_dir
|
||||
True
|
||||
"""
|
||||
xdg_cache_home = os.environ.get('XDG_CACHE_HOME') or \
|
||||
os.path.join(os.path.expanduser('~'), '.cache')
|
||||
try:
|
||||
# If the TLS already has a cache directory path, we return it
|
||||
# without any futher checks since it must have been valid when it
|
||||
# was set and if that has changed, hiding the TOCTOU here would be
|
||||
# potentially confusing
|
||||
return self._tls["cache_dir"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not os.access(xdg_cache_home, os.W_OK):
|
||||
# Attempt to create a Python version specific cache dir and its parents
|
||||
cache_dirname = '.pwntools-cache-%d.%d' % sys.version_info[:2]
|
||||
cache_dirpath = os.path.join(self.cache_dir_base, cache_dirname)
|
||||
try:
|
||||
os.makedirs(cache_dirpath)
|
||||
except OSError as exc:
|
||||
# If we failed for any reason other than the cache directory
|
||||
# already existing then we'll fall back to a temporary directory
|
||||
# object which doesn't respect the `cache_dir_base`
|
||||
if exc.errno != errno.EEXIST:
|
||||
try:
|
||||
cache_dirpath = tempfile.mkdtemp(prefix=".pwntools-tmp")
|
||||
except IOError:
|
||||
# This implies no good candidates for temporary files so we
|
||||
# have to return `None`
|
||||
return None
|
||||
else:
|
||||
# Ensure the temporary cache dir is cleaned up on exit. A
|
||||
# `TemporaryDirectory` would do this better upon garbage
|
||||
# collection but this is necessary for Python 2 support.
|
||||
atexit.register(shutil.rmtree, cache_dirpath)
|
||||
# By this time we have a cache directory which exists but we don't know
|
||||
# if it is actually writable. Some wargames e.g. pwnable.kr have
|
||||
# created dummy directories which cannot be modified by the user
|
||||
# account (owned by root).
|
||||
if os.access(cache_dirpath, os.W_OK):
|
||||
# Stash this in TLS for later reuse
|
||||
self._tls["cache_dir"] = cache_dirpath
|
||||
return cache_dirpath
|
||||
else:
|
||||
return None
|
||||
|
||||
cache = os.path.join(xdg_cache_home, '.pwntools-cache-%d.%d' % sys.version_info[:2])
|
||||
|
||||
if not os.path.exists(cache):
|
||||
try:
|
||||
os.mkdir(cache)
|
||||
except OSError:
|
||||
return None
|
||||
|
||||
# Some wargames e.g. pwnable.kr have created dummy directories
|
||||
# which cannot be modified by the user account (owned by root).
|
||||
if not os.access(cache, os.W_OK):
|
||||
return None
|
||||
|
||||
return cache
|
||||
@cache_dir.setter
|
||||
def cache_dir(self, v):
|
||||
if os.access(v, os.W_OK):
|
||||
# Stash this in TLS for later reuse
|
||||
self._tls["cache_dir"] = v
|
||||
|
||||
@_validator
|
||||
def delete_corefiles(self, v):
|
||||
|
||||
@@ -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:
|
||||
|
||||
+5
-6
@@ -15,7 +15,7 @@ Example
|
||||
# leaks at least one byte at that address.
|
||||
def leak(address):
|
||||
data = p.read(address, 4)
|
||||
log.debug("%#x => %s" % (address, enhex(data or '')))
|
||||
log.debug("%#x => %s", address, enhex(data or ''))
|
||||
return data
|
||||
|
||||
# For the sake of this example, let's say that we
|
||||
@@ -62,8 +62,7 @@ from pwnlib.elf import constants
|
||||
from pwnlib.log import getLogger
|
||||
from pwnlib.memleak import MemLeak
|
||||
from pwnlib.util.fiddling import enhex
|
||||
from pwnlib.util.packing import unpack, _need_bytes
|
||||
from pwnlib.util.web import wget
|
||||
from pwnlib.util.packing import _need_bytes
|
||||
|
||||
log = getLogger(__name__)
|
||||
sizeof = ctypes.sizeof
|
||||
@@ -562,7 +561,7 @@ class DynELF(object):
|
||||
else: dynlib = self
|
||||
|
||||
if dynlib is None:
|
||||
log.failure("Could not find %r" % lib)
|
||||
log.failure("Could not find %r", lib)
|
||||
return None
|
||||
|
||||
#
|
||||
@@ -573,7 +572,7 @@ class DynELF(object):
|
||||
self.status("Trying lookup based on Build ID")
|
||||
build_id = dynlib._lookup_build_id(lib=lib)
|
||||
if build_id:
|
||||
log.info("Trying lookup based on Build ID: %s" % build_id)
|
||||
log.info("Trying lookup based on Build ID: %s", build_id)
|
||||
path = libcdb.search_by_build_id(build_id)
|
||||
if path:
|
||||
with context.local(log_level='error'):
|
||||
@@ -615,7 +614,7 @@ class DynELF(object):
|
||||
addr = leak.field(cur, LinkMap.l_addr)
|
||||
cur = leak.field(cur, LinkMap.l_next)
|
||||
|
||||
log.debug('Found %r @ %#x' % (name, addr))
|
||||
log.debug('Found %r @ %#x', name, addr)
|
||||
|
||||
self._bases[name] = addr
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
+53
-17
@@ -157,6 +157,12 @@ class dotdict(dict):
|
||||
>>> x.bar.baz
|
||||
4
|
||||
"""
|
||||
def __missing__(self, name):
|
||||
if isinstance(name, (bytes, bytearray)):
|
||||
name = packing._decode(name)
|
||||
return self[name]
|
||||
raise KeyError(name)
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self:
|
||||
return self[name]
|
||||
@@ -167,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.
|
||||
@@ -1076,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
|
||||
@@ -1104,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
|
||||
@@ -1753,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):
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
|
||||
# Ported to Python by Gallopsled
|
||||
from __future__ import division
|
||||
|
||||
# +------------------------------------------------------------------------+
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
|
||||
# Ported to Python by Gallopsled
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
|
||||
# Ported to Python by Gallopsled
|
||||
from __future__ import division
|
||||
from __future__ import absolute_import
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
|
||||
# Ported to Python by Gallopsled
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) 2013 Pratik Kumar Sahu, Nagendra Chowdary, Anish Mathuria
|
||||
# Ported to Python by Gallopsled
|
||||
from __future__ import division
|
||||
|
||||
import os
|
||||
|
||||
+9
-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,24 +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:
|
||||
structure += pack(self.__getattr__(val), self.length[val]*8)
|
||||
if self.length[val] > 0:
|
||||
structure += pack(getattr(self, val), self.length[val]*8)
|
||||
return structure
|
||||
|
||||
def struntil(self,v):
|
||||
@@ -218,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]
|
||||
|
||||
+5
-5
@@ -303,7 +303,7 @@ def _gdbserver_args(pid=None, path=None, args=None, which=None, env=None):
|
||||
env_args.append(b'%s=%s' % (key, env.pop(key)))
|
||||
else:
|
||||
env_args.append(b'%s=%s' % (key, env[key]))
|
||||
gdbserver_args += ['--wrapper', 'env', '-i'] + env_args + ['--']
|
||||
gdbserver_args += ['--wrapper', which('env'), '-i'] + env_args + ['--']
|
||||
|
||||
gdbserver_args += ['localhost:0']
|
||||
gdbserver_args += args
|
||||
@@ -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
|
||||
@@ -915,7 +915,7 @@ def attach(target, gdbscript = '', exe = None, gdb_args = None, ssh = None, sysr
|
||||
cmd = ['sshpass', '-p', shell.password] + cmd
|
||||
if shell.keyfile:
|
||||
cmd += ['-i', shell.keyfile]
|
||||
cmd += ['gdb', '-q', target.executable, target.pid, '-x', tmpfile]
|
||||
cmd += ['gdb', '-q', target.executable, str(target.pid), '-x', tmpfile]
|
||||
|
||||
misc.run_in_new_terminal(cmd)
|
||||
return
|
||||
@@ -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)
|
||||
|
||||
+153
-29
@@ -12,9 +12,12 @@ import tempfile
|
||||
from pwnlib.context import context
|
||||
from pwnlib.elf import ELF
|
||||
from pwnlib.log import getLogger
|
||||
from pwnlib.tubes.process import process
|
||||
from pwnlib.util.fiddling import b64d
|
||||
from pwnlib.util.fiddling import enhex
|
||||
from pwnlib.util.fiddling import hexdump
|
||||
from pwnlib.util.misc import read
|
||||
from pwnlib.util.misc import which
|
||||
from pwnlib.util.misc import write
|
||||
from pwnlib.util.safeeval import const
|
||||
from pwnlib.util.web import wget
|
||||
@@ -22,6 +25,7 @@ from pwnlib.util.web import wget
|
||||
log = getLogger(__name__)
|
||||
|
||||
HASHES = ['build_id', 'sha1', 'sha256', 'md5']
|
||||
DEBUGINFOD_SERVERS = ['https://debuginfod.systemtap.org/']
|
||||
|
||||
# https://gitlab.com/libcdb/libcdb wasn't updated after 2019,
|
||||
# but still is a massive database of older libc binaries.
|
||||
@@ -88,29 +92,13 @@ def provider_libc_rip(hex_encoded_id, hash_type):
|
||||
|
||||
PROVIDERS = [provider_libcdb, provider_libc_rip]
|
||||
|
||||
def search_by_hash(hex_encoded_id, hash_type='build_id'):
|
||||
def search_by_hash(hex_encoded_id, hash_type='build_id', unstrip=True):
|
||||
assert hash_type in HASHES, hash_type
|
||||
|
||||
# Ensure that the libcdb cache directory exists
|
||||
cache_dir = os.path.join(context.cache_dir, 'libcdb', hash_type)
|
||||
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
# If we already downloaded the file, and it looks even passingly like
|
||||
# a valid ELF file, return it.
|
||||
cache = os.path.join(cache_dir, hex_encoded_id)
|
||||
|
||||
if os.path.exists(cache):
|
||||
log.debug("Found existing cached libc at %r", cache)
|
||||
|
||||
data = read(cache)
|
||||
if data.startswith(b'\x7FELF'):
|
||||
log.info_once("Using cached data from %r", cache)
|
||||
return cache
|
||||
else:
|
||||
log.info_once("Skipping unavailable libc %s", hex_encoded_id)
|
||||
return None
|
||||
cache, cache_valid = _check_elf_cache('libcdb', hex_encoded_id, hash_type)
|
||||
if cache_valid:
|
||||
return cache
|
||||
|
||||
# Run through all available libc database providers to see if we have a match.
|
||||
for provider in PROVIDERS:
|
||||
@@ -128,16 +116,146 @@ def search_by_hash(hex_encoded_id, hash_type='build_id'):
|
||||
if not data or not data.startswith(b'\x7FELF'):
|
||||
return None
|
||||
|
||||
# Try to find debug info for this libc.
|
||||
if unstrip:
|
||||
unstrip_libc(cache)
|
||||
|
||||
return cache
|
||||
|
||||
def _search_debuginfo_by_hash(base_url, hex_encoded_id):
|
||||
# Deferred import because it's slow
|
||||
import requests
|
||||
from six.moves import urllib
|
||||
|
||||
def search_by_build_id(hex_encoded_id):
|
||||
# Check if we tried this buildid before.
|
||||
cache, cache_valid = _check_elf_cache('libcdb_dbg', hex_encoded_id, 'build_id')
|
||||
if cache_valid:
|
||||
return cache
|
||||
|
||||
# Try to find separate debuginfo.
|
||||
url = '/buildid/{}/debuginfo'.format(hex_encoded_id)
|
||||
url = urllib.parse.urljoin(base_url, url)
|
||||
data = b""
|
||||
log.debug("Downloading data from debuginfod: %s", url)
|
||||
try:
|
||||
data = wget(url, timeout=20)
|
||||
except requests.RequestException as e:
|
||||
log.warn_once("Failed to fetch libc debuginfo for build_id %s from %s: %s", hex_encoded_id, base_url, e)
|
||||
|
||||
# Save whatever we got to the cache
|
||||
write(cache, data or b'')
|
||||
|
||||
# Return ``None`` if we did not get a valid ELF file
|
||||
if not data or not data.startswith(b'\x7FELF'):
|
||||
log.warn_once("Could not fetch libc debuginfo for build_id %s from %s", hex_encoded_id, base_url)
|
||||
return None
|
||||
|
||||
return cache
|
||||
|
||||
def _check_elf_cache(cache_type, hex_encoded_id, hash_type):
|
||||
"""
|
||||
Check if there already is an ELF file for this hash in the cache.
|
||||
|
||||
>>> cache, _ = _check_elf_cache('libcdb', '2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', 'build_id')
|
||||
>>> os.unlink(cache) if os.path.exists(cache)
|
||||
>>> filename = search_by_hash('2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', 'build_id', unstrip=False)
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xe56c0'
|
||||
>>> filename == cache
|
||||
True
|
||||
"""
|
||||
# Ensure that the cache directory exists
|
||||
cache_dir = os.path.join(context.cache_dir, cache_type, hash_type)
|
||||
|
||||
if not os.path.isdir(cache_dir):
|
||||
os.makedirs(cache_dir)
|
||||
|
||||
# If we already downloaded the file, and it looks even passingly like
|
||||
# a valid ELF file, return it.
|
||||
cache = os.path.join(cache_dir, hex_encoded_id)
|
||||
|
||||
if not os.path.exists(cache):
|
||||
return cache, False
|
||||
|
||||
log.debug("Found existing cached ELF at %r", cache)
|
||||
|
||||
data = read(cache)
|
||||
if not data.startswith(b'\x7FELF'):
|
||||
log.info_once("Skipping unavailable ELF %s", hex_encoded_id)
|
||||
return cache, False
|
||||
|
||||
log.info_once("Using cached data from %r", cache)
|
||||
return cache, True
|
||||
|
||||
def unstrip_libc(filename):
|
||||
"""
|
||||
Given a path to a libc binary, attempt to download matching debug info
|
||||
and add them back to the given binary.
|
||||
|
||||
This modifies the given file.
|
||||
|
||||
Arguments:
|
||||
filename(str):
|
||||
Path to the libc binary to unstrip.
|
||||
|
||||
Returns:
|
||||
:const:`True` if binary was unstripped, :const:`False` otherwise.
|
||||
|
||||
Examples:
|
||||
>>> filename = search_by_build_id('2d1c5e0b85cb06ff47fa6fa088ec22cb6e06074e', unstrip=False)
|
||||
>>> libc = ELF(filename)
|
||||
>>> hex(libc.symbols.read)
|
||||
'0xe56c0'
|
||||
>>> 'main_arena' in libc.symbols
|
||||
False
|
||||
>>> unstrip_libc(filename)
|
||||
True
|
||||
>>> libc = ELF(filename)
|
||||
>>> hex(libc.symbols.main_arena)
|
||||
'0x1d57a0'
|
||||
>>> unstrip_libc(which('python'))
|
||||
False
|
||||
>>> filename = search_by_build_id('06a8004be6e10c4aeabbe0db74423ace392a2d6b', unstrip=True)
|
||||
>>> 'main_arena' in ELF(filename).symbols
|
||||
True
|
||||
"""
|
||||
if not which('eu-unstrip'):
|
||||
log.warn_once('Couldn\'t find "eu-unstrip" in PATH. Install elfutils first.')
|
||||
return False
|
||||
|
||||
libc = ELF(filename, checksec=False)
|
||||
if not libc.buildid:
|
||||
log.warn_once('Given libc does not have a buildid. Cannot look for debuginfo to unstrip.')
|
||||
return False
|
||||
|
||||
for server_url in DEBUGINFOD_SERVERS:
|
||||
libc_dbg = _search_debuginfo_by_hash(server_url, enhex(libc.buildid))
|
||||
if libc_dbg:
|
||||
break
|
||||
else:
|
||||
log.warn_once('Couldn\'t find debug info for libc with build_id %s on any debuginfod server.', enhex(libc.buildid))
|
||||
return False
|
||||
|
||||
# Add debug info to given libc binary inplace.
|
||||
p = process(['eu-unstrip', '-o', filename, filename, libc_dbg])
|
||||
output = p.recvall()
|
||||
p.close()
|
||||
|
||||
if output:
|
||||
log.error('Failed to unstrip libc binary: %r', output)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def search_by_build_id(hex_encoded_id, unstrip=True):
|
||||
"""
|
||||
Given a hex-encoded Build ID, attempt to download a matching libc from libcdb.
|
||||
|
||||
Arguments:
|
||||
hex_encoded_id(str):
|
||||
Hex-encoded Build ID (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
@@ -152,15 +270,17 @@ def search_by_build_id(hex_encoded_id):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'build_id')
|
||||
return search_by_hash(hex_encoded_id, 'build_id', unstrip)
|
||||
|
||||
def search_by_md5(hex_encoded_id):
|
||||
def search_by_md5(hex_encoded_id, unstrip=True):
|
||||
"""
|
||||
Given a hex-encoded md5sum, attempt to download a matching libc from libcdb.
|
||||
|
||||
Arguments:
|
||||
hex_encoded_id(str):
|
||||
Hex-encoded md5sum (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
@@ -175,15 +295,17 @@ def search_by_md5(hex_encoded_id):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'md5')
|
||||
return search_by_hash(hex_encoded_id, 'md5', unstrip)
|
||||
|
||||
def search_by_sha1(hex_encoded_id):
|
||||
def search_by_sha1(hex_encoded_id, unstrip=True):
|
||||
"""
|
||||
Given a hex-encoded sha1, attempt to download a matching libc from libcdb.
|
||||
|
||||
Arguments:
|
||||
hex_encoded_id(str):
|
||||
Hex-encoded sha1sum (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
@@ -198,16 +320,18 @@ def search_by_sha1(hex_encoded_id):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'sha1')
|
||||
return search_by_hash(hex_encoded_id, 'sha1', unstrip)
|
||||
|
||||
|
||||
def search_by_sha256(hex_encoded_id):
|
||||
def search_by_sha256(hex_encoded_id, unstrip=True):
|
||||
"""
|
||||
Given a hex-encoded sha256, attempt to download a matching libc from libcdb.
|
||||
|
||||
Arguments:
|
||||
hex_encoded_id(str):
|
||||
Hex-encoded sha256sum (e.g. 'ABCDEF...') of the library
|
||||
unstrip(bool):
|
||||
Try to fetch debug info for the libc and apply it to the downloaded file.
|
||||
|
||||
Returns:
|
||||
Path to the downloaded library on disk, or :const:`None`.
|
||||
@@ -222,7 +346,7 @@ def search_by_sha256(hex_encoded_id):
|
||||
>>> hex(ELF(filename).symbols.read)
|
||||
'0xeef40'
|
||||
"""
|
||||
return search_by_hash(hex_encoded_id, 'sha256')
|
||||
return search_by_hash(hex_encoded_id, 'sha256', unstrip)
|
||||
|
||||
|
||||
|
||||
@@ -279,4 +403,4 @@ def get_build_id_offsets():
|
||||
}.get(context.arch, [])
|
||||
|
||||
|
||||
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5']
|
||||
__all__ = ['get_build_id_offsets', 'search_by_build_id', 'search_by_sha1', 'search_by_sha256', 'search_by_md5', 'unstrip_libc']
|
||||
@@ -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))
|
||||
|
||||
+57
-14
@@ -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:
|
||||
@@ -1477,8 +1520,6 @@ class ROP(object):
|
||||
# Find an appropriate, non-library ELF.
|
||||
# Prioritise non-PIE binaries so we can use _fini
|
||||
exes = (elf for elf in self.elfs if not elf.library and elf.bits == 64)
|
||||
if not exes:
|
||||
log.error('No non-library binaries in [elfs]')
|
||||
|
||||
nonpie = csu = None
|
||||
for elf in exes:
|
||||
@@ -1488,6 +1529,8 @@ class ROP(object):
|
||||
nonpie = elf
|
||||
elif '__libc_csu_init' in elf.symbols:
|
||||
csu = elf
|
||||
else:
|
||||
log.error('No non-library binaries in [elfs]')
|
||||
|
||||
if elf.pie:
|
||||
if nonpie:
|
||||
|
||||
+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:
|
||||
|
||||
+33
-26
@@ -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:
|
||||
@@ -1523,29 +1534,24 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
local: Local directory
|
||||
remote: Remote directory
|
||||
"""
|
||||
remote = remote or self.cwd
|
||||
|
||||
remote = packing._encode(remote or self.cwd)
|
||||
|
||||
if self.sftp:
|
||||
remote = str(self.sftp.normalize(remote))
|
||||
remote = packing._encode(self.sftp.normalize(remote))
|
||||
else:
|
||||
with context.local(log_level='error'):
|
||||
remote = self.system('readlink -f ' + sh_string(remote))
|
||||
remote = self.system(b'readlink -f ' + sh_string(remote))
|
||||
|
||||
basename = os.path.basename(remote)
|
||||
local = local or '.'
|
||||
local = os.path.expanduser(local)
|
||||
|
||||
|
||||
local = local or '.'
|
||||
local = os.path.expanduser(local)
|
||||
|
||||
self.info("Downloading %r to %r" % (basename,local))
|
||||
self.info("Downloading %r to %r" % (remote, local))
|
||||
|
||||
with context.local(log_level='error'):
|
||||
remote_tar = self.mktemp()
|
||||
cmd = 'tar -C %s -czf %s %s' % \
|
||||
cmd = b'tar -C %s -czf %s .' % \
|
||||
(sh_string(remote),
|
||||
sh_string(remote_tar),
|
||||
sh_string(basename))
|
||||
sh_string(remote_tar))
|
||||
tar = self.system(cmd)
|
||||
|
||||
if 0 != tar.wait():
|
||||
@@ -1686,10 +1692,8 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
local(str): Local path to store the data.
|
||||
By default, uses the current directory.
|
||||
"""
|
||||
if not self.sftp:
|
||||
self.error("Cannot determine remote file type without SFTP")
|
||||
|
||||
with self.system('test -d ' + sh_string(file_or_directory)) as io:
|
||||
file_or_directory = packing._encode(file_or_directory)
|
||||
with self.system(b'test -d ' + sh_string(file_or_directory)) as io:
|
||||
is_dir = io.wait()
|
||||
|
||||
if 0 == is_dir:
|
||||
@@ -1827,6 +1831,9 @@ from ctypes import *; libc = CDLL('libc.so.6'); print(libc.getenv(%r))
|
||||
symlink = os.path.join(self.pwd(), b'*')
|
||||
if not hasattr(symlink, 'encode') and hasattr(symlink, 'decode'):
|
||||
symlink = symlink.decode('utf-8')
|
||||
|
||||
if isinstance(wd, six.text_type):
|
||||
wd = packing._need_bytes(wd, 2, 0x80)
|
||||
|
||||
if not wd:
|
||||
wd, status = self.run_to_end('x=$(mktemp -d) && cd $x && chmod +x . && echo $PWD', wd='.')
|
||||
|
||||
+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''):
|
||||
|
||||
+32
-4
@@ -197,6 +197,16 @@ def cyclic_find(subseq, alphabet = None, n = None):
|
||||
>>> cyclic_find(0x61616162, endian='big')
|
||||
1
|
||||
|
||||
Similarly to the case where you can provide a bytes value that is longer
|
||||
than four bytes, you can provided an integer value that is larger than
|
||||
what can be held in four bytes. If such a large value is given, it is
|
||||
automatically truncated.
|
||||
|
||||
>>> cyclic_find(0x6161616361616162)
|
||||
4
|
||||
>>> cyclic_find(0x6261616163616161, endian='big')
|
||||
4
|
||||
|
||||
You can use anything for the cyclic pattern, including non-printable
|
||||
characters.
|
||||
|
||||
@@ -208,14 +218,32 @@ def cyclic_find(subseq, alphabet = None, n = None):
|
||||
n = context.cyclic_size
|
||||
|
||||
if isinstance(subseq, six.integer_types):
|
||||
if subseq >= 2**(8*n):
|
||||
# Assumption: The user has given an integer that is more than 2**(8n) bits, but would otherwise fit within
|
||||
# a register of size 2**(8m) where m is a multiple of four
|
||||
notice = ("cyclic_find() expected an integer argument <= {cap:#x}, you gave {gave:#x}\n"
|
||||
"Unless you specified cyclic(..., n={fits}), you probably just want the first {n} bytes.\n"
|
||||
"Truncating the data at {n} bytes. Specify cyclic_find(..., n={fits}) to override this.").format(
|
||||
cap=2**(8*n)-1,
|
||||
gave=subseq,
|
||||
# The number of bytes needed to represent subseq, rounded to the next 4
|
||||
fits=int(round(float(subseq.bit_length()) / 32 + 0.5) * 32) // 8,
|
||||
n=n,
|
||||
)
|
||||
log.warn_once(notice)
|
||||
if context.endian == 'little':
|
||||
subseq &= 2**(8*n) - 1
|
||||
else:
|
||||
while subseq >= 2**(8*n):
|
||||
subseq >>= 8*n
|
||||
subseq = packing.pack(subseq, bytes=n)
|
||||
subseq = packing._need_bytes(subseq, 2, 0x80)
|
||||
|
||||
if len(subseq) != n:
|
||||
log.warn_once("cyclic_find() expects %i-byte subsequences by default, you gave %r\n"
|
||||
"Unless you specified cyclic(..., n=%i), you probably just want the first 4 bytes.\n"
|
||||
"Truncating the data at 4 bytes. Specify cyclic_find(..., n=%i) to override this.",
|
||||
n, subseq, len(subseq), len(subseq))
|
||||
log.warn_once("cyclic_find() expected a %i-byte subsequence, you gave %r\n"
|
||||
"Unless you specified cyclic(..., n=%i), you probably just want the first %d bytes.\n"
|
||||
"Truncating the data at %d bytes. Specify cyclic_find(..., n=%i) to override this.",
|
||||
n, subseq, len(subseq), n, n, len(subseq))
|
||||
subseq = subseq[:n]
|
||||
|
||||
if alphabet is None:
|
||||
|
||||
+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)
|
||||
+64
-9
@@ -100,13 +100,13 @@ def size(n, abbrev = 'B', si = False):
|
||||
|
||||
return '%.02fP%s' % (n / base, abbrev)
|
||||
|
||||
KB = 1024
|
||||
MB = 1024 * KB
|
||||
GB = 1024 * MB
|
||||
KB = 1000
|
||||
MB = 1000 * KB
|
||||
GB = 1000 * MB
|
||||
|
||||
KiB = 1000
|
||||
MiB = 1000 * KB
|
||||
GiB = 1000 * MB
|
||||
KiB = 1024
|
||||
MiB = 1024 * KiB
|
||||
GiB = 1024 * MiB
|
||||
|
||||
def read(path, count=-1, skip=0):
|
||||
r"""read(path, count=-1, skip=0) -> str
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1040,7 +1040,7 @@ def _need_text(s, level=1):
|
||||
if encoding == 'auto':
|
||||
for encoding in 'ASCII', 'UTF-8', 'ISO-8859-1':
|
||||
try:
|
||||
s = s.decode(encoding)
|
||||
s.decode(encoding)
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
|
||||
+4
-5
@@ -333,9 +333,9 @@ def wait_for_debugger(pid, debugger_pid=None):
|
||||
t = Timeout()
|
||||
with t.countdown(timeout=15):
|
||||
with log.waitfor('Waiting for debugger') as l:
|
||||
while debugger_pid:
|
||||
debugger = psutil.Process(debugger_pid)
|
||||
while t.timeout and tracer(pid) is None:
|
||||
while t.timeout and tracer(pid) is None:
|
||||
if debugger_pid:
|
||||
debugger = psutil.Process(debugger_pid)
|
||||
try:
|
||||
debugger.wait(0.01)
|
||||
except psutil.TimeoutExpired:
|
||||
@@ -343,8 +343,7 @@ def wait_for_debugger(pid, debugger_pid=None):
|
||||
else:
|
||||
debugger_pid = 0
|
||||
break
|
||||
else:
|
||||
while t.timeout and tracer(pid) is None:
|
||||
else:
|
||||
time.sleep(0.01)
|
||||
|
||||
if tracer(pid):
|
||||
|
||||
@@ -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.7.0'
|
||||
__version__ = '4.9.0'
|
||||
Reference in new issue
Block a user