Skip to content

Commit a9494f7

Browse files
committed
feat(server): added more CLI flags. Fixed bug with ASCII85/'<' in SVG
1 parent 599b2bd commit a9494f7

7 files changed

Lines changed: 89 additions & 60 deletions

File tree

‎python/self_unzip_html/cli/__init__.py‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from enum import Enum
33

44
class Subcommand(Enum):
5+
SERVE = "serve"
56
SVG = "svg"
67
HTML = "html"
78
SVG_ENCRYPTED = "encrypted-svg"
@@ -27,18 +28,16 @@ def main_wrapped() -> None:
2728
(Subcommand.HTML_ENCRYPTED, "Create an encrypted HTML smuggling page"),
2829
(Subcommand.SVG, "Create an unencrypted SVG with HTML smuggling code"),
2930
(Subcommand.SVG_ENCRYPTED, "Create an encrypted SVG with HTML smuggling code"),
31+
(Subcommand.SERVE, "Start a HTTP server that dynamically creates HTML smuggling pages for files in the current directory")
3032
]:
3133
ap_subcommand = subparsers.add_parser(subcommand.value, description=description, help=description)
3234
ap_subcommand.add_argument("-q", "--quiet", action="store_true", help="minimize console output")
3335

36+
register_server_argument_parser(ap_subcommand, subcommand)
3437
register_output_argument_parser(ap_subcommand, subcommand)
3538
register_action_argument_parser(ap_subcommand, subcommand)
3639
register_encryption_argument_parser(ap_subcommand, subcommand)
3740
register_template_argument_parser(ap_subcommand, subcommand)
38-
39-
ap_serve = subparsers.add_parser("serve", description="start HTTP server", help="start HTTP server")
40-
ap_serve.add_argument("-q", "--quiet", action="store_true", help="minimize console output")
41-
register_server_argument_parser(ap_serve)
4241

4342
args = ap.parse_args()
4443

@@ -47,12 +46,10 @@ def main_wrapped() -> None:
4746
PRINT_INFO_MESSAGES = True
4847

4948
if args.encoder == "serve":
50-
main_serve(args)
49+
start_server(args.bind, args.port, args)
5150
else:
5251
main_encode(args)
5352

54-
def main_serve(args):
55-
start_server(args.bind, args.port)
5653

5754
def main_encode(args):
5855
input_data, file_name = read_input_file(args)
@@ -61,7 +58,7 @@ def main_encode(args):
6158
get_javascript(args, file_name, is_svg(args)),
6259
get_encryptor(args),
6360
obscure_action=args.obscure_action,
64-
encode_library_as_base64=is_svg(args),
61+
encode_library_as_base64=False,
6562
insert_debug_statements=args.console_log,
6663
compression_list=get_compression_list(args),
6764
encoding_list=get_encoding_list(args)

‎python/self_unzip_html/cli/action.py‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,21 @@
66
from ..util import OperationNotImplemented
77

