From 4d600e0840855ef19097d47cced7026f48dc4a9f Mon Sep 17 00:00:00 2001 From: Arusekk Date: Fri, 28 Feb 2025 14:26:08 +0100 Subject: [PATCH 1/2] Use format strings and raw strings --- pwnlib/adb/adb.py | 14 ++++---- pwnlib/asm.py | 15 ++++---- pwnlib/commandline/checksec.py | 2 +- pwnlib/commandline/shellcraft.py | 16 ++------- pwnlib/constants/__init__.py | 4 +-- .../data/includes/generator/load_constants.py | 2 +- pwnlib/data/syscalls/generate.py | 7 +--- pwnlib/data/templates/pwnup.mako | 4 +-- pwnlib/elf/elf.py | 18 +++++----- pwnlib/encoders/i386/ascii_shellcode.py | 13 ++++--- pwnlib/libcdb.py | 8 ++--- pwnlib/memleak.py | 6 ++-- pwnlib/rop/srop.py | 2 +- pwnlib/shellcraft/__init__.py | 5 +-- pwnlib/term/completer.py | 4 +-- pwnlib/term/readline.py | 2 +- pwnlib/term/term.py | 2 +- pwnlib/term/windows_termcap.py | 4 +-- pwnlib/tubes/process.py | 20 +++++------ pwnlib/tubes/ssh.py | 6 ++-- pwnlib/tubes/tube.py | 10 +++--- pwnlib/ui.py | 2 +- pwnlib/util/fiddling.py | 35 +++++++++++++------ pwnlib/util/misc.py | 6 ++-- pwnlib/util/packing.py | 30 ++++++++-------- pwnlib/util/sh_string.py | 6 ++-- 26 files changed, 121 insertions(+), 122 deletions(-) diff --git a/pwnlib/adb/adb.py b/pwnlib/adb/adb.py index e2d8ebce7..23692e41c 100644 --- a/pwnlib/adb/adb.py +++ b/pwnlib/adb/adb.py @@ -887,15 +887,15 @@ def which(name, all = False, *a, **kw): [] """ # Unfortunately, there is no native 'which' on many phones. - which_cmd = ''' + which_cmd = fr''' (IFS=: for directory in $PATH; do [ -x "$directory/{name}" ] || continue; - echo -n "$directory/{name}\\x00"; + echo -n "$directory/{name}\x00"; done ) -[ -x "{name}" ] && echo -n "$PWD/{name}\\x00" -'''.format(name=name) +[ -x "{name}" ] && echo -n "$PWD/{name}\x00" +''' which_cmd = which_cmd.strip() data = process(['sh','-c', which_cmd], *a, **kw).recvall() @@ -1562,9 +1562,9 @@ def install(apk, *arguments): log.error("APK must have .apk extension") basename = os.path.basename(apk) - target_path = '/data/local/tmp/{}.apk'.format(basename) + target_path = f'/data/local/tmp/{basename}.apk' - with log.progress("Installing APK {}".format(basename)) as p: + with log.progress(f"Installing APK {basename}") as p: with context.quiet: p.status('Copying APK to device') push(apk, target_path) @@ -1585,7 +1585,7 @@ def uninstall(package, *arguments): package(str): Name of the package to uninstall (e.g. ``'com.foo.MyPackage'``) arguments: Supplementary arguments to ``'pm install'``, e.g. ``'-k'``. """ - with log.progress("Uninstalling package {}".format(package)): + with log.progress(f"Uninstalling package {package}"): with context.quiet: return process(['pm','uninstall',package] + list(arguments)).recvall() diff --git a/pwnlib/asm.py b/pwnlib/asm.py index a41352a91..8529eaf97 100644 --- a/pwnlib/asm.py +++ b/pwnlib/asm.py @@ -59,6 +59,7 @@ from pwnlib.context import LocalContext from pwnlib.context import context from pwnlib.log import getLogger +from pwnlib.util.fiddling import hexstr from pwnlib.util.hashes import sha1sumhex from pwnlib.util.packing import _encode from pwnlib.version import __version__ @@ -133,11 +134,10 @@ def print_binutils_instructions(util, context): if packages: instructions = '$ sudo apt-get install %s' % packages[0] - log.error(""" -Could not find %(util)r installed for %(context)s + log.error(f"""\ +Could not find {util!r} installed for {context} Try installing binutils for this architecture: -%(instructions)s -""".strip() % locals()) +{instructions}""") def check_binutils_version(util): @@ -624,8 +624,7 @@ def make_elf(data, assembler = _assembler() linker = _linker() code = _arch_header() - code += '.string "%s"' % ''.join('\\x%02x' % c for c in bytearray(data)) - code += '\n' + code += f'.string {hexstr(data)}\n' log.debug("Building ELF:\n" + code) @@ -693,7 +692,7 @@ def make_macho(data, is_shellcode=False): if is_shellcode: code += cpp(data) else: - code += '.string "%s"' % ''.join('\\x%02x' % c for c in bytearray(data)) + code += f'.string {hexstr(data)}\n' log.debug('Assembling\n%s' % code) @@ -802,7 +801,7 @@ def asm(shellcode, vma = 0, extract = True, shared = False): os.makedirs(cache_dir) # Include the context in the hash in addition to the shellcode - hash_params = '{}_{}_{}_{}'.format(vma, extract, shared, __version__) + hash_params = f'{vma}_{extract}_{shared}_{__version__}' fingerprint_params = _encode(code) + _encode(hash_params) + _encode(' '.join(assembler)) + _encode(' '.join(linker)) + _encode(' '.join(objcopy)) asm_hash = sha1sumhex(fingerprint_params) cache_file = os.path.join(cache_dir, asm_hash) diff --git a/pwnlib/commandline/checksec.py b/pwnlib/commandline/checksec.py index d346a89c4..ab8ca6c4c 100644 --- a/pwnlib/commandline/checksec.py +++ b/pwnlib/commandline/checksec.py @@ -36,7 +36,7 @@ def main(args): try: e = ELF(f) except Exception as e: - print("{name}: {error}".format(name=f, error=e)) + print(f"{f}: {e}") if __name__ == '__main__': common.main(__file__, main) diff --git a/pwnlib/commandline/shellcraft.py b/pwnlib/commandline/shellcraft.py index f5f6fd6cf..cb4ed6d70 100644 --- a/pwnlib/commandline/shellcraft.py +++ b/pwnlib/commandline/shellcraft.py @@ -11,6 +11,7 @@ from pwn import * from pwnlib.commandline import common +from pwnlib.util.fiddling import hexstr # ____ _ _ _ __ _ @@ -19,17 +20,6 @@ # ___) | | | | __/ | | (__| | | (_| | _| |_ # |____/|_| |_|\___|_|_|\___|_| \__,_|_| \__| -def _string(s): - out = [] - for co in bytearray(s): - c = chr(co) - if co >= 0x20 and co <= 0x7e and c not in '/$\'"`': - out.append(c) - else: - out.append('\\x%02x' % co) - return '"' + ''.join(out) + '"\n' - - p = common.parser_commands.add_parser( 'shellcraft', help = 'Microwave shellcode -- Easy, fast and delicious', @@ -366,7 +356,7 @@ def main(args): sys.exit(0) if args.format in ['s', 'str', 'string']: - code = _string(code) + code = hexstr(code) elif args.format == 'c': code = '{' + ', '.join(map(hex, bytearray(code))) + '}' + '\n' elif args.format in ['h', 'hex']: @@ -374,7 +364,7 @@ def main(args): elif args.format in ['i', 'hexii']: code = hexii(code) + '\n' elif args.format in ['d', 'escaped']: - code = ''.join('\\x%02x' % c for c in bytearray(code)) + '\n' + code = hexstr(code, force=True) + '\n' if not sys.stdin.isatty(): args.out.write(getattr(sys.stdin, 'buffer', sys.stdin).read()) diff --git a/pwnlib/constants/__init__.py b/pwnlib/constants/__init__.py index 104af2cc0..938f0488d 100644 --- a/pwnlib/constants/__init__.py +++ b/pwnlib/constants/__init__.py @@ -148,10 +148,10 @@ def eval(self, string): val = safeeval.values(string, self._env_store[key]) # if the expression is not assembly-safe, it is not so vital to preserve it - if set(string) & (set(bytearray(range(32)).decode()) | set('"#$\',.;@[\\]`{}')): + if set(string) & (set(bytearray(range(32)).decode()) | set(r'"#$\',.;@[]`{}')): string = val - return Constant('(%s)' % string, val) + return Constant(f'({string})', val) # To prevent garbage collection diff --git a/pwnlib/data/includes/generator/load_constants.py b/pwnlib/data/includes/generator/load_constants.py index 79b9e3144..7bca268a3 100755 --- a/pwnlib/data/includes/generator/load_constants.py +++ b/pwnlib/data/includes/generator/load_constants.py @@ -39,7 +39,7 @@ if paren: val = '(%s)' % val - print("{key} = Constant({key!r},{val})".format(**locals()), file=python) + print(f"{key} = Constant({key!r},{val})", file=python) if re.search(r'0o[0-7]', val) or re.match(r'[^0-9a-fA-Fx]0[0-9]', val): print("#define %s %s" % (key, hex(safeeval.expr(val))), file=header) else: diff --git a/pwnlib/data/syscalls/generate.py b/pwnlib/data/syscalls/generate.py index 9a3af4910..53f4420c5 100644 --- a/pwnlib/data/syscalls/generate.py +++ b/pwnlib/data/syscalls/generate.py @@ -239,12 +239,7 @@ def generate_one(target): string_arguments.append(argname) argtype = str(arg.type) + ('*' * arg.derefcnt) - arg_docs.append( - ' {argname_}({argtype}): {argname}'.format( - argname_=argname_, - argname=argname, - argtype=argtype, - )) + arg_docs.append(f' {argname_}({argtype}): {argname}') # Mako is unable to use *vararg and *kwarg, so we just stub in # a whole bunch of additional arguments. diff --git a/pwnlib/data/templates/pwnup.mako b/pwnlib/data/templates/pwnup.mako index 28c670869..a3e2d32e5 100644 --- a/pwnlib/data/templates/pwnup.mako +++ b/pwnlib/data/templates/pwnup.mako @@ -155,7 +155,7 @@ def start(argv=[], *a, **kw): # GDB will be launched if the exploit is run via e.g. # ./exploit.py GDB %endif -gdbscript = ''' +gdbscript = f''' %if ctx.binary: %if 'main' in ctx.binary.symbols: tbreak main @@ -164,7 +164,7 @@ tbreak *0x{exe.entry:x} %endif %endif continue -'''.format(**locals()) +''' %endif diff --git a/pwnlib/elf/elf.py b/pwnlib/elf/elf.py index df8661e45..31646adac 100644 --- a/pwnlib/elf/elf.py +++ b/pwnlib/elf/elf.py @@ -1196,7 +1196,7 @@ def find_ret_main_addr(lines, calls): return 0 def search(self, needle, writable = False, executable = False): - """search(needle, writable = False, executable = False) -> generator + r"""search(needle, writable = False, executable = False) -> generator Search the ELF's virtual address space for the specified string. @@ -1216,7 +1216,7 @@ def search(self, needle, writable = False, executable = False): Examples: - An ELF header starts with the bytes ``\\x7fELF``, so we + An ELF header starts with the bytes ``\x7fELF``, so we sould be able to find it easily. >>> bash = ELF('/bin/bash') @@ -1725,7 +1725,7 @@ def relro(self): @property def nx(self): - """:class:`bool`: Whether the current binary uses NX protections. + r""":class:`bool`: Whether the current binary uses NX protections. Specifically, we are checking for ``READ_IMPLIES_EXEC`` being set by the kernel, as a result of honoring ``PT_GNU_STACK`` in the kernel. @@ -1788,7 +1788,7 @@ def nx(self): | the rest | [#the_rest]_ | exec / non-exec / missing | | enabled | +-----------+--------------+---------------------------+------------------------------------------------+----------+ - \\* Hardware limitations are ignored. + \* Hardware limitations are ignored. If ``READ_IMPLIES_EXEC`` is set, then `all readable pages are executable`__. .. __: https://github.com/torvalds/linux/blob/v6.3/fs/binfmt_elf.c#L1008-L1009 @@ -1802,7 +1802,7 @@ def nx(self): .. code-block:: c - #define elf_read_implies_exec(ex, executable_stack) \\ + #define elf_read_implies_exec(ex, executable_stack) \ (executable_stack != EXSTACK_DISABLE_X) .. [#x86_5.8] @@ -1810,7 +1810,7 @@ def nx(self): .. code-block:: c - #define elf_read_implies_exec(ex, executable_stack) \\ + #define elf_read_implies_exec(ex, executable_stack) \ (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT) `mmap_is_ia32()`__: @@ -1889,7 +1889,7 @@ def nx(self): #ifdef __powerpc64__ /* stripped */ - # define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \\ + # define elf_read_implies_exec(ex, exec_stk) (is_32bit_task() ? \ (exec_stk == EXSTACK_DEFAULT) : 0) #else # define elf_read_implies_exec(ex, exec_stk) (exec_stk == EXSTACK_DEFAULT) @@ -1900,7 +1900,7 @@ def nx(self): .. code-block:: c - #define elf_read_implies_exec(ex, executable_stack) \\ + #define elf_read_implies_exec(ex, executable_stack) \ ((executable_stack!=EXSTACK_DISABLE_X) && ((ex).e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK) != 0) EF_IA_64_LINUX_EXECUTABLE_STACK__: @@ -2153,7 +2153,7 @@ def checksec(self, banner=True, color=True): for name, message in sorted(values): line = '{} = {}'.format(name, red(str(self.config.get(name, None)))) if message: - line += ' ({})'.format(message) + line += f' ({message})' res.append(' ' + line) # res.extend(sorted(config_opts)) diff --git a/pwnlib/encoders/i386/ascii_shellcode.py b/pwnlib/encoders/i386/ascii_shellcode.py index 7071d30ff..ec6befe18 100644 --- a/pwnlib/encoders/i386/ascii_shellcode.py +++ b/pwnlib/encoders/i386/ascii_shellcode.py @@ -86,10 +86,9 @@ def __call__(self, raw_bytes, avoid=None, pcreg=None): b'Hello world' """ if not avoid: - vocab = bytearray( - b"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~") + vocab = bytes(range(0x21, 0x7f)) else: - required_chars = set('\\-%TXP') + required_chars = set(r'\-%TXP') allowed = set(all_chars) if avoid.intersection(required_chars): raise RuntimeError( @@ -127,7 +126,7 @@ def _get_allocator(self, size, vocab): Examples: >>> context.update(arch='i386', os='linux') - >>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~') + >>> vocab = bytes(range(0x21, 0x7f)) >>> encoders.i386.ascii_shellcode.encode._get_allocator(300, vocab) bytearray(b'TX-!!!!-!_``-t~~~P\\%!!!!%@@@@') """ @@ -173,7 +172,7 @@ def _find_negatives(self, vocab): Examples: >>> context.update(arch='i386', os='linux') - >>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~') + >>> vocab = bytes(range(0x21, 0x7f)) >>> a, b = encoders.i386.ascii_shellcode.encode._find_negatives(vocab) >>> a & b 0 @@ -207,7 +206,7 @@ def _get_subtractions(self, shellcode, vocab): >>> context.update(arch='i386', os='linux') >>> sc = bytearray(b'ABCDEFGHIGKLMNOPQRSTUVXYZ') - >>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~') + >>> vocab = bytes(range(0x21, 0x7f)) >>> encoders.i386.ascii_shellcode.encode._get_subtractions(sc, vocab) bytearray(b'-(!!!-~NNNP-!=;:-f~~~-~~~~P-!!!!-edee-~~~~P-!!!!-eddd-~~~~P-!!!!-egdd-~~~~P-!!!!-eadd-~~~~P-!!!!-eddd-~~~~P') """ @@ -250,7 +249,7 @@ def _calc_subtractions(self, last, target, vocab): Examples: >>> context.update(arch='i386', os='linux') - >>> vocab = bytearray(b'!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~') + >>> vocab = bytes(range(0x21, 0x7f)) >>> print(encoders.i386.ascii_shellcode.encode._calc_subtractions(bytearray(b'\x10'*4), bytearray(b'\x11'*4), vocab)) [bytearray(b'!!!!'), bytearray(b'`___'), bytearray(b'~~~~')] >>> print(encoders.i386.ascii_shellcode.encode._calc_subtractions(bytearray(b'\x11\x12\x13\x14'), bytearray(b'\x15\x16\x17\x18'), vocab)) diff --git a/pwnlib/libcdb.py b/pwnlib/libcdb.py index bde4f2c56..33d7f720f 100644 --- a/pwnlib/libcdb.py +++ b/pwnlib/libcdb.py @@ -89,7 +89,7 @@ def provider_libcdb(hex_encoded_id, search_type): import urllib.parse # Build the URL using the requested hash type - url_base = "{}/libcdb/libcdb/raw/master/hashes/{}/".format(GITLAB_LIBCDB_URL, search_type) + url_base = f"{GITLAB_LIBCDB_URL}/libcdb/libcdb/raw/master/hashes/{search_type}/" url = urllib.parse.urljoin(url_base, hex_encoded_id) data = b"" @@ -114,7 +114,7 @@ def query_libc_rip(params): # Deferred import because it's slow import requests - url = "{}/api/find".format(LIBC_RIP_URL) + url = f"{LIBC_RIP_URL}/api/find" try: result = requests.post(url, json=params, timeout=20) result.raise_for_status() @@ -316,7 +316,7 @@ def _search_debuginfo_by_hash(base_url, hex_encoded_id): return None # Try to find separate debuginfo. - url = '/buildid/{}/debuginfo'.format(hex_encoded_id) + url = f'/buildid/{hex_encoded_id}/debuginfo' url = urllib.parse.urljoin(base_url, url) data = b"" log.debug("Downloading data from debuginfod: %s", url) @@ -513,7 +513,7 @@ def _find_libc_package_lib_url(libc): version = re.search(br'GNU C Library \(Ubuntu E?GLIBC ([^\)]+)\)', libc.data) if version is not None: libc_version = version.group(1).decode() - yield 'https://launchpad.net/ubuntu/+archive/primary/+files/libc6_{}_{}.deb'.format(libc_version, libc.arch) + yield f'https://launchpad.net/ubuntu/+archive/primary/+files/libc6_{libc_version}_{libc.arch}.deb' def download_libraries(libc_path, unstrip=True): """download_libraries(str, bool) -> str diff --git a/pwnlib/memleak.py b/pwnlib/memleak.py index 46871ab26..24437a66c 100644 --- a/pwnlib/memleak.py +++ b/pwnlib/memleak.py @@ -15,7 +15,7 @@ __all__ = ['MemLeak', 'RelativeMemLeak'] class MemLeak(object): - """MemLeak is a caching and heuristic tool for exploiting memory leaks. + r"""MemLeak is a caching and heuristic tool for exploiting memory leaks. It can be used as a decorator, around functions of the form: @@ -48,9 +48,9 @@ def some_leaker(addr): >>> leaker.s(0)[:4] leaking 0x0 leaking 0x4 - b'\\x7fELF' + b'\x7fELF' >>> leaker[:4] - b'\\x7fELF' + b'\x7fELF' >>> hex(leaker.d(0)) '0x464c457f' >>> hex(leaker.clearb(1)) diff --git a/pwnlib/rop/srop.py b/pwnlib/rop/srop.py index 2d2a27f24..c8f7c53a1 100644 --- a/pwnlib/rop/srop.py +++ b/pwnlib/rop/srop.py @@ -15,7 +15,7 @@ Let's just print a message out using SROP. - >>> message = "Hello, World\\n" + >>> message = r"Hello, World\n" First, we'll create our example binary. It just reads some data onto the stack, and invokes diff --git a/pwnlib/shellcraft/__init__.py b/pwnlib/shellcraft/__init__.py index 58f20379b..7b6fb9c04 100644 --- a/pwnlib/shellcraft/__init__.py +++ b/pwnlib/shellcraft/__init__.py @@ -149,8 +149,9 @@ def pretty(self, n, comment=True): if not isinstance(n, int): return n if isinstance(n, constants.Constant): - if comment: return '%s /* %s */' % (n,self.pretty(int(n))) - else: return '%s (%s)' % (n,self.pretty(int(n))) + pretty = self.pretty(int(n)) + if comment: return f'{n} /* {pretty} */' + else: return f'{n} ({pretty})' elif abs(n) < 10: return '%d' % n else: diff --git a/pwnlib/term/completer.py b/pwnlib/term/completer.py index e928cb8cc..3831510c6 100644 --- a/pwnlib/term/completer.py +++ b/pwnlib/term/completer.py @@ -22,7 +22,7 @@ def __exit__(self, *args): class WordCompleter(Completer): def __init__(self, delims = None): - self.delims = delims or ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' + self.delims = delims or r' \t\n`!@#$^&*()=+[{]}|;:\'",<>?' self._cur_word = None self._completions = [] @@ -85,7 +85,7 @@ def complete_word(self, word): class PathCompleter(Completer): def __init__(self, mask = '*', only_dirs = False): if mask != '*': - mask = mask.replace('.', '\\.').replace('*', '.*') + mask = mask.replace('.', r'\.').replace('*', '.*') self.mask = re.compile('^' + mask + '$') else: self.mask = None diff --git a/pwnlib/term/readline.py b/pwnlib/term/readline.py index 82536b6eb..fd27deab8 100644 --- a/pwnlib/term/readline.py +++ b/pwnlib/term/readline.py @@ -26,7 +26,7 @@ startup_hook = None shutdown_hook = None -delims = ' /;:.\\' +delims = r' /;:\.' show_completion = True show_suggestions = False diff --git a/pwnlib/term/term.py b/pwnlib/term/term.py index d8062bfa5..1eefbb3b1 100644 --- a/pwnlib/term/term.py +++ b/pwnlib/term/term.py @@ -164,7 +164,7 @@ def hook(*args): traceback.print_exception(*args) sys.excepthook = hook -tmap = {c: '\\x{:02x}'.format(c) for c in set(range(0x20)) - {0x09, 0x0a, 0x0d, 0x1b} | {0x7f}} +tmap = {c: fr'\x{c:02x}' for c in set(range(0x20)) - {0x09, 0x0a, 0x0d, 0x1b} | {0x7f}} def put(s): global cached_pos, epoch diff --git a/pwnlib/term/windows_termcap.py b/pwnlib/term/windows_termcap.py index 0b9d6f71d..b0cfd0ba7 100644 --- a/pwnlib/term/windows_termcap.py +++ b/pwnlib/term/windows_termcap.py @@ -45,8 +45,8 @@ def init(): cache['bold'] = '\x1b[1m' cache['smul'] = '\x1b[4m' cache['rev'] = '\x1b[7m' - cache['setaf'] = lambda c: '\x1b[3{}m'.format(c) if c < 8 else '\x1b[9{}m'.format(c-8) - cache['setab'] = lambda c: '\x1b[4{}m'.format(c) if c < 8 else '\x1b[10{}m'.format(c-8) + cache['setaf'] = lambda c: f'\x1b[3{c}m' if c < 8 else f'\x1b[9{c-8}m' + cache['setab'] = lambda c: f'\x1b[4{c}m' if c < 8 else f'\x1b[10{c-8}m' # Enable ANSI escape sequences on Windows 10. # https://bugs.python.org/issue30075 diff --git a/pwnlib/tubes/process.py b/pwnlib/tubes/process.py index c53ab2e5c..93c25b31d 100644 --- a/pwnlib/tubes/process.py +++ b/pwnlib/tubes/process.py @@ -889,7 +889,7 @@ def __pty_make_controlling_tty(self, tty_fd): os.close(fd) def maps(self): - """maps() -> [mapping] + r"""maps() -> [mapping] Returns a list of process mappings. @@ -904,7 +904,7 @@ def maps(self): >>> p = process(['cat']) >>> p.sendline(b"meow") >>> p.recvline() - b'meow\\n' + b'meow\n' >>> proc_maps = open("/proc/" + str(p.pid) + "/maps", "r").readlines() >>> pwn_maps = p.maps() >>> len(proc_maps) == len(pwn_maps) @@ -1041,7 +1041,7 @@ def stack_mapping(self, single=True): return self.get_mapping('[stack]', single) def heap_mapping(self, single=True): - """heap_mapping(single=True) -> mapping + r"""heap_mapping(single=True) -> mapping heap_mapping(False) -> [mapping] Arguments: @@ -1055,7 +1055,7 @@ def heap_mapping(self, single=True): >>> p = process(['cat']) >>> p.sendline(b'meow') >>> p.recvline() - b'meow\\n' + b'meow\n' >>> mapping = p.heap_mapping() >>> mapping.path '[heap]' @@ -1131,7 +1131,7 @@ def vvar_mapping(self, single=True): return self.get_mapping('[vvar]', single) def libc_mapping(self, single=True): - """libc_mapping(single=True) -> mapping + r"""libc_mapping(single=True) -> mapping libc_mapping(False) -> [mapping] Arguments: @@ -1146,7 +1146,7 @@ def libc_mapping(self, single=True): >>> p = process(['cat']) >>> p.sendline(b'meow') >>> p.recvline() - b'meow\\n' + b'meow\n' >>> mapping = p.libc_mapping() >>> mapping.path # doctest: +ELLIPSIS '...libc...' @@ -1212,7 +1212,7 @@ def musl_mapping(self, single=True): return m_mappings def elf_mapping(self, single=True): - """elf_mapping(single=True) -> mapping + r"""elf_mapping(single=True) -> mapping elf_mapping(False) -> [mapping] Arguments: @@ -1226,7 +1226,7 @@ def elf_mapping(self, single=True): >>> p = process(['cat']) >>> p.sendline(b'meow') >>> p.recvline() - b'meow\\n' + b'meow\n' >>> mapping = p.elf_mapping() >>> mapping.path # doctest: +ELLIPSIS '...cat...' @@ -1292,7 +1292,7 @@ def lib_size(self, path_value): return total_size def address_mapping(self, address): - """address_mapping(address) -> mapping + r"""address_mapping(address) -> mapping Returns the mapping at the specified address. @@ -1301,7 +1301,7 @@ def address_mapping(self, address): >>> p = process(['cat']) >>> p.sendline(b'meow') >>> p.recvline() - b'meow\\n' + b'meow\n' >>> libc = p.libc_mapping().address >>> heap = p.heap_mapping().address >>> elf = p.elf_mapping().address diff --git a/pwnlib/tubes/ssh.py b/pwnlib/tubes/ssh.py index a22accb47..bb04f4a5f 100644 --- a/pwnlib/tubes/ssh.py +++ b/pwnlib/tubes/ssh.py @@ -683,10 +683,10 @@ def __init__(self, user=None, host=None, port=22, password=None, key=None, try: self.client.connect(host, port, user, password, key, keyfiles, self.timeout, allow_agent=ssh_agent, compress=True, sock=proxy_sock, look_for_keys=not ignore_config) except paramiko.BadHostKeyException as e: - self.error("Remote host %(host)s is using a different key than stated in known_hosts\n" + self.error(f"Remote host {host} is using a different key than stated in known_hosts\n" " To remove the existing entry from your known_hosts and trust the new key, run the following commands:\n" - " $ ssh-keygen -R %(host)s\n" - " $ ssh-keygen -R [%(host)s]:%(port)s" % locals()) + " $ ssh-keygen -R {host}\n" + " $ ssh-keygen -R [{host}]:{port}") except paramiko.SSHException as e: if user and auth_none and str(e) == "No authentication methods available": self.client.get_transport().auth_none(user) diff --git a/pwnlib/tubes/tube.py b/pwnlib/tubes/tube.py index 47afa0055..275eb3a88 100644 --- a/pwnlib/tubes/tube.py +++ b/pwnlib/tubes/tube.py @@ -1131,7 +1131,7 @@ def clean_and_log(self, timeout = 0.05): return cached_data + self.clean(timeout) def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_size = 0x200, chmod_flags = 'u+x', compression='auto', end_marker = 'PWNTOOLS_DONE'): - """upload_manually(data, target_path = './payload', prompt = b'$', chunk_size = 0x200, chmod_flags = 'u+x', compression='auto', end_marker = 'PWNTOOLS_DONE') + r"""upload_manually(data, target_path = './payload', prompt = b'$', chunk_size = 0x200, chmod_flags = 'u+x', compression='auto', end_marker = 'PWNTOOLS_DONE') Upload a file manually using base64 encoding and compression. This can be used when the tube is connected to a shell. @@ -1169,21 +1169,21 @@ def upload_manually(self, data, target_path = './payload', prompt = b'$', chunk_ >>> l = listen() >>> l.spawn_process('/bin/sh') >>> r = remote('127.0.0.1', l.lport) - >>> r.upload_manually(b'some\\xca\\xfedata\\n', prompt=b'', chmod_flags='') + >>> r.upload_manually(b'some\xca\xfedata\n', prompt=b'', chmod_flags='') >>> r.sendline(b'cat ./payload') >>> r.recvline() - b'some\\xca\\xfedata\\n' + b'some\xca\xfedata\n' >>> r.upload_manually(cyclic(0x1000), target_path='./cyclic_pattern', prompt=b'', chunk_size=0x10, compression='gzip') >>> r.sendline(b'sha256sum ./cyclic_pattern') >>> r.recvlineS(keepends=False).startswith(sha256sumhex(cyclic(0x1000))) True - >>> blob = ELF.from_assembly(shellcraft.echo('Hello world!\\n') + shellcraft.exit(0)) + >>> blob = ELF.from_assembly(shellcraft.echo('Hello world!\n') + shellcraft.exit(0)) >>> r.upload_manually(blob.data, prompt=b'') >>> r.sendline(b'./payload') >>> r.recvline() - b'Hello world!\\n' + b'Hello world!\n' >>> r.close() >>> l.close() """ diff --git a/pwnlib/ui.py b/pwnlib/ui.py index 2789f6eba..adccb0105 100644 --- a/pwnlib/ui.py +++ b/pwnlib/ui.py @@ -319,7 +319,7 @@ def more(text): >>> more("text") text - >>> p = testpwnproc("more('text\\n' * (term.height + 2))") + >>> p = testpwnproc(r"more('text\n' * (term.height + 2))") >>> p.send(b"x") >>> data = p.recvall() >>> b"text" in data or data diff --git a/pwnlib/util/fiddling.py b/pwnlib/util/fiddling.py index 967892fd1..da3850281 100644 --- a/pwnlib/util/fiddling.py +++ b/pwnlib/util/fiddling.py @@ -61,6 +61,21 @@ def enhex(x): x = x.decode('ascii') return x + +def hexstr(s, force=False): + out = bytearray(b'"') + ban = False + for co in s: + if 0x20 <= co < 0x7f and co not in br'/$\'"`' + string.hexdigits.encode() * ban and not force: + out.append(co) + ban = False + else: + out.extend(br'\x%02x' % co) + ban = True + out.extend(b'"') + return out.decode() + + def urlencode(s): """urlencode(s) -> str @@ -179,7 +194,7 @@ def bits_str(s, endian = 'big', zero = '0', one = '1'): return ''.join(bits(s, endian, zero, one)) def unbits(s, endian = 'big'): - """unbits(s, endian = 'big') -> str + r"""unbits(s, endian = 'big') -> str Converts an iterable of bits into a string. @@ -193,11 +208,11 @@ def unbits(s, endian = 'big'): Example: >>> unbits([1]) - b'\\x80' + b'\x80' >>> unbits([1], endian = 'little') - b'\\x01' + b'\x01' >>> unbits(bits(b'hello'), endian = 'little') - b'\\x16\\xa666\\xf6' + b'\x16\xa666\xf6' """ if endian == 'little': u = lambda s: packing._p8lu(int(s[::-1], 2)) @@ -227,14 +242,14 @@ def unbits(s, endian = 'big'): def bitswap(s): - """bitswap(s) -> str + r"""bitswap(s) -> str Reverses the bits in every byte of a given string. Example: >>> bitswap(b"1234") - b'\\x8cL\\xcc,' + b'\x8cL\xcc,' """ out = [] @@ -362,7 +377,7 @@ def get(n): return b''.join(map(get, range(cut))) def xor_pair(data, avoid = b'\x00\n'): - """xor_pair(data, avoid = '\\x00\\n') -> None or (str, str) + r"""xor_pair(data, avoid = '\x00\n') -> None or (str, str) Finds two strings that will xor into a given string, while only using a given alphabet. @@ -377,7 +392,7 @@ def xor_pair(data, avoid = b'\x00\n'): Example: >>> xor_pair(b"test") - (b'\\x01\\x01\\x01\\x01', b'udru') + (b'\x01\x01\x01\x01', b'udru') """ if isinstance(data, int): @@ -1023,9 +1038,9 @@ def js_escape(data, padding=context.cyclic_alphabet[0:1], **kwargs): 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)) + return ''.join(f'%u{a:02x}{b:02x}' 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)) + return ''.join(f'%u{a:02x}{b:02x}' for a, b in iters.group(2, data)) @LocalNoarchContext def js_unescape(s, **kwargs): diff --git a/pwnlib/util/misc.py b/pwnlib/util/misc.py index 3b5682e8d..ad77c1071 100644 --- a/pwnlib/util/misc.py +++ b/pwnlib/util/misc.py @@ -53,14 +53,14 @@ def align_down(alignment, x): def binary_ip(host): - """binary_ip(host) -> str + r"""binary_ip(host) -> str Resolve host and return IP as four byte string. Example: >>> binary_ip("127.0.0.1") - b'\\x7f\\x00\\x00\\x01' + b'\x7f\x00\x00\x01' """ return socket.inet_aton(socket.gethostbyname(host)) @@ -926,7 +926,7 @@ def to_carray(py_list): # but just in case, indicate that something went wrong. libc.perror(b"execve") raise OSError("execve failed") -""" % locals() +""" script = script.strip() return script diff --git a/pwnlib/util/packing.py b/pwnlib/util/packing.py index e6a55f9a8..045fcda4b 100644 --- a/pwnlib/util/packing.py +++ b/pwnlib/util/packing.py @@ -238,7 +238,7 @@ def unpack(data, word_size = None): @LocalNoarchContext def unpack_many(data, word_size = None): - """unpack_many(data, word_size = None, endianness = None, sign = None) -> int list + r"""unpack_many(data, word_size = None, endianness = None, sign = None) -> int list Splits `data` into groups of ``word_size//8`` bytes and calls :func:`unpack` on each group. Returns a list of the results. @@ -256,15 +256,15 @@ def unpack_many(data, word_size = None): Examples: - >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='little', sign=False))) + >>> list(map(hex, unpack_many(b'\xaa\x55\xcc\x33', 16, endian='little', sign=False))) ['0x55aa', '0x33cc'] - >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='big', sign=False))) + >>> list(map(hex, unpack_many(b'\xaa\x55\xcc\x33', 16, endian='big', sign=False))) ['0xaa55', '0xcc33'] - >>> list(map(hex, unpack_many(b'\\xaa\\x55\\xcc\\x33', 16, endian='big', sign=True))) + >>> list(map(hex, unpack_many(b'\xaa\x55\xcc\x33', 16, endian='big', sign=True))) ['-0x55ab', '-0x33cd'] - >>> list(map(hex, unpack_many(b'\\xff\\x02\\x03', 'all', endian='little', sign=True))) + >>> list(map(hex, unpack_many(b'\xff\x02\x03', 'all', endian='little', sign=True))) ['0x302ff'] - >>> list(map(hex, unpack_many(b'\\xff\\x02\\x03', 'all', endian='big', sign=True))) + >>> list(map(hex, unpack_many(b'\xff\x02\x03', 'all', endian='big', sign=True))) ['-0xfdfd'] """ # Lookup in context if None @@ -512,7 +512,7 @@ def u64(data, endianness = None, **kwargs): return _do_packing('u', 64, data, endianness) def make_packer(word_size = None, sign = None, **kwargs): - """make_packer(word_size = None, endianness = None, sign = None) -> number → str + r"""make_packer(word_size = None, endianness = None, sign = None) -> number → str Creates a packer by "freezing" the given arguments. @@ -536,7 +536,7 @@ def make_packer(word_size = None, sign = None, **kwargs): >>> p >>> p(42) - b'*\\x00\\x00\\x00' + b'*\x00\x00\x00' >>> p(-1) Traceback (most recent call last): ... @@ -968,7 +968,7 @@ def unsigned(integer): return unpack(pack(integer)) def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False): - """dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False) -> dst + r"""dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False) -> dst Inspired by the command line tool ``dd``, this function copies `count` byte values from offset `seek` in `src` to offset `skip` in `dst`. If `count` is @@ -1012,10 +1012,10 @@ def dd(dst, src, count = 0, skip = 0, seek = 0, truncate = False): >>> _ = open('/tmp/foo', 'w').write('A' * 10) >>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4).read() - 'AAA\\x00\\x00\\x00\\x00AAA' + 'AAA\x00\x00\x00\x00AAA' >>> _ = open('/tmp/foo', 'w').write('A' * 10) >>> dd(open('/tmp/foo'), open('/dev/zero'), skip = 3, count = 4, truncate = True).read() - 'AAA\\x00\\x00\\x00\\x00' + 'AAA\x00\x00\x00\x00' """ # Re-open file objects to make sure we have the mode right @@ -1170,8 +1170,8 @@ def _need_bytes(s, level=1, min_wrong=0): encoding = 'ASCII' if worst >= min_wrong: - warnings.warn("Text is not bytes; assuming {}, no guarantees. See https://docs.pwntools.com/#bytes" - .format(encoding), BytesWarning, level + 2) + warnings.warn(f"Text is not bytes; assuming {encoding}, no guarantees. See https://docs.pwntools.com/#bytes", + BytesWarning, level + 2) return s.encode(encoding, errors) def _need_text(s, level=1): @@ -1192,8 +1192,8 @@ def _need_text(s, level=1): else: break - warnings.warn("Bytes is not text; assuming {}, no guarantees. See https://docs.pwntools.com/#bytes" - .format(encoding), BytesWarning, level + 2) + warnings.warn(f"Bytes is not text; assuming {encoding}, no guarantees. See https://docs.pwntools.com/#bytes", + BytesWarning, level + 2) return s.decode(encoding, errors) def _encode(s): diff --git a/pwnlib/util/sh_string.py b/pwnlib/util/sh_string.py index eb4ca19e9..0cae12958 100644 --- a/pwnlib/util/sh_string.py +++ b/pwnlib/util/sh_string.py @@ -353,7 +353,7 @@ def test(original): ESCAPED = { # The single quote itself must be escaped, outside of single quotes. - "'": "\\'", ## + "'": r"\'", ## # Slashes must themselves be escaped # @@ -460,8 +460,8 @@ def sh_prepare(variables, export = False): >>> sh_prepare({'X': 'foobar'}) b'X=foobar' >>> r = sh_prepare({'X': 'foobar', 'Y': 'cookies'}) - >>> r == b'X=foobar;Y=cookies' or r == b'Y=cookies;X=foobar' or r - True + >>> r + b'X=foobar;Y=cookies' >>> sh_prepare({'X': 'foo bar'}) b"X='foo bar'" >>> sh_prepare({'X': "foo'bar"}) From 174041ca8d7c49459df20b3b1b8cdef48916535d Mon Sep 17 00:00:00 2001 From: Arusekk Date: Wed, 2 Apr 2025 13:51:40 +0200 Subject: [PATCH 2/2] Revert functional changes --- pwnlib/commandline/shellcraft.py | 2 +- pwnlib/term/completer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pwnlib/commandline/shellcraft.py b/pwnlib/commandline/shellcraft.py index cb4ed6d70..d44afe459 100644 --- a/pwnlib/commandline/shellcraft.py +++ b/pwnlib/commandline/shellcraft.py @@ -364,7 +364,7 @@ def main(args): elif args.format in ['i', 'hexii']: code = hexii(code) + '\n' elif args.format in ['d', 'escaped']: - code = hexstr(code, force=True) + '\n' + code = ''.join(fr'\x{c:02x}' for c in code) + '\n' if not sys.stdin.isatty(): args.out.write(getattr(sys.stdin, 'buffer', sys.stdin).read()) diff --git a/pwnlib/term/completer.py b/pwnlib/term/completer.py index 3831510c6..e2f28856c 100644 --- a/pwnlib/term/completer.py +++ b/pwnlib/term/completer.py @@ -22,7 +22,7 @@ def __exit__(self, *args): class WordCompleter(Completer): def __init__(self, delims = None): - self.delims = delims or r' \t\n`!@#$^&*()=+[{]}|;:\'",<>?' + self.delims = delims or ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' self._cur_word = None self._completions = []