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
+5 -2
View File
@@ -4,16 +4,19 @@ bak/
tools/ tools/
doc/ doc/
projects/*
!projects/default/
!projects/default/**
data/binary/exes_more/ data/binary/exes_more/
data/source/payload/ data/source/payload/
data/binary/exes/*
log-* log-*
*.verify.exe *.verify.exe
*.verify.dll *.verify.dll
*.infected.exe *.infected.exe
projects/*
*.test.exe *.test.exe
data/binary/exes/*
main.obj main.obj
mlink$.lnk mlink$.lnk
+19 -21
View File
@@ -1,52 +1,50 @@
import pickle import pickle
import os import os
import yaml
import pickle import pickle
import logging import logging
from typing import List, Tuple from typing import List, Tuple
from model.defs import * from model.defs import *
from model.project import WebProject from model.project import Settings
logger = logging.getLogger("Storage") logger = logging.getLogger("Storage")
class Storage(): class Storage():
def __init__(self): def __init__(self):
pass pass
def get_projects(self) -> List[WebProject]: def get_project_settings(self) -> List[Settings]:
projects: List[WebProject] = [] project_settings: List[Settings] = []
for project_name in os.listdir(PATH_WEB_PROJECT): for project_name in os.listdir(PATH_WEB_PROJECT):
project = self.get_project(project_name) project_setting = self.get_project_setting(project_name)
if project is None: if project_setting is None:
continue continue
projects.append(project) project_settings.append(project_setting)
return projects return project_settings
def get_project(self, project_name: str) -> WebProject: def get_project_setting(self, project_name: str) -> Settings| None:
logger.debug("Load project: {}".format(project_name))
path = "{}/{}".format(PATH_WEB_PROJECT, project_name) path = "{}/{}".format(PATH_WEB_PROJECT, project_name)
json_path = "{}/project.pickle".format(path) json_path = "{}/project.pickle".format(path)
if not os.path.exists(json_path): if not os.path.exists(json_path):
return None return None
logger.info("Loading project from: {}".format(json_path))
with open(json_path, "rb") as f: with open(json_path, "rb") as f:
project = pickle.load(f) settings = pickle.load(f)
return project return settings
def add_project(self, project: WebProject): def add_project_setting(self, settings: Settings):
# directories and contents os.makedirs(PATH_WEB_PROJECT + settings.project_name, exist_ok=True)
os.makedirs(PATH_WEB_PROJECT + project.name, exist_ok=True) with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, settings.project_name), "wb") as f:
with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, project.name), "wb") as f: pickle.dump(settings, f)
pickle.dump(project, f)
def save_project(self, project: WebProject): def save_project_settings(self, settings: Settings):
with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, project.name), "wb") as f: with open("{}/{}/project.pickle".format(PATH_WEB_PROJECT, settings.project_name), "wb") as f:
pickle.dump(project, 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 '' }}" <a class="nav-link {{ 'active' if request.path == '/' else '' }}"
href="/">Home</a> href="/">Home</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/projects' else '' }}" <a class="nav-link {{ 'active' if request.path == '/projects' else '' }}"
href="/projects">Projects</a> href="/projects">Projects</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/shellcodes' else '' }}" <a class="nav-link {{ 'active' if request.path == '/exes' else '' }}"
href="/shellcodes">Shellcodes</a> href="/exes">EXEs</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/exes' else '' }}" <a class="nav-link {{ 'active' if request.path == '/exes' else '' }}"
href="/exes">EXE's</a> href="/shellcodes">Shellcodes</a>
</li>
<li class="nav-item">
<a class="nav-link {{ 'active' if request.path == '/shcdev' else '' }}"
href="/shcdev">ShcDev</a>
</li> </li>
</ul> </ul>
</div> </div>
+18 -18
View File
@@ -45,7 +45,7 @@
onchange="this.form.submit()" readonly> onchange="this.form.submit()" readonly>
<input type="text" name="comment" class="hidden form-control" <input type="text" name="comment" class="hidden form-control"
placeholder="" value="{{project.comment}}" placeholder="" value="{{project_comment}}"
aria-label="PROJECTNAME" aria-describedby="basic-addon1" aria-label="PROJECTNAME" aria-describedby="basic-addon1"
onchange="this.form.submit()"> onchange="this.form.submit()">
@@ -63,7 +63,7 @@
aria-label="SHELLCODE" onchange="this.form.submit()"> aria-label="SHELLCODE" onchange="this.form.submit()">
{% for shellcode in shellcodes %} {% for shellcode in shellcodes %}
<option value="{{shellcode['filename']}}" <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']}}) {{shellcode['filename']}} ({{shellcode['size']}})
</option> </option>
@@ -85,7 +85,7 @@
aria-label="EXE" onchange="this.form.submit()"> aria-label="EXE" onchange="this.form.submit()">
{% for exe in exes %} {% for exe in exes %}
<option value="{{exe['filename']}}" <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> {{exe['filename'] | basename}} ({{exe['size']}})</option>
{% endfor %} {% endfor %}
@@ -98,7 +98,7 @@
<select class="form-select" name="dllfunc" aria-label="DLLFUNC" onchange="this.form.submit()"> <select class="form-select" name="dllfunc" aria-label="DLLFUNC" onchange="this.form.submit()">
{% for export in exports %} {% for export in exports %}
<option value="{{export['name']}}" <option value="{{export['name']}}"
{% if export["name"] == project.settings.dllfunc %} selected {% endif %} {% if export["name"] == settings.dllfunc %} selected {% endif %}
> >
{{export['name']}} ({{export['size']}})</option> {{export['name']}} ({{export['size']}})</option>
{% endfor %} {% endfor %}
@@ -108,7 +108,7 @@
<!-- Row 3: exe and shellcode info --> <!-- Row 3: exe and shellcode info -->
<div class="col-2"> <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> <ul>
<li> <li>
{% if is_64 %} {% if is_64 %}
@@ -154,11 +154,11 @@
<div class="col-3"> <div class="col-3">
<div class="form-group row"> <div class="form-group row">
<label for="carrier_name" class="col-sm-5 col-form-label <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 opsec_bad
{% elif 'peb_walk' in project.settings.carrier_name %} {% elif 'peb_walk' in settings.carrier_name %}
opsec_bad opsec_bad
{% elif 'change' in project.settings.carrier_name %} {% elif 'change' in settings.carrier_name %}
opsec_neutral opsec_neutral
{% else %} {% else %}
opsec_good opsec_good
@@ -172,7 +172,7 @@
aria-label="CARRIERNAME" onchange="this.form.submit()"> aria-label="CARRIERNAME" onchange="this.form.submit()">
{% for name in carrier_names %} {% for name in carrier_names %}
<option value="{{name}}" <option value="{{name}}"
{% if name in project.settings.carrier_name %} selected {% endif %} {% if name in settings.carrier_name %} selected {% endif %}
>{{name}}</option> >{{name}}</option>
{% endfor %} {% endfor %}
</select> </select>
@@ -188,7 +188,7 @@
aria-label="INJECTSTYLE" onchange="this.form.submit()"> aria-label="INJECTSTYLE" onchange="this.form.submit()">
{% for name, value in carrier_invoke_styles %} {% for name, value in carrier_invoke_styles %}
<option value="{{name}}" <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> >{{value}}</option>
{% endfor %} {% endfor %}
</select> </select>
@@ -204,7 +204,7 @@
aria-label="PAYLOADLOCATION" onchange="this.form.submit()"> aria-label="PAYLOADLOCATION" onchange="this.form.submit()">
{% for name, value in payload_locations %} {% for name, value in payload_locations %}
<option value="{{name}}" <option value="{{name}}"
{% if value in project.settings.payload_location.value %} selected {% endif %} {% if value in settings.payload_location.value %} selected {% endif %}
>{{value}}</option> >{{value}}</option>
{% endfor %} {% endfor %}
</select> </select>
@@ -231,7 +231,7 @@
aria-label="DECODERESTYLE" onchange="this.form.submit()"> aria-label="DECODERESTYLE" onchange="this.form.submit()">
{% for name in decoder_styles %} {% for name in decoder_styles %}
<option value="{{name}}" <option value="{{name}}"
{% if name in project.settings.decoder_style %} selected {% endif %} {% if name in settings.decoder_style %} selected {% endif %}
>{{name}} >{{name}}
</option> </option>
{% endfor %} {% endfor %}
@@ -248,7 +248,7 @@
aria-label="GUARDRAILSTYLE" onchange="this.form.submit()"> aria-label="GUARDRAILSTYLE" onchange="this.form.submit()">
{% for name in guardrailstyles %} {% for name in guardrailstyles %}
<option value="{{name}}" <option value="{{name}}"
{% if name in project.settings.plugin_guardrail %} selected {% endif %} {% if name in settings.plugin_guardrail %} selected {% endif %}
>{{name}} >{{name}}
</option> </option>
{% endfor %} {% endfor %}
@@ -256,14 +256,14 @@
</div> </div>
</div> </div>
{% if project.settings.plugin_guardrail != "none" %} {% if settings.plugin_guardrail != "none" %}
<div class="form-group row"> <div class="form-group row">
<label for="guardrail" class="col-sm-5 col-form-label"> <label for="guardrail" class="col-sm-5 col-form-label">
Guard Data Guard Data
</label> </label>
<div class="col-sm-7"> <div class="col-sm-7">
<input type="text" name="guardrail_data" class="hidden form-control" <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" aria-label="guardrail_data" aria-describedby="basic-addon1"
onchange="this.form.submit()"> onchange="this.form.submit()">
</div> </div>
@@ -279,7 +279,7 @@
aria-label="antiemulation" onchange="this.form.submit()"> aria-label="antiemulation" onchange="this.form.submit()">
{% for name in antiemulationstyles %} {% for name in antiemulationstyles %}
<option value="{{name}}" <option value="{{name}}"
{% if name in project.settings.plugin_antiemulation %} selected {% endif %} {% if name in settings.plugin_antiemulation %} selected {% endif %}
>{{name}} >{{name}}
</option> </option>
{% endfor %} {% endfor %}
@@ -296,7 +296,7 @@
aria-label="decoy" onchange="this.form.submit()"> aria-label="decoy" onchange="this.form.submit()">
{% for name in decoystyles %} {% for name in decoystyles %}
<option value="{{name}}" <option value="{{name}}"
{% if name in project.settings.plugin_decoy %} selected {% endif %} {% if name in settings.plugin_decoy %} selected {% endif %}
>{{name}} >{{name}}
</option> </option>
{% endfor %} {% endfor %}
@@ -313,7 +313,7 @@
aria-label="virtualprotect" onchange="this.form.submit()"> aria-label="virtualprotect" onchange="this.form.submit()">
{% for name in virtualprotectstyles %} {% for name in virtualprotectstyles %}
<option value="{{name}}" <option value="{{name}}"
{% if name in project.settings.plugin_virtualprotect %} selected {% endif %} {% if name in settings.plugin_virtualprotect %} selected {% endif %}
>{{name}} >{{name}}
</option> </option>
{% endfor %} {% endfor %}
+2 -2
View File
@@ -11,8 +11,8 @@
<h1> Projects </h1> <h1> Projects </h1>
<ul> <ul>
{% for project in projects %} {% for settings in project_settings %}
<li><a href="/project/{{project.name}}">{{ project.name }}</a></li> <li><a href="/project/{{settings.project_name}}">{{ settings.project_name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
+1
View File
@@ -15,6 +15,7 @@ logger = logging.getLogger("Views")
@views.route("/") @views.route("/")
def index(): def index():
return render_template('index.html') return render_template('index.html')
return redirect("/project/default", code=302)
@views.route("/exes/<exe_name>") @views.route("/exes/<exe_name>")
+66 -53
View File
@@ -15,7 +15,8 @@ from config import config
from model.settings import Settings from model.settings import Settings
from model.defs import * from model.defs import *
from supermega import start 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 sender import scannerDetectsBytes
from phases.injector import verify_injected_exe from phases.injector import verify_injected_exe
from phases.templater import get_template_names from phases.templater import get_template_names
@@ -36,19 +37,21 @@ thread_running = False
@views_project.route("/projects") @views_project.route("/projects")
def projects_route(): def projects_route():
projects = storage.get_projects() projects_settings = storage.get_project_settings()
return render_template('projects.html', projects=projects) return render_template('projects.html', projects_settings=projects_settings)
@views_project.route("/project/<name>") @views_project.route("/project/<name>")
def project(name): def project(name):
project = storage.get_project(name) project_setting = storage.get_project_setting(name)
if project == None: if project_setting == None:
logger.error("Project {} not found".format(name))
return redirect("/projects", code=302) return redirect("/projects", code=302)
exe_path = project.settings.inject_exe_out project_setting.print()
is_built = False is_built = False
if os.path.exists(exe_path): if os.path.exists(project_setting.project_exe_path):
is_built = True is_built = True
exports = [] exports = []
@@ -65,16 +68,16 @@ def project(name):
if config.get("avred_server") != "": if config.get("avred_server") != "":
has_remote = True has_remote = True
# when we select a shellcode # payload / shellcode
if project.settings.payload_path != "": if project_setting.get_payload_path() != None:
payload_len = os.path.getsize(project.settings.payload_path) payload_len = os.path.getsize(project_setting.get_payload_path())
# when we selected an input file # injectable / exe
if project.settings.inject_exe_in != "" and os.path.exists(project.settings.inject_exe_in): if project_setting.get_inject_exe_in() != None and os.path.exists(project_setting.get_inject_exe_in()):
superpe = SuperPe(project.settings.inject_exe_in) superpe = SuperPe(project_setting.get_inject_exe_in())
#if not superpe.is_64(): #if not superpe.is_64():
# # return 500 # # 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_64 = superpe.is_64()
is_dotnet = superpe.is_dotnet() is_dotnet = superpe.is_dotnet()
@@ -85,17 +88,17 @@ def project(name):
if rdata_section != None: if rdata_section != None:
data_sect_size = rdata_section.virt_size data_sect_size = rdata_section.virt_size
else: 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() has_rodata_section = superpe.has_rodata_section()
if has_rodata_section: if has_rodata_section:
data_sect_largest_gap_size = superpe.get_rdata_rangemanager().find_largest_gap() data_sect_largest_gap_size = superpe.get_rdata_rangemanager().find_largest_gap()
unresolved_dlls = pe.dllresolver.unresolved_dlls(superpe) unresolved_dlls = pe.dllresolver.unresolved_dlls(superpe)
project_dir = os.path.dirname(os.getcwd() + "\\" + project.settings.main_dir) project_dir = os.path.dirname(os.getcwd() + "\\" + project_setting.project_path)
log_files = get_logfiles(project.settings.main_dir) log_files = get_logfiles(project_setting.project_path)
exes = list_files_and_sizes(PATH_EXES, prepend=PATH_EXES) exes = list_files_and_sizes(PATH_EXES)
exes += list_files_and_sizes(PATH_EXES_MORE, prepend=PATH_EXES_MORE) #exes += list_files_and_sizes(PATH_EXES_MORE, prepend=PATH_EXES_MORE)
shellcodes = list_files_and_sizes(PATH_SHELLCODES) shellcodes = list_files_and_sizes(PATH_SHELLCODES)
carrier_names = get_template_names() carrier_names = get_template_names()
@@ -110,9 +113,10 @@ def project(name):
return render_template('project.html', return render_template('project.html',
project_name = name, project_name = name,
project=project, project_comment = project_setting.project_comment,
is_built=is_built, is_built=is_built,
project_dir=project_dir, project_dir=project_dir,
settings=project_setting,
exes=exes, exes=exes,
shellcodes=shellcodes, shellcodes=shellcodes,
@@ -133,7 +137,7 @@ def project(name):
has_rodata_section=has_rodata_section, has_rodata_section=has_rodata_section,
has_remote=has_remote, has_remote=has_remote,
fix_missing_iat=project.settings.fix_missing_iat, fix_missing_iat=project_setting.fix_missing_iat,
guardrailstyles = guardrail_styles, guardrailstyles = guardrail_styles,
antiemulationstyles = antiemulation_styles, antiemulationstyles = antiemulation_styles,
@@ -170,18 +174,17 @@ def list_files(directory, prepend="") -> List[str]:
def add_project(): def add_project():
if request.method == 'POST': if request.method == 'POST':
project_name = request.form['project_name'] project_name = request.form['project_name']
settings = Settings(project_name)
comment = request.form['comment'] comment = request.form['comment']
# Empty settings, except name
settings = Settings(project_name)
# new project? # new project?
if storage.get_project(project_name) == None: if storage.get_project_setting(project_name) == None:
# Default values for web create # Sane defaults for web
settings.init_payload_injectable( settings.injectable_base = "7z.exe"
FilePath("messagebox.bin"), settings.payload_base = "calc64.bin"
FilePath("data/binary/exes/procexp64.exe"),
""
)
settings.decoder_style = "xor_2" settings.decoder_style = "xor_2"
settings.carrier_name = "alloc_rw_rx" settings.carrier_name = "alloc_rw_rx"
settings.carrier_invoke_style = CarrierInvokeStyle.BackdoorCallInstr settings.carrier_invoke_style = CarrierInvokeStyle.BackdoorCallInstr
@@ -189,17 +192,20 @@ def add_project():
settings.fix_missing_iat = True settings.fix_missing_iat = True
# add new project # add new project
project = WebProject(project_name, settings) settings.project_comment = comment
project.comment = comment storage.add_project_setting(settings)
storage.add_project(project)
# update project # update project
else: else:
settings.init_payload_injectable( logger.info("Update project: {}".format(project_name))
FilePath(request.form['shellcode']),
FilePath(request.form['exe']), shellcode_file = request.form['shellcode']
request.form.get('dllfunc', "") 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.fix_missing_iat = True if request.form.get('fix_missing_iat') != None else False
settings.carrier_name = request.form['carrier_name'] settings.carrier_name = request.form['carrier_name']
@@ -216,10 +222,8 @@ def add_project():
settings.plugin_virtualprotect = request.form.get('virtualprotect', "standard") settings.plugin_virtualprotect = request.form.get('virtualprotect', "standard")
# overwrite project # overwrite project
project = storage.get_project(project_name) #settings = storage.get_project(project_name)
project.settings = settings storage.save_project_settings(settings)
project.comment = comment
storage.save_project(project)
return redirect("/project/{}".format(project_name), code=302) return redirect("/project/{}".format(project_name), code=302)
@@ -237,14 +241,18 @@ def supermega_thread(settings: Settings):
def build_project(project_name): def build_project(project_name):
global thread_running 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 == "": # if project.settings.dllfunc == "":
# logger.error("DLL injection requires a DLL function name") # logger.error("DLL injection requires a DLL function name")
# return redirect("/project/{}".format(project_name), code=302) # 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) prepare_project(project_name, project.settings)
thread = Thread(target=supermega_thread, args=(project.settings, )) thread = Thread(target=supermega_thread, args=(project.settings, ))
thread.start() thread.start()
@@ -266,10 +274,15 @@ def status_project(project_name):
@views_project.route("/project/<project_name>/exec", methods=['POST', 'GET']) @views_project.route("/project/<project_name>/exec", methods=['POST', 'GET'])
def start_project(project_name): def start_project(project_name):
project = storage.get_project(project_name) project_settings = storage.get_project_setting(project_name)
if project == None: if project_settings == None:
return redirect("/", code=302) 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 = False
remote_arg = request.args.get('remote') remote_arg = request.args.get('remote')
if remote_arg == "true": if remote_arg == "true":
@@ -283,10 +296,10 @@ def start_project(project_name):
logger.info(" Exec project: {} remote: {} no_exec: {}".format(project_name, remote, no_exec)) logger.info(" Exec project: {} remote: {} no_exec: {}".format(project_name, remote, no_exec))
if remote: if remote:
logger.info(" Exec {} on server {}".format(project.settings.inject_exe_out, config.get("avred_server"))) logger.info(" Exec {} on server {}".format(project.settings.get_inject_exe_out(), config.get("avred_server")))
with open(project.settings.inject_exe_out, "rb") as f: with open(project.settings.get_inject_exe_out(), "rb") as f:
data = f.read() data = f.read()
filename = os.path.basename(project.settings.inject_exe_out) filename = os.path.basename(project.settings.get_inject_exe_out())
try: try:
scannerDetectsBytes(data, scannerDetectsBytes(data,
filename, filename,
@@ -302,11 +315,11 @@ def start_project(project_name):
# Start/verify it at the end # Start/verify it at the end
if project.settings.verify: if project.settings.verify:
logger.info(" Verify infected exe") 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: 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: 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)) logger.info(" Open folder: {}".format(dirname))
subprocess.run(['explorer', 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)
+5 -14
View File
@@ -6,7 +6,7 @@ import logging
import pickle import pickle
import math import math
from model.project import WebProject from model.project import Project
from config import config from config import config
from model.defs import * from model.defs import *
from observer import observer from observer import observer
@@ -16,15 +16,6 @@ logger = logging.getLogger("Helper")
SHC_VERIFY_SLEEP = 0.2 SHC_VERIFY_SLEEP = 0.2
def write_webproject(project_name, settings):
filepath = "{}project.pickle".format(settings.main_dir)
logger.info("Write project to: {}".format(filepath))
webProject = WebProject(project_name, settings)
webProject.comment = "Created by command line interface"
with open(filepath, "wb") as f:
pickle.dump(webProject, f)
def clean_tmp_files(): def clean_tmp_files():
files_to_clean = [ files_to_clean = [
# compile artefacts in current working dir # compile artefacts in current working dir
@@ -39,10 +30,10 @@ def clean_tmp_files():
def clean_files(settings): def clean_files(settings):
files_to_clean = [ files_to_clean = [
# temporary files # temporary files
settings.main_c_path, settings.project_c_path,
settings.main_asm_path, settings.project_asm_path,
settings.main_shc_path, settings.project_shc_path,
settings.main_exe_path, settings.project_exe_path,
] ]
for file in files_to_clean: for file in files_to_clean:
pathlib.Path(file).unlink(missing_ok=True) pathlib.Path(file).unlink(missing_ok=True)
+1 -2
View File
@@ -13,14 +13,13 @@ PATH_EXES_MORE = "data/binary/exes_more/"
PATH_DLLS = "data/binary/dlls/" PATH_DLLS = "data/binary/dlls/"
PATH_SHELLCODES = "data/binary/shellcodes/" PATH_SHELLCODES = "data/binary/shellcodes/"
PATH_CARRIER = "data/source/carrier/" PATH_CARRIER = "data/source/carrier/"
PATH_PAYLOAD = "data/source/payload/"
PATH_DECODER = "data/source/decoder/" PATH_DECODER = "data/source/decoder/"
PATH_ANTIEMULATION = "data/source/antiemulation/" PATH_ANTIEMULATION = "data/source/antiemulation/"
PATH_DECOY = "data/source/decoy/" PATH_DECOY = "data/source/decoy/"
PATH_GUARDRAILS = "data/source/guardrails/" PATH_GUARDRAILS = "data/source/guardrails/"
PATH_VIRTUALPROTECT = "data/source/virtualprotect/" PATH_VIRTUALPROTECT = "data/source/virtualprotect/"
PATH_PAYLOAD = "data/source/payload/"
PATH_WEB_PROJECT = "projects/" PATH_WEB_PROJECT = "projects/"
+14 -14
View File
@@ -1,5 +1,4 @@
import logging import logging
import shutil
from model.defs import * from model.defs import *
from model.payload import Payload from model.payload import Payload
@@ -9,26 +8,19 @@ from model.injectable import Injectable
logger = logging.getLogger("Project") logger = logging.getLogger("Project")
class WebProject():
def __init__(self, name: str, settings: Settings):
self.name = name
self.settings: Settings = settings
self.comment: str = ""
class Project(): class Project():
def __init__(self, settings: Settings): def __init__(self, settings: Settings):
self.name: str = ""
self.comment: str = ""
self.settings: Settings = settings self.settings: Settings = settings
self.payload: Payload = Payload(self.settings.payload_path)
self.injectable: Injectable = Injectable(self.settings.inject_exe_in)
self.project_dir: str = "" # Set by init()
self.project_exe: str = "" self.payload: Payload
self.injectable: Injectable
def init(self) -> bool: def init(self) -> bool:
self.payload: Payload = Payload(self.settings.get_payload_path())
self.injectable: Injectable = Injectable(self.settings.get_inject_exe_in())
if not self.payload.init(): if not self.payload.init():
return False return False
if not self.injectable.init(): if not self.injectable.init():
@@ -36,6 +28,14 @@ class Project():
return True return True
def print(self):
logger.info("Project Name: {}".format(self.settings.project_name))
logger.info("Comment: {}".format(self.settings.project_comment))
logger.info("Settings: {}".format(self.settings.__dict__))
logger.info("Payload Path: {}".format(self.payload.payload_path))
logger.info("Injectable Path: {}".format(self.injectable.exe_filepath))
def prepare_project(project_name, settings): def prepare_project(project_name, settings):
src = "{}{}/".format(PATH_CARRIER, settings.carrier_name) src = "{}{}/".format(PATH_CARRIER, settings.carrier_name)
dst = "{}{}/".format(PATH_WEB_PROJECT, project_name) dst = "{}{}/".format(PATH_WEB_PROJECT, project_name)
+46 -40
View File
@@ -7,33 +7,46 @@ logger = logging.getLogger("Views")
class Settings(): class Settings():
def __init__(self, project_name: str = "default"): def __init__(self, project_name: str = "default"):
self.project_name: str = project_name self.project_name: str = project_name
self.payload_path: FilePath = FilePath("") self.project_comment: str = ""
self.project_path: FilePath = FilePath("{}{}/".format(PATH_WEB_PROJECT, self.project_name))
# Settings # OUT: Project directories and files (based on project_path)
self.project_c_path: FilePath = FilePath(self.project_path + "main.c")
self.project_asm_path: FilePath = FilePath(self.project_path + "main.asm")
self.project_exe_path: FilePath = FilePath(self.project_path + "main.exe")
self.project_shc_path: FilePath = FilePath(self.project_path + "main.bin")
# IN: Injectable (like "7z.exe", in data/input/exes/)
self.injectable_base: str = ""
# IN: Payload / Shellcode (like "createfile.bin", in data/input/shellcodes/)
self.payload_base: str = ""
# Config
self.carrier_name: str = "" self.carrier_name: str = ""
self.carrier_invoke_style: CarrierInvokeStyle = CarrierInvokeStyle.BackdoorCallInstr
self.decoder_style: str = "xor_2" self.decoder_style: str = "xor_2"
self.payload_location: PayloadLocation = PayloadLocation.DATA
self.short_call_patching: bool = False self.short_call_patching: bool = False
self.fix_missing_iat = True
self.patch_show_window = True
self.dllfunc: str = "" # For DLL injection
self.plugin_antiemulation: str = "none" # PLUGIN: Guardrail
self.plugin_decoy: str = "none"
self.plugin_guardrail: str = "none" self.plugin_guardrail: str = "none"
self.plugin_guardrail_data_key: str = "" self.plugin_guardrail_data_key: str = ""
self.plugin_guardrail_data_value: str = "" self.plugin_guardrail_data_value: str = ""
self.plugin_virtualprotect: str = "standard"
self.plugin_virtualprotect_data: str = ""
self.dllfunc: str = "" # For DLL injection # PLUGIN: Anti-Emulation / EDR deconditioner
self.plugin_antiemulation: str = "none"
# Anti-debugging
self.sir_iteration_count: int = 5 self.sir_iteration_count: int = 5
self.sir_alloc_count: int = 100 self.sir_alloc_count: int = 100
# Injectable # PLUGIN: Other (not widely used or important)
self.carrier_invoke_style: CarrierInvokeStyle = CarrierInvokeStyle.BackdoorCallInstr self.plugin_virtualprotect: str = "standard"
self.inject_exe_in: FilePath = FilePath("") self.plugin_virtualprotect_data: str = ""
self.inject_exe_out: FilePath = FilePath("") self.plugin_decoy: str = "none"
# Debug # DEBUG: Debug stuff (for development)
self.show_command_output: bool = False self.show_command_output: bool = False
self.verify: bool = False self.verify: bool = False
self.try_start_final_infected_exe: bool = False self.try_start_final_infected_exe: bool = False
@@ -41,33 +54,26 @@ class Settings():
self.cleanup_files_on_exit: bool = True self.cleanup_files_on_exit: bool = True
self.generate_asm_from_c: bool = True self.generate_asm_from_c: bool = True
# More def get_payload_path(self) -> FilePath:
self.fix_missing_iat = True if self.payload_base == "":
self.patch_show_window = True return None
self.payload_location: PayloadLocation = PayloadLocation.DATA return FilePath(PATH_SHELLCODES + self.payload_base)
# directories and filenames def get_inject_exe_in(self) -> FilePath:
self.main_dir: FilePath = FilePath("{}{}/".format(PATH_WEB_PROJECT, self.project_name)) if self.injectable_base == "":
self.main_c_path: FilePath = FilePath(self.main_dir + "main.c") return None
self.main_asm_path: FilePath = FilePath(self.main_dir + "main.asm") return FilePath(PATH_EXES + self.injectable_base)
self.main_exe_path: FilePath = FilePath(self.main_dir + "main.exe")
self.main_shc_path: FilePath = FilePath(self.main_dir + "main.bin")
self.inject_exe_out: FilePath = FilePath("{}{}".format(
self.main_dir, os.path.basename(self.inject_exe_in).replace(".exe", ".infected.exe")))
def get_inject_exe_out(self) -> FilePath:
def init_payload_injectable(self, shellcode: FilePath, injectable: FilePath, dll_func: str ): return FilePath("{}{}".format(
self.payload_path = FilePath(PATH_SHELLCODES + shellcode) self.project_path,
if shellcode == "createfile.bin": self.injectable_base.replace(".exe", ".infected.exe")
self.verify = True
self.try_start_final_infected_exe = False
else:
self.cleanup_files_on_exit = False
self.inject_exe_in = FilePath(PATH_EXES + injectable)
self.inject_exe_out = FilePath("{}{}".format(
self.main_dir,
os.path.basename(self.inject_exe_in).replace(".exe", ".infected.exe")
)) ))
self.dllfunc = dll_func def print(self):
logger.info("Settings for project: {}".format(self.project_name))
for attr, value in self.__dict__.items():
if isinstance(value, FilePath):
value = str(value)
logger.info(" {}: {}".format(attr, value))
logger.info("-" * 40)
+3 -3
View File
@@ -33,7 +33,7 @@ class Injector():
# superpe is a representation of the exe file. We gonna modify it, and save it at the end. # superpe is a representation of the exe file. We gonna modify it, and save it at the end.
# reuse from injectable # reuse from injectable
#self.superpe = SuperPe(settings.inject_exe_in) #self.superpe = SuperPe(settings.get_inject_exe_in())
self.superpe = injectable.superpe self.superpe = injectable.superpe
self.function_backdoorer = FunctionBackdoorer(self.superpe) self.function_backdoorer = FunctionBackdoorer(self.superpe)
@@ -110,8 +110,8 @@ class Injector():
## Inject ## Inject
def inject_exe(self): def inject_exe(self):
exe_in = self.settings.inject_exe_in exe_in = self.settings.get_inject_exe_in()
exe_out = self.settings.inject_exe_out exe_out = self.settings.get_inject_exe_out()
carrier_invoke_style: CarrierInvokeStyle = self.settings.carrier_invoke_style carrier_invoke_style: CarrierInvokeStyle = self.settings.carrier_invoke_style
logger.info("-[ Injecting Carrier".format()) logger.info("-[ Injecting Carrier".format())
+2 -2
View File
@@ -29,7 +29,7 @@ def create_c_from_template(settings: Settings, payload_len: int):
dst = "{}{}/".format(PATH_WEB_PROJECT, settings.project_name) dst = "{}{}/".format(PATH_WEB_PROJECT, settings.project_name)
logger.info("-[ Carrier create Template: {}".format( logger.info("-[ Carrier create Template: {}".format(
settings.main_c_path)) settings.project_c_path))
# check that source directory exists # check that source directory exists
if not os.path.exists(src): if not os.path.exists(src):
@@ -139,6 +139,6 @@ def create_c_from_template(settings: Settings, payload_len: int):
'PAYLOAD_LEN': payload_len, 'PAYLOAD_LEN': payload_len,
'plugin_virtualprotect': plugin_virtualprotect, 'plugin_virtualprotect': plugin_virtualprotect,
}) })
with open(settings.main_c_path, "w", encoding='utf-8') as file: with open(settings.project_c_path, "w", encoding='utf-8') as file:
file.write(rendered_template) file.write(rendered_template)
observer.add_text_file("main_c_rendered", rendered_template) observer.add_text_file("main_c_rendered", rendered_template)
View File
Binary file not shown.
+25 -25
View File
@@ -54,10 +54,18 @@ def main():
else: else:
setup_logging(logging.INFO) setup_logging(logging.INFO)
# IN:
# Shellcode: filename
# Inject: filename
settings.injectable_base = args.inject
settings.payload_base = args.shellcode
# Cleanup
settings.try_start_final_infected_exe = args.start settings.try_start_final_infected_exe = args.start
settings.cleanup_files_on_start = not args.no_clean_at_start settings.cleanup_files_on_start = not args.no_clean_at_start
settings.cleanup_files_on_exit =not args.no_clean_at_exit settings.cleanup_files_on_exit =not args.no_clean_at_exit
# Settings
settings.fix_missing_iat = not args.no_fix_iat settings.fix_missing_iat = not args.no_fix_iat
if args.guardrail: if args.guardrail:
settings.plugin_guardrail = args.guardrail settings.plugin_guardrail = args.guardrail
@@ -71,13 +79,6 @@ def main():
settings.plugin_guardrail_data_key, settings.plugin_guardrail_data_key,
settings.plugin_guardrail_data_value)) settings.plugin_guardrail_data_value))
# Shellcode: filename
# Inject: filename
settings.init_payload_injectable(
shellcode=FilePath(args.shellcode),
injectable=FilePath(args.inject),
dll_func="")
settings.decoder_style = args.decoder settings.decoder_style = args.decoder
settings.carrier_name = args.carrier settings.carrier_name = args.carrier
settings.payload_location = PayloadLocation.CODE # makes sense settings.payload_location = PayloadLocation.CODE # makes sense
@@ -89,11 +90,10 @@ def main():
settings.carrier_invoke_style = CarrierInvokeStyle.BackdoorCallInstr settings.carrier_invoke_style = CarrierInvokeStyle.BackdoorCallInstr
settings.plugin_antiemulation = args.antiemulation settings.plugin_antiemulation = args.antiemulation
if not os.path.exists(settings.main_dir): if not os.path.exists(settings.project_path):
logger.info("Creating project directory: {}".format(settings.main_dir)) logger.info("Creating project directory: {}".format(settings.project_path))
os.makedirs(settings.main_dir) os.makedirs(settings.project_path)
write_webproject("default", settings)
exit_code = start(settings) exit_code = start(settings)
exit(exit_code) exit(exit_code)
@@ -124,7 +124,7 @@ def start(settings: Settings) -> int:
ret = start_real(settings) ret = start_real(settings)
except Exception as e: except Exception as e:
logger.error(f'Error compiling: {e}') logger.error(f'Error compiling: {e}')
observer.write_logs(settings.main_dir) observer.write_logs(settings.project_path)
return 1 return 1
# Cleanup files # Cleanup files
@@ -133,16 +133,16 @@ def start(settings: Settings) -> int:
clean_files(settings) clean_files(settings)
# Write logs (on success) # Write logs (on success)
observer.write_logs(settings.main_dir) observer.write_logs(settings.project_path)
return ret return ret
def sanity_checks(settings): def sanity_checks(settings):
if 'dll_loader' in settings.carrier_name: if 'dll_loader' in settings.carrier_name:
if not settings.payload_path.endswith(".dll"): if not settings.get_payload_path().endswith(".dll"):
raise Exception("dll loader requires a dll as payload, not shellcode") raise Exception("dll loader requires a dll as payload, not shellcode")
else: else:
if not settings.payload_path.endswith(".bin"): if not settings.get_payload_path().endswith(".bin"):
raise Exception("loader requires shellcode as payload, not DLL") raise Exception("loader requires shellcode as payload, not DLL")
@@ -158,7 +158,7 @@ def start_real(settings: Settings) -> bool:
# CHECK if 64 bit # CHECK if 64 bit
if not project.injectable.superpe.is_64(): if not project.injectable.superpe.is_64():
raise Exception("Binary is not 64bit: {}".format(project.settings.inject_exe_in)) raise Exception("Binary is not 64bit: {}".format(project.settings.get_inject_exe_in()))
# Tell user if they attempt to do something stupid # Tell user if they attempt to do something stupid
sanity_checks(project.settings) sanity_checks(project.settings)
@@ -192,8 +192,8 @@ def start_real(settings: Settings) -> bool:
if settings.generate_asm_from_c: if settings.generate_asm_from_c:
try: try:
phases.compiler.compile( phases.compiler.compile(
c_in = settings.main_c_path, c_in = settings.project_c_path,
asm_out = settings.main_asm_path, asm_out = settings.project_asm_path,
injectable = project.injectable, injectable = project.injectable,
settings = project.settings) settings = project.settings)
except ChildProcessError as e: except ChildProcessError as e:
@@ -212,8 +212,8 @@ def start_real(settings: Settings) -> bool:
# ASSEMBLE: Assemble .asm to .shc (ASM -> SHC) # ASSEMBLE: Assemble .asm to .shc (ASM -> SHC)
carrier_shellcode: bytes = phases.assembler.asm_to_shellcode( carrier_shellcode: bytes = phases.assembler.asm_to_shellcode(
asm_in = settings.main_asm_path, asm_in = settings.project_asm_path,
build_exe = settings.main_exe_path) build_exe = settings.project_exe_path)
observer.add_code_file("carrier_shc", carrier_shellcode) observer.add_code_file("carrier_shc", carrier_shellcode)
# INJECT loader into an exe and do IAT & data references. Big task. # INJECT loader into an exe and do IAT & data references. Big task.
@@ -227,13 +227,13 @@ def start_real(settings: Settings) -> bool:
injector.inject_exe() injector.inject_exe()
except Exception as e: except Exception as e:
return False return False
#observer.add_code_file("exe_final", extract_code_from_exe_file_ep(settings.inject_exe_out, 300)) #observer.add_code_file("exe_final", extract_code_from_exe_file_ep(settings.get_inject_exe_out(), 300))
# Check binary with avred # Check binary with avred
if config.get("avred_server") != "": if config.get("avred_server") != "":
if settings.verify or settings.try_start_final_infected_exe: if settings.verify or settings.try_start_final_infected_exe:
filename = os.path.basename(settings.inject_exe_in) filename = os.path.basename(settings.get_inject_exe_in())
with open(settings.inject_exe_out, "rb") as f: with open(settings.get_inject_exe_out(), "rb") as f:
data = f.read() data = f.read()
scannerDetectsBytes(data, filename, useBrotli=True, verify=settings.verify) scannerDetectsBytes(data, filename, useBrotli=True, verify=settings.verify)
else: else:
@@ -241,12 +241,12 @@ def start_real(settings: Settings) -> bool:
if settings.verify: if settings.verify:
logger.info(" Verify infected exe") logger.info(" Verify infected exe")
payload_exit_code = phases.injector.verify_injected_exe( payload_exit_code = phases.injector.verify_injected_exe(
settings.inject_exe_out, settings.get_inject_exe_out(),
dllfunc=settings.dllfunc) dllfunc=settings.dllfunc)
if payload_exit_code != 0: if payload_exit_code != 0:
logger.warning("Payload exit code: {}".format(payload_exit_code)) logger.warning("Payload exit code: {}".format(payload_exit_code))
elif settings.try_start_final_infected_exe: elif settings.try_start_final_infected_exe:
run_exe(settings.inject_exe_out, dllfunc=settings.dllfunc, check=False) run_exe(settings.get_inject_exe_out(), dllfunc=settings.dllfunc, check=False)
if settings.plugin_guardrail != "none": if settings.plugin_guardrail != "none":
logger.warning("! Remember your guardrails settings when testing") logger.warning("! Remember your guardrails settings when testing")
-2
View File
@@ -7,7 +7,6 @@ import logging
from app.views import views from app.views import views
from app.views_project import views_project from app.views_project import views_project
from app.views_shcdev import views_shcdev
from log import setup_logging from log import setup_logging
from utils import check_deps from utils import check_deps
@@ -42,5 +41,4 @@ if __name__ == "__main__":
app.register_blueprint(views) app.register_blueprint(views)
app.register_blueprint(views_project) app.register_blueprint(views_project)
app.register_blueprint(views_shcdev)
app.run(host=args.listenip, port=args.listenport, debug=args.debug) app.run(host=args.listenip, port=args.listenport, debug=args.debug)