88
def register_action_argument_parser(ap: ArgumentParser, subcommand: Subcommand):
9-
payload_option_visual_group = ap.add_argument_group("Payload Action")
10-
payload_option_mutex = payload_option_visual_group.add_mutually_exclusive_group(required=True)
11-
payload_option_mutex.add_argument("--download", nargs="?", metavar="FILE_NAME", const="", help="show a download link to download the payload as a file. If you specify an argument that is used as the name of the file to download")
12-
payload_option_mutex.add_argument("--eval", action="store_true", help="pass the payload to eval() to run it as JavaScript code")
13-
if subcommand not in [Subcommand.SVG, Subcommand.SVG_ENCRYPTED]:
14-
# Setting the innerHTML of a svg.text always resulted in errors. So we do not show this option with SVGs
15-
payload_option_mutex.add_argument("--replace", action="store_true", help="replace the page's content with the payload. Use this to compress HTML pages")
16-
17-
payload_option_mutex.add_argument("--show-text", action="store_true", help="use this to show plain text. Unlike --replace this does not interpret HTML tags and does not change whitespace")
18-
payload_option_mutex.add_argument("--driveby-redirect", metavar="REDIRECT_URL", help="downlaod the payload as a file in the background and immediately redirect the user to another site. Useful for phishing")
19-
payload_option_mutex.add_argument("--custom", metavar="YOUR_JAVASCRIPT_CODE", help="run your own action. Provide a JavaScript snippet that uses the decoded payload, which is stored in the 'og_data' variable. Note that data is a byte array, so you likely want to use 'new TextDecoder().decode(og_data)' to convert it to Unicode")
20-
payload_option_visual_group.add_argument("--obscure-action", action="store_true", help="obscures the action JavaScript code")
9+
if subcommand != Subcommand.SERVE: #@TODO: figure out how to do it later
10+
payload_option_visual_group = ap.add_argument_group("Payload Action")
11+
payload_option_mutex = payload_option_visual_group.add_mutually_exclusive_group(required=True)
12+
payload_option_mutex.add_argument("--download", nargs="?", metavar="FILE_NAME", const="", help="show a download link to download the payload as a file. If you specify an argument that is used as the name of the file to download")
13+
payload_option_mutex.add_argument("--eval", action="store_true", help="pass the payload to eval() to run it as JavaScript code")
14+
if subcommand not in [Subcommand.SVG, Subcommand.SVG_ENCRYPTED]:
15+
# Setting the innerHTML of a svg.text always resulted in errors. So we do not show this option with SVGs
16+
payload_option_mutex.add_argument("--replace", action="store_true", help="replace the page's content with the payload. Use this to compress HTML pages")
17+
18+
payload_option_mutex.add_argument("--show-text", action="store_true", help="use this to show plain text. Unlike --replace this does not interpret HTML tags and does not change whitespace")
19+
payload_option_mutex.add_argument("--driveby-redirect", metavar="REDIRECT_URL", help="downlaod the payload as a file in the background and immediately redirect the user to another site. Useful for phishing")
20+
payload_option_mutex.add_argument("--custom", metavar="YOUR_JAVASCRIPT_CODE", help="run your own action. Provide a JavaScript snippet that uses the decoded payload, which is stored in the 'og_data' variable. Note that data is a byte array, so you likely want to use 'new TextDecoder().decode(og_data)' to convert it to Unicode")
21+
payload_option_visual_group.add_argument("--obscure-action", action="store_true", help="obscures the action JavaScript code")
22+
else:
23+
ap.add_argument("--obscure-action", action="store_true", help="obscures the action JavaScript code")
2124

2225

2326
def get_javascript(args: Any, file_name: str, is_svg: bool) -> str:

‎python/self_unzip_html/cli/encryption.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
from ..crypto import BaseEncryptor, NullEncryptor
55

66
def register_encryption_argument_parser(ap: ArgumentParser, subcommand: Subcommand) -> None:
7-
if subcommand in [Subcommand.HTML_ENCRYPTED, Subcommand.SVG_ENCRYPTED]:
7+
if subcommand in [Subcommand.HTML_ENCRYPTED, Subcommand.SVG_ENCRYPTED, Subcommand.SERVE]:
8+
require_password = subcommand != Subcommand.SERVE
89
# Only show encryption options when an encrypted subcommand is used
910
ap_encryption = ap.add_argument_group("Encryption Options")
10-
ap_encryption.add_argument("-p", "--password", required=True, help="encrypt the compressed data using this password")
11+
ap_encryption.add_argument("-p", "--password", required=require_password, help="encrypt the compressed data using this password")
1112
ap_encryption.add_argument("-P", "--password-prompt", default="Please enter the decryption password", help="provide your custom password prompt, that can for example be used to provide a password hint")
1213
ap_encryption.add_argument("-C", "--cache-password", action="store_true", help="cache password to localStorage, so that you can reload the page without entering password again")
1314
ap_encryption.add_argument("--iterations", "-I", type=int, default=1_000_000, help="minimum number of iterations for the PBKDF2 key derivation function")

‎python/self_unzip_html/cli/output.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
from ..page_builder import Compression, Encoding
77
from ..util import print_info
88

9-
def register_output_argument_parser(ap: ArgumentParser, _subcommand: Subcommand):
10-
ap.add_argument("-i", "--input", metavar="INPUT_FILE", required=True, help="the file to encode. Use '-' to read from standard input")
9+
def register_output_argument_parser(ap: ArgumentParser, subcommand: Subcommand):
10+
if subcommand != Subcommand.SERVE:
11+
ap.add_argument("-i", "--input", metavar="INPUT_FILE", required=True, help="the file to encode. Use '-' to read from standard input")
1112

