Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add js_escape and js_unescape #1979

Merged
merged 7 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,14 @@ The table below shows which release corresponds to each branch, and what date th
## 4.9.0 (`dev`)

- [#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
- [#2035][2035] Change Buffer's parent class to object

[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
Expand Down
115 changes: 115 additions & 0 deletions pwnlib/util/fiddling.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
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
Expand Down Expand Up @@ -971,3 +973,116 @@ 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)

if len(padding) != 1:
raise ValueError("Padding must be a single byte")
padding = packing._need_bytes(padding)
Arusekk marked this conversation as resolved.
Show resolved Hide resolved

if len(data) % 2:
data += padding[0:1]

if isinstance(data, six.string_types):
# Give Python 2 an iterable of ints, similar to how a Python 3 bytes works
data = map(ord, data)
Arusekk marked this conversation as resolved.
Show resolved Hide resolved

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'abcAA123'

>>> 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._need_text(s)
Arusekk marked this conversation as resolved.
Show resolved Hide resolved
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._encode(s[p]))
Arusekk marked this conversation as resolved.
Show resolved Hide resolved
p += 1

return b''.join(res)
3 changes: 2 additions & 1 deletion pwnlib/util/packing.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
r"""
Module for packing and unpacking integers.
Module for packing and unpacking integers and data.
Arusekk marked this conversation as resolved.
Show resolved Hide resolved

Simplifies access to the standard ``struct.pack`` and ``struct.unpack``
functions, and also adds support for packing/unpacking arbitrary-width
Expand Down Expand Up @@ -35,6 +35,7 @@

import collections
import six
import string
Arusekk marked this conversation as resolved.
Show resolved Hide resolved
import struct
import sys
import warnings
Expand Down