refactor: make web work again (split project <-> settings)

This commit is contained in:
Dobin Rutishauser
2025-06-18 21:24:35 +02:00
parent fcb40ccb6a
commit 6864656381
20 changed files with 214 additions and 349 deletions
+20 -22
View File
@@ -1,52 +1,50 @@
import pickle
import os
import yaml
import pickle
import logging
from typing import List, Tuple
from model.defs import *
from model.project import WebProject
from model.project import Settings
logger = logging.getLogger("Storage")
class Storage():
def __init__(self):
pass
def get_projects(self) -> List[WebProject]:
projects: List[WebProject] = []
def get_project_settings(self) -> List[Settings]:
project_settings: List[Settings] = []
for project_name in os.listdir(PATH_WEB_PROJECT):
project = self.get_project(project_name)
if project is None:
project_setting = self.get_project_setting(project_name)
if project_setting is None:
continue
projects.append(project)
return projects
project_settings.append(project_setting)
return project_settings
def get_project(self, project_name: str) -> WebProject:
logger.debug("Load project: {}".format(project_name))
def get_project_setting(self, project_name: str) -> Settings| None:
path = "{}/{}".format(PATH_WEB_PROJECT, project_name)
json_path = "{}/project.pickle".format(path)
if not os.path.exists(json_path):
return None
logger.info("Loading project from: {}".format(json_path))
with open(json_path, "rb") as f:
project = pickle.load(f)
return project
settings = pickle.load(f)
return settings
def add_project(self, project: WebProject):
# directories and contents
os.makedirs(PATH_WEB_PROJECT + project.name, exist_ok=True)
with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, project.name), "wb") as f:
pickle.dump(project, f)
def add_project_setting(self, settings: Settings):
os.makedirs(PATH_WEB_PROJECT + settings.project_name, exist_ok=True)
with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, settings.project_name), "wb") as f:
pickle.dump(settings, f)
def save_project(self, project: WebProject):
with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, project.name), "wb") as f:
pickle.dump(project, f)
def save_project_settings(self, settings: Settings):
with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, settings.project_name), "wb") as f:
pickle.dump(settings, f)
storage = Storage()
storage = Storage()
-31
View File
@@ -1,31 +0,0 @@
<!DOCTYPE html>
<html>
<head>
{% include 'header.html' %}
</head>
<body>
{% include 'navigation.html' %}
<div class="indent">
<h1> ShcDev: {{name}}</h1>
<table class="table">
{% for file in files %}
<tr>
<td><a href="/shcdev/{{name}}/file/{file['name']}">{{ file['name']}}</a></td>
<td>{{file["date"]}}</td>
<td>{{file["info"]}}</td>
</tr>
{% endfor %}
</table>
<a href="/shcdev/{{name}}/build">Build</a>
<br><hr>
<pre>{{log}}</pre>
</div>
</body>
</html>
-23
View File
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
{% include 'header.html' %}
</head>
<body>
{% include 'navigation.html' %}
<div class="indent">
<h1> ShcDevs: </h1>
<ul>
{% for item in data %}
<li><a href="/shcdev/{{item['name']}}">{{ item['name'] }}</a>
({{item["date"]}})
</li>
{% endfor %}
</ul>
</div>
</body>
</html>
+3 -10
View File
@@ -18,24 +18,17 @@
<a class="nav-link {{ 'active' if request.path == '/' else '' }}"
href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/projects' else '' }}"
href="/projects">Projects</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/shellcodes' else '' }}"
href="/shellcodes">Shellcodes</a>
<a class="nav-link {{ 'active' if request.path == '/exes' else '' }}"
href="/exes">EXEs</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/exes' else '' }}"
href="/exes">EXE's</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/shcdev' else '' }}"
href="/shcdev">ShcDev</a>
href="/shellcodes">Shellcodes</a>
</li>
</ul>
</div>
+19 -19
View File
@@ -45,7 +45,7 @@
onchange="this.form.submit()" readonly>
<input type="text" name="comment" class="hidden form-control"
placeholder="" value="{{project.comment}}"
placeholder="" value="{{project_comment}}"
aria-label="PROJECTNAME" aria-describedby="basic-addon1"
onchange="this.form.submit()">
@@ -63,7 +63,7 @@
aria-label="SHELLCODE" onchange="this.form.submit()">
{% for shellcode in shellcodes %}
<option value="{{shellcode['filename']}}"
{% if shellcode["filename"] in project.settings.payload_path %} selected {% endif %}
{% if shellcode["filename"] == settings.payload_base %} selected {% endif %}
>
{{shellcode['filename']}} ({{shellcode['size']}})
</option>
@@ -71,7 +71,7 @@
</select>
</div>
</div>
<!-- Input: EXE File -->
<div class="form-group row">
<label for="exe" class="col-sm-3 col-form-label"
@@ -85,7 +85,7 @@
aria-label="EXE" onchange="this.form.submit()">
{% for exe in exes %}
<option value="{{exe['filename']}}"
{% if exe['filename'] == project.settings.inject_exe_in %} selected {% endif %}
{% if exe['filename'] == settings.injectable_base %} selected {% endif %}
>
{{exe['filename'] | basename}} ({{exe['size']}})</option>
{% endfor %}
@@ -98,7 +98,7 @@
<select class="form-select" name="dllfunc" aria-label="DLLFUNC" onchange="this.form.submit()">
{% for export in exports %}
<option value="{{export['name']}}"
{% if export["name"] == project.settings.dllfunc %} selected {% endif %}
{% if export["name"] == settings.dllfunc %} selected {% endif %}
>
{{export['name']}} ({{export['size']}})</option>
{% endfor %}
@@ -108,7 +108,7 @@
<!-- Row 3: exe and shellcode info -->
<div class="col-2">
<a href="/exes/{{project.settings.inject_exe_in | basename}}">EXE Info:</a>
<a href="/exes/{{settings.get_inject_exe_in() | basename}}">EXE Info:</a>
<ul>
<li>
{% if is_64 %}
@@ -154,11 +154,11 @@
<div class="col-3">
<div class="form-group row">
<label for="carrier_name" class="col-sm-5 col-form-label
{% if 'rwx' in project.settings.carrier_name %}
{% if 'rwx' in settings.carrier_name %}
opsec_bad
{% elif 'peb_walk' in project.settings.carrier_name %}
{% elif 'peb_walk' in settings.carrier_name %}
opsec_bad
{% elif 'change' in project.settings.carrier_name %}
{% elif 'change' in settings.carrier_name %}
opsec_neutral
{% else %}
opsec_good
@@ -172,7 +172,7 @@
aria-label="CARRIERNAME" onchange="this.form.submit()">
{% for name in carrier_names %}
<option value="{{name}}"
{% if name in project.settings.carrier_name %} selected {% endif %}
{% if name in settings.carrier_name %} selected {% endif %}
>{{name}}</option>
{% endfor %}
</select>
@@ -188,7 +188,7 @@
aria-label="INJECTSTYLE" onchange="this.form.submit()">
{% for name, value in carrier_invoke_styles %}
<option value="{{name}}"
{% if value in project.settings.carrier_invoke_style.value %} selected {% endif %}
{% if value in settings.carrier_invoke_style.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
@@ -204,7 +204,7 @@
aria-label="PAYLOADLOCATION" onchange="this.form.submit()">
{% for name, value in payload_locations %}
<option value="{{name}}"
{% if value in project.settings.payload_location.value %} selected {% endif %}
{% if value in settings.payload_location.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
@@ -231,7 +231,7 @@
aria-label="DECODERESTYLE" onchange="this.form.submit()">
{% for name in decoder_styles %}
<option value="{{name}}"
{% if name in project.settings.decoder_style %} selected {% endif %}
{% if name in settings.decoder_style %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
@@ -248,7 +248,7 @@
aria-label="GUARDRAILSTYLE" onchange="this.form.submit()">
{% for name in guardrailstyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_guardrail %} selected {% endif %}
{% if name in settings.plugin_guardrail %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
@@ -256,14 +256,14 @@
</div>
</div>
{% if project.settings.plugin_guardrail != "none" %}
{% if settings.plugin_guardrail != "none" %}
<div class="form-group row">
<label for="guardrail" class="col-sm-5 col-form-label">
Guard Data
</label>
<div class="col-sm-7">
<input type="text" name="guardrail_data" class="hidden form-control"
placeholder="" value="{{project.settings.plugin_guardrail_data}}"
placeholder="" value="{{settings.plugin_guardrail_data}}"
aria-label="guardrail_data" aria-describedby="basic-addon1"
onchange="this.form.submit()">
</div>
@@ -279,7 +279,7 @@
aria-label="antiemulation" onchange="this.form.submit()">
{% for name in antiemulationstyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_antiemulation %} selected {% endif %}
{% if name in settings.plugin_antiemulation %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
@@ -296,7 +296,7 @@
aria-label="decoy" onchange="this.form.submit()">
{% for name in decoystyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_decoy %} selected {% endif %}
{% if name in settings.plugin_decoy %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
@@ -313,7 +313,7 @@
aria-label="virtualprotect" onchange="this.form.submit()">
{% for name in virtualprotectstyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_virtualprotect %} selected {% endif %}
{% if name in settings.plugin_virtualprotect %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
+2 -2
View File
@@ -11,8 +11,8 @@
<h1> Projects </h1>
<ul>
{% for project in projects %}
<li><a href="/project/{{project.name}}">{{ project.name }}</a></li>
{% for settings in project_settings %}
<li><a href="/project/{{settings.project_name}}">{{ settings.project_name }}</a></li>
{% endfor %}
</ul>
+1
View File
@@ -15,6 +15,7 @@ logger = logging.getLogger("Views")
@views.route("/")
def index():
return render_template('index.html')
return redirect("/project/default", code=302)
@views.route("/exes/<exe_name>")
+66 -53
View File
@@ -15,7 +15,8 @@ from config import config
from model.settings import Settings
from model.defs import *
from supermega import start
from app.storage import storage, WebProject
from app.storage import storage
from model.project import Project
from sender import scannerDetectsBytes
from phases.injector import verify_injected_exe
from phases.templater import get_template_names
@@ -36,19 +37,21 @@ thread_running = False
@views_project.route("/projects")
def projects_route():
projects = storage.get_projects()
return render_template('projects.html', projects=projects)
projects_settings = storage.get_project_settings()
return render_template('projects.html', projects_settings=projects_settings)
@views_project.route("/project/<name>")
def project(name):
project = storage.get_project(name)
if project == None:
project_setting = storage.get_project_setting(name)
if project_setting == None:
logger.error("Project {} not found".format(name))
return redirect("/projects", code=302)
exe_path = project.settings.inject_exe_out
project_setting.print()
is_built = False
if os.path.exists(exe_path):
if os.path.exists(project_setting.project_exe_path):
is_built = True
exports = []
@@ -65,16 +68,16 @@ def project(name):
if config.get("avred_server") != "":
has_remote = True
# when we select a shellcode
if project.settings.payload_path != "":
payload_len = os.path.getsize(project.settings.payload_path)
# payload / shellcode
if project_setting.get_payload_path() != None:
payload_len = os.path.getsize(project_setting.get_payload_path())
# when we selected an input file
if project.settings.inject_exe_in != "" and os.path.exists(project.settings.inject_exe_in):
superpe = SuperPe(project.settings.inject_exe_in)
# injectable / exe
if project_setting.get_inject_exe_in() != None and os.path.exists(project_setting.get_inject_exe_in()):
superpe = SuperPe(project_setting.get_inject_exe_in())
#if not superpe.is_64():
# # return 500
# return "Error: Binary {} is not 64bit".format(project.settings.inject_exe_in), 500
# return "Error: Binary {} is not 64bit".format(project.settings.get_inject_exe_in()), 500
is_64 = superpe.is_64()
is_dotnet = superpe.is_dotnet()
@@ -85,17 +88,17 @@ def project(name):
if rdata_section != None:
data_sect_size = rdata_section.virt_size
else:
logger.warning("No .rdata section found in {}".format(project.settings.inject_exe_in))
logger.warning("No .rdata section found in {}".format(project_setting.get_inject_exe_in()))
has_rodata_section = superpe.has_rodata_section()
if has_rodata_section:
data_sect_largest_gap_size = superpe.get_rdata_rangemanager().find_largest_gap()
unresolved_dlls = pe.dllresolver.unresolved_dlls(superpe)
project_dir = os.path.dirname(os.getcwd() + "\\" + project.settings.main_dir)
log_files = get_logfiles(project.settings.main_dir)
exes = list_files_and_sizes(PATH_EXES, prepend=PATH_EXES)
exes += list_files_and_sizes(PATH_EXES_MORE, prepend=PATH_EXES_MORE)
project_dir = os.path.dirname(os.getcwd() + "\\" + project_setting.project_path)
log_files = get_logfiles(project_setting.project_path)
exes = list_files_and_sizes(PATH_EXES)
#exes += list_files_and_sizes(PATH_EXES_MORE, prepend=PATH_EXES_MORE)
shellcodes = list_files_and_sizes(PATH_SHELLCODES)
carrier_names = get_template_names()
@@ -110,9 +113,10 @@ def project(name):
return render_template('project.html',
project_name = name,
project=project,
project_comment = project_setting.project_comment,
is_built=is_built,
project_dir=project_dir,
settings=project_setting,
exes=exes,
shellcodes=shellcodes,
@@ -133,7 +137,7 @@ def project(name):
has_rodata_section=has_rodata_section,
has_remote=has_remote,
fix_missing_iat=project.settings.fix_missing_iat,
fix_missing_iat=project_setting.fix_missing_iat,
guardrailstyles = guardrail_styles,
antiemulationstyles = antiemulation_styles,
@@ -170,18 +174,17 @@ def list_files(directory, prepend="") -> List[str]:
def add_project():
if request.method == 'POST':
project_name = request.form['project_name']
settings = Settings(project_name)
comment = request.form['comment']
# Empty settings, except name
settings = Settings(project_name)
# new project?
if storage.get_project(project_name) == None:
# Default values for web create
settings.init_payload_injectable(
FilePath("messagebox.bin"),
FilePath("data/binary/exes/procexp64.exe"),
""
)
if storage.get_project_setting(project_name) == None:
# Sane defaults for web
settings.injectable_base = "7z.exe"
settings.payload_base = "calc64.bin"
settings.decoder_style = "xor_2"
settings.carrier_name = "alloc_rw_rx"
settings.carrier_invoke_style = CarrierInvokeStyle.BackdoorCallInstr
@@ -189,17 +192,20 @@ def add_project():
settings.fix_missing_iat = True
# add new project
project = WebProject(project_name, settings)
project.comment = comment
storage.add_project(project)
settings.project_comment = comment
storage.add_project_setting(settings)
# update project
else:
settings.init_payload_injectable(
FilePath(request.form['shellcode']),
FilePath(request.form['exe']),
request.form.get('dllfunc', "")
)
logger.info("Update project: {}".format(project_name))
shellcode_file = request.form['shellcode']
injectable_file = request.form['exe']
dll_func = request.form.get('dllfunc', "")
settings.injectable_base = injectable_file
settings.payload_base = shellcode_file
settings.dllfunc = dll_func
settings.fix_missing_iat = True if request.form.get('fix_missing_iat') != None else False
settings.carrier_name = request.form['carrier_name']
@@ -216,10 +222,8 @@ def add_project():
settings.plugin_virtualprotect = request.form.get('virtualprotect', "standard")
# overwrite project
project = storage.get_project(project_name)
project.settings = settings
project.comment = comment
storage.save_project(project)
#settings = storage.get_project(project_name)
storage.save_project_settings(settings)
return redirect("/project/{}".format(project_name), code=302)
@@ -237,14 +241,18 @@ def supermega_thread(settings: Settings):
def build_project(project_name):
global thread_running
project = storage.get_project(project_name)
project_settings = storage.get_project_setting(project_name)
if project_settings == None:
logger.error("Project {} not found".format(project_name))
return redirect("/projects", code=302)
#if project.settings.inject_exe_in.endswith(".dll"):
#if project.settings.get_inject_exe_in().endswith(".dll"):
# if project.settings.dllfunc == "":
# logger.error("DLL injection requires a DLL function name")
# return redirect("/project/{}".format(project_name), code=302)
project.settings.try_start_final_infected_exe = False
project_settings.try_start_final_infected_exe = False
project = Project(project_settings)
prepare_project(project_name, project.settings)
thread = Thread(target=supermega_thread, args=(project.settings, ))
thread.start()
@@ -266,9 +274,14 @@ def status_project(project_name):
@views_project.route("/project/<project_name>/exec", methods=['POST', 'GET'])
def start_project(project_name):
project = storage.get_project(project_name)
if project == None:
project_settings = storage.get_project_setting(project_name)
if project_settings == None:
return redirect("/", code=302)
project = Project(project_settings)
if not project.init():
logger.error("Project {} could not be initialized".format(project_name))
return redirect("/project/{}".format(project_name), code=302)
remote = False
remote_arg = request.args.get('remote')
@@ -283,10 +296,10 @@ def start_project(project_name):
logger.info(" Exec project: {} remote: {} no_exec: {}".format(project_name, remote, no_exec))
if remote:
logger.info(" Exec {} on server {}".format(project.settings.inject_exe_out, config.get("avred_server")))
with open(project.settings.inject_exe_out, "rb") as f:
logger.info(" Exec {} on server {}".format(project.settings.get_inject_exe_out(), config.get("avred_server")))
with open(project.settings.get_inject_exe_out(), "rb") as f:
data = f.read()
filename = os.path.basename(project.settings.inject_exe_out)
filename = os.path.basename(project.settings.get_inject_exe_out())
try:
scannerDetectsBytes(data,
filename,
@@ -302,11 +315,11 @@ def start_project(project_name):
# Start/verify it at the end
if project.settings.verify:
logger.info(" Verify infected exe")
exit_code = verify_injected_exe(project.settings.inject_exe_out)
exit_code = verify_injected_exe(project.settings.get_inject_exe_out())
elif no_exec == False:
run_exe(project.settings.inject_exe_out, dllfunc=project.settings.dllfunc, check=False)
run_exe(project.settings.get_inject_exe_out(), dllfunc=project.settings.dllfunc, check=False)
elif no_exec == True:
dirname = os.path.dirname(os.path.abspath(project.settings.inject_exe_out))
dirname = os.path.dirname(os.path.abspath(project.settings.get_inject_exe_out()))
logger.info(" Open folder: {}".format(dirname))
subprocess.run(['explorer', dirname])
-83
View File
@@ -1,83 +0,0 @@
from flask import Flask, Blueprint, current_app, request, redirect, url_for, render_template, send_file, make_response, session, jsonify
from threading import Thread
import os
import logging
from typing import List, Tuple
from datetime import datetime
from observer import observer
from model.defs import *
from supermega import start
from phases.compiler import compile_dev
from phases.assembler import asm_to_shellcode
from helper import clean_tmp_files
views_shcdev = Blueprint('views_shcdev', __name__)
logger = logging.getLogger("ViewsShcdev")
@views_shcdev.route("/shcdev")
def devs_route():
data = []
for filename in os.listdir(PATH_PAYLOAD):
file_path = PATH_PAYLOAD + filename
creation_time = os.path.getctime(file_path)
readable_time = datetime.fromtimestamp(creation_time).strftime('%Y-%m-%d %H:%M:%S')
data.append({
"name": filename,
"date": readable_time,
})
return render_template('devs.html', data=data)
@views_shcdev.route("/shcdev/<name>")
def dev_route(name):
data = []
log = ""
path = PATH_PAYLOAD + name
for filename in os.listdir(path):
filepath = path + "/" + filename
creation_time = os.path.getmtime(filepath)
readable_time = datetime.fromtimestamp(creation_time).strftime('%Y-%m-%d %H:%M:%S')
info = ""
if filename.endswith(".asm"):
info = "text assembly (cleaned, from compiled .c)"
elif filename.endswith(".bin"):
info = "generated shellcode (from .exe)"
elif filename.endswith(".c"):
info = "input C code"
elif filename.endswith(".exe"):
info = "temporary shellcode holder (from .c)"
elif filename.endswith("cmdoutput.log"):
info = "command output"
with open(path + "/" + filename, "r") as f:
log += f.read() + "\n-----------------------------------\n"
elif filename.endswith("supermega.log"):
info = "supermega logging output"
with open(path + "/" + filename, "r") as f:
log += f.read()
data.append({
"name": filename,
"date": readable_time,
"info": info,
})
return render_template('dev.html',
name=name, files=data, log=log)
@views_shcdev.route("/shcdev/<name>/build")
def dev_build_route(name):
c_in = PATH_PAYLOAD + "{}/main.c".format(name)
asm_out = PATH_PAYLOAD + "{}/main.asm".format(name)
build_exe = PATH_PAYLOAD + "{}/main.exe".format(name)
shellcode_out = PATH_PAYLOAD + "{}/main.bin".format(name)
compile_dev(c_in, asm_out)
asm_to_shellcode(asm_out, build_exe, shellcode_out)
observer.write_logs(PATH_PAYLOAD + "{}/".format(name))
clean_tmp_files()
return redirect("/shcdev/{}".format(name), code=302)