1213
ap_output = ap.add_argument_group("Output Options")
13-
ap_output.add_argument("-o", "--output", help="the location to write the output to. If not specified stdout will be used instead")
14-
ap_output.add_argument("-O", "--open", action="store_true", help="if writing output to a file, try to immediately open the file in the default web browser afterwards")
14+
if subcommand != Subcommand.SERVE:
15+
ap_output.add_argument("-o", "--output", help="the location to write the output to. If not specified stdout will be used instead")
16+
ap_output.add_argument("-O", "--open", action="store_true", help="if writing output to a file, try to immediately open the file in the default web browser afterwards")
17+
1518
ap_output.add_argument("-c", "--compression", default="auto", choices=["auto", "none", "gzip"], help="how to compress the contents (default: auto)")
1619
ap_output.add_argument("-e", "--encoding", default="auto", choices=["auto", "base64", "ascii85", "hex"], help="how to encode the binary data (default: auto). base64 may not work for large contents (>65kB) due to different browser limitations")
1720
ap_output.add_argument("--console-log", action="store_true", help="insert debug statements to see the output of the individual steps")

‎python/self_unzip_html/cli/server.py‎

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,47 @@
66
from urllib.parse import unquote
77
from http.server import HTTPServer, BaseHTTPRequestHandler
88
# local
9-
from ..template import get_html_template, get_svg_template
10-
from ..crypto import NullEncryptor
11-
from ..page_builder import PageBuilder, Compression, Encoding, DEFAULT_TEMPLATE_FILE, DEFAULT_SVG_FILE
9+
from . import Subcommand
10+
from .template import get_initial_page_contents
11+
from .encryption import get_encryptor
12+
from .output import get_compression_list, get_encoding_list
13+
from ..template import get_html_template, get_svg_template, DEFAULT_HTML_TEMPLATE_PATH
14+
from ..page_builder import PageBuilder
1215
from ..static_js import JS_DOWNLOAD, JS_DOWNLOAD_SVG
1316

1417

15-
def register_server_argument_parser(ap: ArgumentParser):
16-
ap.add_argument("-b", "--bind", default="0.0.0.0", help="IP address to bind to (default: 0.0.0.0)")
17-
ap.add_argument("-p", "--port", type=int, default=8000, help="port to bind to (default: 8000)")
18+
def register_server_argument_parser(ap: ArgumentParser, subcommand: Subcommand):
19+
if subcommand == Subcommand.SERVE:
20+
ap_server = ap.add_argument_group("Server options")
21+
ap_server.add_argument("-b", "--bind", default="0.0.0.0", help="IP address to bind to (default: 0.0.0.0)")
22+
ap_server.add_argument("port", nargs="?", type=int, default=8000, help="port to bind to (default: 8000)")
1823

1924
class HTMLSmugglingServer(HTTPServer):
20-
def __init__(self, server_address, RequestHandlerClass):
25+
def __init__(self, server_address, RequestHandlerClass, args):
2126
super().__init__(server_address, RequestHandlerClass)
22-
self.html_template = get_html_template(DEFAULT_TEMPLATE_FILE, "File Download", "File download link should be shown immediately")
23-
self.svg_template = get_svg_template(DEFAULT_SVG_FILE)
24-
self.encryptor = NullEncryptor()
27+
28+
try:
29+
self.svg_template = get_svg_template(args.svg)
30+
except:
31+
raise Exception(f"Failed to load SVG file '{args.svg}'. Try specifying a different file with the --svg option")
32+
33+
template_file = args.template or DEFAULT_HTML_TEMPLATE_PATH
34+
initial_page_contents = get_initial_page_contents(args)
35+
try:
36+
self.html_template = get_html_template(template_file, args.title, initial_page_contents)
37+
except:
38+
raise Exception(f"Failed to load template file '{template_file}'. Try specifying a different file with the --template option")
39+
40+
self.compression_list = get_compression_list(args)
41+
self.encoding_list = get_encoding_list(args)
42+
self.obscure_action = args.obscure_action
43+
self.insert_debug_statements = args.console_log
44+
45+
self.encryptor = get_encryptor(args)
46+
47+
# self.html_template = get_html_template(DEFAULT_TEMPLATE_FILE, "File Download", "File download link should be shown immediately")
48+
# self.svg_template = get_svg_template(DEFAULT_SVG_FILE)
49+
# self.encryptor = NullEncryptor()
2550

2651

2752
class HTMLSmugglingRequestHandler(BaseHTTPRequestHandler):
@@ -60,38 +85,38 @@ def serve_html(self, path):
6085
with open(path, "rb") as f:
6186
file_contents = f.read()
6287

