Skip to content

Commit 4daabd1

Browse files
committed
Added codecs: aaencode, jjencode
1 parent da515dc commit 4daabd1

7 files changed

Lines changed: 384 additions & 0 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,9 @@ This category also contains `ascii85`, `adobe`, `[x]btoa`, `zeromq` with the `ba
464464

465465
#### [Web](https://python-codext.readthedocs.io/en/latest/enc/web.html)
466466

467+
- [X] `aaencode`: JavaScript obfuscation with Japanese-style emoticons (encoding generates a program running the code)
467468
- [X] `html`: implements entities according to [this reference](https://dev.w3.org/html5/html-author/charref)
469+
- [X] `jjencode`: JavaScript obfuscation with only symbols (encoding generates a program running the code)
468470
- [X] `url`: aka URL encoding
469471

470472

‎docs/pages/enc/web.md‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,24 @@
22

33
-----
44

5+
### AAEncode
6+
7+
[AAEncode](https://utf-8.jp/public/aaencode.html), by Yosuke Hasegawa, turns JavaScript code into a JavaScript program made of Japanese-style emoticons (e.g. `゚ω゚ノ` or `(゚Д゚)`) that runs the code. The program builds the characters it needs from the string representations of JavaScript values (e.g. `false` or `undefined`), gets the `Function` constructor and calls it on a string literal holding the code, each character being written as an octal or Unicode escape sequence of its UTF-16 code unit. Encoding gives the same program as the reference encoder, while decoding parses the program (without running it), whatever its whitespaces, and returns the code.
8+
9+
**Codec** | **Conversions** | **Aliases** | **Comment**
10+
:---: | :---: | --- | ---
11+
`aaencode` | JavaScript code <-> AAEncoded program | `aa-encode`, `aa_encode` | malformed programs reported whatever the errors handling
12+
13+
```python
14+
>>> js = codext.encode("alert(1)", "aaencode")
15+
>>> js[:46]
16+
"゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) ="
17+
>>> codext.decode(js, "aaencode")
18+
'alert(1)'
19+
```
20+
21+
-----
22+
523
### HTML Entities
624

725
This implements the full list of characters available at [this reference](https://dev.w3.org/html5/html-author/charref).
@@ -19,6 +37,31 @@ This implements the full list of characters available at [this reference](https:
1937

2038
-----
2139

40+
### JJEncode
41+
42+
[JJEncode](https://utf-8.jp/public/jjencode.html), by Yosuke Hasegawa, turns JavaScript code into a JavaScript program only made of symbols (`$`, `_`, `+`, `!`, ...) and of the name of a global variable (`$` by default), that runs the code. The program fills the variable with an object whose properties hold the hexadecimal digits and some letters taken from the string representations of JavaScript values (e.g. `false` or `[object Object]`), gets the `Function` constructor and calls it on a string literal holding the code. Some punctuation characters are kept as is, the hexadecimal digits and the letters `l`, `o`, `t` and `u` are taken from the properties and the other characters are written as octal or Unicode escape sequences of their UTF-16 code units. Decoding parses the program (without running it), whatever the name of the variable, and returns the code.
43+
44+
**Codec** | **Conversions** | **Aliases** | **Comment**
45+
:---: | :---: | --- | ---
46+
`jjencode` | JavaScript code <-> JJEncoded program | `jj-encode`, `jj_encode` | malformed programs reported whatever the errors handling
47+
`jjencode-[var]` | JavaScript code <-> JJEncoded program using the given global variable | `jjencode-_`, `jj_encode-$x` | the variable name is a JavaScript identifier ; decoding reads it from the program
48+
49+
!!! note "Differences with the reference encoder"
50+
51+
The reference encoder gives a wrong program in two cases, where this codec pads the escape sequences instead: a character above U+007F and below U+1000 gets less than four hexadecimal digits (e.g. `\ue9` for `é`, a syntax error), and an octal escape sequence followed by a digit absorbs it (e.g. `\1` followed by `2` gives `\12`, a newline). Otherwise, encoding gives the same program as the reference encoder.
52+
53+
```python
54+
>>> js = codext.encode("alert(1)", "jjencode-x")
55+
>>> js[:17]
56+
'x=~[];x={___:++x,'
57+
>>> js[432:]
58+
'x.$(x.$(x.$$+"\\""+x.$_$_+(![]+"")[x._$_]+x.$$$_+"\\\\"+x.__$+x.$$_+x._$_+x.__+"("+x.__$+")"+"\\"")())();'
59+
>>> codext.decode(js, "jjencode")
60+
'alert(1)'
61+
```
62+
63+
-----
64+
2265
### URL
2366

2467
This handles URL encoding, regardless of the case when decoding and with no error.

‎src/codext/web/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: UTF-8 -*-
2+
from .aaencode import *
23
from .html import *
4+
from .jjencode import *
35
from .url import *
46

‎src/codext/web/_javascript.py‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# -*- coding: UTF-8 -*-
2+
"""Helpers for the codecs generating JavaScript (aaencode, jjencode).
3+
4+
"""
5+
import re
6+
7+
8+
__all__ = ["js_unescape", "utf16_units"]
9+
10+
11+
ESCAPES = {'b': "\b", 'f': "\f", 'n': "\n", 'r': "\r", 't': "\t", 'v': "\v"}
12+
13+
14+
def js_unescape(s, err):
15+
""" Evaluate the body of a JavaScript double-quoted string literal (in sloppy mode, thus with legacy octal escapes),
16+
calling err with a message on a syntax error. UTF-16 surrogate pairs are combined into characters. """
17+
r, i, n = [], 0, len(s)
18+
while i < n:
19+
c = s[i]
20+
if c != "\\":
21+
if c in "\"\r\n":
22+
err(f"unescaped {c!r} in string literal")
23+
r.append(c)
24+
i += 1
25+
continue
26+
if i + 1 >= n:
27+
err("unterminated escape sequence in string literal")
28+
e, i = s[i+1], i + 2
29+
if e in ESCAPES:
30+
r.append(ESCAPES[e])
31+
elif e in "\r\n

":
32+
# line continuation
33+
i += e == "\r" and s.startswith("\n", i)
34+
elif e in "01234567":
35+
# legacy octal escape: up to \377
36+
m = re.match(r"[0-7]{0,2}" if e in "0123" else r"[0-7]?", s[i:])
37+
r.append(chr(int(e + m.group(), 8)))
38+
i += m.end()
39+
elif e == "x":
40+
if not re.fullmatch(r"[0-9a-fA-F]{2}", s[i:i+2]):
41+
err("invalid hexadecimal escape sequence")
42+
r.append(chr(int(s[i:i+2], 16)))
43+
i += 2
44+
elif e == "u":
45+
if (m := re.match(r"\{([0-9a-fA-F]+)\}|[0-9a-fA-F]{4}", s[i:])) is None or \
46+
int(m.group(1) or m.group(), 16) > 0x10ffff:
47+
err("invalid Unicode escape sequence")
48+
r.append(chr(int(m.group(1) or m.group(), 16)))
49+
i += m.end()
50+
else:
51+
# including "8" and "9"
52+
r.append(e)
53+
return "".join(r).encode("utf-16-le", "surrogatepass").decode("utf-16-le", "surrogatepass")
54+
55+
56+
def utf16_units(text):
57+
""" Get the UTF-16 code units of a string, as with JavaScript's charCodeAt. """
58+
b = text.encode("utf-16-le", "surrogatepass")
59+
return [int.from_bytes(b[i:i+2], "little") for i in range(0, len(b), 2)]

‎src/codext/web/aaencode.py‎

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# -*- coding: UTF-8 -*-
2+
"""AAEncode Codec - aaencode content encoding.
3+
4+
AAEncode, created by Yosuke Hasegawa, converts JavaScript code into a JavaScript program made of Japanese-style
5+
emoticons ("kaomoji", e.g. "゚ω゚ノ" or "(゚Д゚)") that runs the code. The program builds the digits and the letters it
6+
needs from the string representations of JavaScript values (e.g. "false" or "undefined"), gets the Function constructor
7+
through "(3).constructor.constructor" and calls it on a string literal holding the code, each character being written
8+
as an octal ("\\NNN") or Unicode ("\\uXXXX") escape sequence of its UTF-16 code unit.
9+
10+
Encoding gives the same program as the reference encoder. Decoding parses the program (without running it), whatever its
11+
whitespaces, and returns the code. Malformed programs are reported whatever the errors handling.
12+
13+
This codec:
14+
- en/decodes strings from str to str
15+
- en/decodes strings from bytes to bytes
16+
- decodes file content to str (read)
17+
- encodes file content from str to bytes (write)
18+
19+
Reference: https://utf-8.jp/public/aaencode.html
20+
"""
21+
from ._javascript import *
22+
from ..__common__ import *
23+
24+
25+
__examples__ = {
26+
'enc(aaencode|aa_encode)': {
27+
'alert(1)': "゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o"
28+
"^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚="
29+
"=3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚"
30+
"Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +"
31+
"'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д"
32+
"゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] ["
33+
"゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -"
34+
"゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚"
35+
"ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\\\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д"
36+
"゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚"
37+
")+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(゚Θ"
38+
"゚)+ ((o^_^o) +(o^_^o))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+"
39+
" (゚Θ゚)+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');",
40+
},
41+
'enc-dec(aaencode)': ["", "alert(\"Hello, World!\")", "@random{256}", "caf\xe9 \u20ac \U0001f600",
42+
"\x00\x01\n\\\"'"],
43+
'dec(aaencode)': {'': None, 'alert(1)': None},
44+
}
45+
46+
47+
DIGITS = ["(c^_^o)", "(゚Θ゚)", "((o^_^o) - (゚Θ゚))", "(o^_^o)", "(゚ー゚)", "((゚ー゚) + (゚Θ゚))", "((o^_^o) +(o^_^o))",
48+
"((゚ー゚) + (o^_^o))", "((゚ー゚) + (゚ー゚))", "((゚ー゚) + (゚ー゚) + (゚Θ゚))", "(゚Д゚) .゚ω゚ノ", "(゚Д゚) .゚Θ゚ノ",
49+
"(゚Д゚) ['c']", "(゚Д゚) .゚ー゚ノ", "(゚Д゚) .゚Д゚ノ", "(゚Д゚) [゚Θ゚]"]
50+
HEADER = "゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); "
51+
# easter egg of the reference encoder
52+
EASTER_EGG = (re.compile(r"ひだまりスケッチ×(365|356)\s*来週も見てくださいね[!!]"),
53+
"X=_=3; \r\n\r\n X / _ / X < \"来週も見てくださいね!\";\r\n\r\n")
54+
PROLOGUE = "(゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')" \
55+
"[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = " \
56+
"((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+" \
57+
"(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+" \
58+
"((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+" \
59+
"((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+" \
60+
"((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; " \
61+
"(゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];" \
62+
"(゚Д゚) [゚o゚]='\\\"';"
63+
# Function(Function("return\"" + <escaped code> + "\"")(1))("_")
64+
START = "(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ "
65+
BACKSLASH, QUOTE, U = "(゚Д゚)[゚ε゚]+", "(゚Д゚)[゚o゚]", "(o゚ー゚o)+ "
66+
EPILOGUE = QUOTE + ") (゚Θ゚)) ('_');"
67+
# for decoding, whitespaces are removed from the program and from its tokens
68+
_ws = lambda s: re.sub(r"\s+", "", s)
69+
_START, _END = _ws(START), _ws(EPILOGUE).rstrip(";")
70+
_TOKENS = {_ws(BACKSLASH): "\\", _ws(U): "u", **{_ws(d) + "+": "0123456789abcdef"[i] for i, d in enumerate(DIGITS)}}
71+
_TOKEN = re.compile("|".join(map(re.escape, sorted(_TOKENS, key=len, reverse=True))))
72+
73+
74+
def _err(msg):
75+
raise AaencodeDecodeError(f"'aaencode' codec can't decode: {msg}")
76+
77+
78+
def aaencode_encode(text, errors="strict"):
79+
t = ensure_str(text)
80+
r = HEADER + [EASTER_EGG[1], ""][EASTER_EGG[0].search(t) is None] + PROLOGUE + START
81+
for n in utf16_units(t):
82+
r += BACKSLASH + ("".join(DIGITS[int(c)] + "+ " for c in f"{n:o}") if n <= 127 else
83+
U + "".join(DIGITS[int(c, 16)] + "+ " for c in f"{n:04x}"))
84+
return r + EPILOGUE, len(text)
85+
86+
87+
def aaencode_decode(text, errors="strict"):
88+
p = _ws(ensure_str(text))
89+
if (i := p.find(_START)) < 0:
90+
_err("payload not found")
91+
p = p[i+len(_START):].rstrip(";")
92+
if not p.endswith(_END):
93+
_err("end of payload not found")
94+
p, body, i = p[:-len(_END)], "", 0
95+
# the payload is the source of a string literal, made of backslashes, "u"'s and hexadecimal digits
96+
while i < len(p):
97+
if (m := _TOKEN.match(p, i)) is None:
98+
_err(f"unexpected token at position {i} of the payload")
99+
body, i = body + _TOKENS[m.group()], m.end()
100+
return (r := js_unescape(body, _err)), len(r)
101+
102+
103+
add("aaencode", aaencode_encode, aaencode_decode, r"^aa[-_]?encode$", expansion_factor=None)

0 commit comments

Comments
 (0)