63-
html_page_builder = PageBuilder(
64-
self.server.html_template,
65-
JS_DOWNLOAD.replace("{{NAME}}", file_name),
66-
self.server.encryptor,
67-
compression_list = [Compression.NONE],
68-
encoding_list = [Encoding.BASE64],
69-
)
70-
html_str = html_page_builder.build_page(file_contents)
88+
file_contents = self.build_page(file_name, file_contents, self.server.html_template, JS_DOWNLOAD, False)
7189

7290
self.send_response(200)
7391
self.send_header("Content-type", "text/html; charset=utf-8")
7492
self.end_headers()
75-
self.wfile.write(html_str.encode())
93+
self.wfile.write(file_contents)
7694

7795
def serve_svg(self, path):
7896
file_name = os.path.basename(path)
7997
with open(path, "rb") as f:
8098
file_contents = f.read()
8199

100+
file_contents = self.build_page(file_name, file_contents, self.server.svg_template, JS_DOWNLOAD_SVG, True)
101+
102+
self.send_response(200)
103+
self.send_header("Content-type", "image/svg+xml")
104+
self.end_headers()
105+
self.wfile.write(file_contents)
106+
107+
def build_page(self, file_name: str, file_contents: bytes, template: str, js_payload: str, is_svg: bool) -> bytes:
82108
html_page_builder = PageBuilder(
83-
self.server.svg_template,
84-
JS_DOWNLOAD_SVG.replace("{{NAME}}", file_name),
109+
template,
110+
js_payload.replace("{{NAME}}", file_name),
85111
self.server.encryptor,
86-
compression_list = [Compression.NONE],
87-
encoding_list = [Encoding.BASE64],
112+
obscure_action=self.server.obscure_action,
113+
encode_library_as_base64=False,
114+
insert_debug_statements=self.server.insert_debug_statements,
115+
compression_list = self.server.compression_list,
116+
encoding_list = self.server.encoding_list,
88117
)
89118
html_str = html_page_builder.build_page(file_contents)
90-
91-
self.send_response(200)
92-
self.send_header("Content-type", "text/html; charset=utf-8")
93-
self.end_headers()
94-
self.wfile.write(html_str.encode())
119+
return html_str.encode()
95120

96121
def list_directory(self, path):
97122
try:
@@ -133,10 +158,10 @@ def translate_path(self, path):
133158
return base_path
134159

135160

136-
def start_server(bind_ip: str, bind_port: int):
161+
def start_server(bind_ip: str, bind_port: int, args):
137162
try:
138163
server_address = (bind_ip, bind_port)
139-
httpd = HTMLSmugglingServer(server_address, HTMLSmugglingRequestHandler)
164+
httpd = HTMLSmugglingServer(server_address, HTMLSmugglingRequestHandler, args)
140165
print(f"Serving at http://{bind_ip}:{bind_port}")
141166
httpd.serve_forever()
142167
except KeyboardInterrupt:

‎python/self_unzip_html/cli/template.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
def register_template_argument_parser(ap: ArgumentParser, subcommand: Subcommand):
88
ap_template = ap.add_argument_group("Template Settings")
9-
if subcommand in [Subcommand.SVG, Subcommand.SVG_ENCRYPTED]:
9+
if subcommand in [Subcommand.SVG, Subcommand.SVG_ENCRYPTED, Subcommand.SERVE]:
1010
ap_template.add_argument("--svg", metavar="SVG_FILE_PATH", nargs="?", default=DEFAULT_SVG_TEMPLATE_PATH, help="use this SVG instead of a normal HTML page for the smuggling")
11-
else:
11+
if subcommand in [Subcommand.HTML, Subcommand.HTML_ENCRYPTED, Subcommand.SERVE]:
1212
ap_template.add_argument("--template", help="use this template file instead of the default one")
1313
ap_template.add_argument("--title", default="Self Extracting Page", help="set the title of the HTML page")
1414
initial_page_contents_mutex = ap_template.add_mutually_exclusive_group()

‎python/self_unzip_html/template.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import os
22

33
SCRIPT_TAG_TEMPLATE_FOR_SVG_FILES = """
4-
<script>
4+
<script><![CDATA[
55
{{LIBRARY_CODE}}
66
// My code (c) six-two, MIT License
77
const action = (og_data) => { {{PAYLOAD_CODE}} };
88
const c_data = "{{DATA}}";
99
{{GLUE_CODE}}
10-
</script>
10+
]]></script>
1111
"""
1212

1313
SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))

0 commit comments

Comments
 (0)