From f08334dc1a01362c5dd4b679ac19a66505c9aae3 Mon Sep 17 00:00:00 2001 From: Dobin Date: Tue, 26 Mar 2024 17:46:09 +0000 Subject: [PATCH] feature: dev (shellcode projects) phase 1 --- .gitignore | 3 +- app/templates/dev.html | 31 +++++++++++++ app/templates/devs.html | 23 ++++++++++ app/templates/index.html | 8 ---- app/templates/navigation.html | 15 ++++++- app/templates/projects.html | 22 +++++++++ app/views.py | 84 +++++++++++++++++++++++++++++++++++ helper.py | 47 ++++---------------- log.py | 3 ++ observer.py | 6 ++- pe/derbackdoorer.py | 2 +- pe/r2helper.py | 2 +- pe/superpe.py | 2 +- phases/compiler.py | 41 +++++++++++++++++ supermega.py | 2 +- utils.py | 49 ++++++++++++++++++++ 16 files changed, 285 insertions(+), 55 deletions(-) create mode 100644 app/templates/dev.html create mode 100644 app/templates/devs.html create mode 100644 app/templates/projects.html create mode 100644 utils.py diff --git a/.gitignore b/.gitignore index 5d24cf8..5240295 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ tools/ doc/ *.pickle logs/ -app/projects/* \ No newline at end of file +app/projects/* +data/dev/* \ No newline at end of file diff --git a/app/templates/dev.html b/app/templates/dev.html new file mode 100644 index 0000000..0939f07 --- /dev/null +++ b/app/templates/dev.html @@ -0,0 +1,31 @@ + + + + {% include 'header.html' %} + + + {% include 'navigation.html' %} + +
+ +

ShcDev: {{name}}

+ + + {% for file in files %} + + + + + + {% endfor %} +
{{ file['name']}}{{file["date"]}}{{file["info"]}}
+ + Build + +

+ +
{{log}}
+ +
+ + \ No newline at end of file diff --git a/app/templates/devs.html b/app/templates/devs.html new file mode 100644 index 0000000..84eee5b --- /dev/null +++ b/app/templates/devs.html @@ -0,0 +1,23 @@ + + + + {% include 'header.html' %} + + + {% include 'navigation.html' %} + +
+ +

ShcDevs:

+ + + +
+ + \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html index 8b45358..211bb7f 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -10,14 +10,6 @@

SuperMega

- - - - Add Project \ No newline at end of file diff --git a/app/templates/navigation.html b/app/templates/navigation.html index db30bcb..7429231 100644 --- a/app/templates/navigation.html +++ b/app/templates/navigation.html @@ -14,7 +14,20 @@ diff --git a/app/templates/projects.html b/app/templates/projects.html new file mode 100644 index 0000000..f7ab355 --- /dev/null +++ b/app/templates/projects.html @@ -0,0 +1,22 @@ + + + + {% include 'header.html' %} + + + {% include 'navigation.html' %} + +
+ +

Projects

+ + + + Add Project +
+ + \ No newline at end of file diff --git a/app/views.py b/app/views.py index 139f062..6e8fdef 100644 --- a/app/views.py +++ b/app/views.py @@ -11,7 +11,10 @@ import difflib from ansi2html import Ansi2HTMLConverter import shutil import subprocess +import time +from datetime import datetime +from observer import observer from config import config from model.settings import Settings from model.defs import * @@ -19,7 +22,10 @@ from supermega import start from app.storage import storage, Project from sender import scannerDetectsBytes from phases.injector import verify_injected_exe +from phases.compiler import compile_dev +from phases.assembler import asm_to_shellcode from helper import run_process_checkret +from log import getlog views = Blueprint('views', __name__) @@ -37,6 +43,84 @@ def index(): return render_template('index.html', data=storage.data) +@views.route("/projects") +def projects_route(): + return render_template('projects.html', data=storage.data) + + +@views.route("/dev") +def devs_route(): + data = [] + path = "data/dev" + for file_path in os.listdir(path): + creation_time = os.path.getctime("data/dev" + "/" + file_path) + readable_time = datetime.fromtimestamp(creation_time).strftime('%Y-%m-%d %H:%M:%S') + data.append({ + "name": file_path, + "date": readable_time, + }) + return render_template('devs.html', data=data) + + +@views.route("/dev/") +def dev_route(name): + data = [] + log = "" + path = "data/dev/{}".format(name) + for file_path in os.listdir(path): + creation_time = os.path.getctime(path + "/" + file_path) + readable_time = datetime.fromtimestamp(creation_time).strftime('%Y-%m-%d %H:%M:%S') + + info = "" + if file_path.endswith(".asm"): + info = "text assembly (cleaned, from compiled .c)" + elif file_path.endswith(".bin"): + info = "generated shellcode (from .exe)" + elif file_path.endswith(".c"): + info = "input C code" + elif file_path.endswith(".exe"): + info = "temporary shellcode holder (from .c)" + elif file_path.endswith(".log"): + info = "log file" + with open(path + "/" + file_path, "r") as f: + log = f.read() + + print(log) + + data.append({ + "name": file_path, + "date": readable_time, + "info": info, + }) + + return render_template('dev.html', + name=name, files=data, log=log) + + +@views.route("/dev//build") +def dev_build_route(name): + + c_in = "data/dev/{}/main.c".format(name) + asm_out = "data/dev/{}/main.asm".format(name) + build_exe = "data/dev/{}/main.exe".format(name) + shellcode_out = "data/dev/{}/main.bin".format(name) + log = "data/dev/{}/main.log".format(name) + + compile_dev(c_in, asm_out) + asm_to_shellcode(asm_out, build_exe, shellcode_out) + + with open(log, "w") as f: + for log_line in getlog(): + f.write("{}\n".format(log_line)) + + f.write("\n\n") + + for log in observer.logs: + f.write("{}".format(log)) + + return redirect("/dev/{}".format(name), code=302) + + @views.route("/project/") def project(name): project = storage.get_project(name) diff --git a/helper.py b/helper.py index 3b9b134..4d4140c 100644 --- a/helper.py +++ b/helper.py @@ -6,6 +6,7 @@ import logging from config import config from model.defs import * +from observer import observer logger = logging.getLogger("Helper") @@ -48,22 +49,28 @@ def run_process_checkret(args, check=True): except Exception as e: logger.warn(f"An error occurred: {e}") # Handle other exceptions - + with open(f"{logs_dir}/cmdoutput.log", "ab") as f: cmd = "------------------------------------\n" cmd += "--- " + " ".join(args) + "\n" f.write(cmd.encode('utf-8')) if ret.stdout != None: + observer.add_log(ret.stdout.decode('utf-8')) f.write(ret.stdout) if ret.stderr != None: + observer.add_log(ret.stderr.decode('utf-8')) f.write(ret.stderr) + if ret.returncode != 0 and check: logger.info("----! FAILED Command: {}".format(" ".join(args))) if ret.stdout != None: + observer.add_log(ret.stdout.decode('utf-8')) logger.info(ret.stdout.decode('utf-8')) if ret.stderr != None: + observer.add_log(ret.stderr.decode('utf-8')) logger.info(ret.stderr.decode('utf-8')) raise Exception("Command failed: " + " ".join(args)) + if config.ShowCommandOutput: logger.info("> " + " ".join(args)) if ret.stdout != None: @@ -92,16 +99,6 @@ def file_readall_binary(filepath) -> bytes: return data -def delete_all_files_in_directory(directory_path): - files = glob.glob(os.path.join(directory_path, '*')) - for file_path in files: - try: - os.remove(file_path) - #logger.info(f"Deleted {file_path}") - except Exception as e: - logger.info(f"Error deleting {file_path}: {e}") - - def rbrunmode_str(rbrunmode): rbrunmode = str(rbrunmode) if rbrunmode == "1": @@ -112,34 +109,6 @@ def rbrunmode_str(rbrunmode): return "Invalid: {}".format(rbrunmode) -def hexdump(data, addr = 0, num = 0): - s = '' - n = 0 - lines = [] - if num == 0: num = len(data) - - if len(data) == 0: - return '' - - for i in range(0, num, 16): - line = '' - line += '%04x | ' % (addr + i) - n += 16 - - for j in range(n-16, n): - if j >= len(data): break - line += '%02x ' % (data[j] & 0xff) - - line += ' ' * (3 * 16 + 7 - len(line)) + ' | ' - - for j in range(n-16, n): - if j >= len(data): break - c = data[j] if not (data[j] < 0x20 or data[j] > 0x7e) else '.' - line += '%c' % c - - lines.append(line) - return '\n'.join(lines) - def file_to_lf(filename): with open(filename, 'rb') as f: diff --git a/log.py b/log.py index 8a87875..377b910 100644 --- a/log.py +++ b/log.py @@ -52,6 +52,9 @@ def writelog(): for line in log_messages: f.write(line + "\n") +def getlog(): + return log_messages + def setup_logging(level = logging.INFO): root_logger = logging.getLogger() root_logger.setLevel(level) diff --git a/observer.py b/observer.py index 9356374..8c16d59 100644 --- a/observer.py +++ b/observer.py @@ -2,9 +2,8 @@ import json import pprint from capstone import Cs, CS_ARCH_X86, CS_MODE_64 -from model import * from pe.r2helper import r2_disas -from helper import delete_all_files_in_directory +from utils import delete_all_files_in_directory from model.defs import * @@ -18,6 +17,9 @@ class Observer(): self.logs = [] self.idx = 0 + def add_log(self, log): + self.logs.append(log) + def add_text(self, name, data): self.write_to_file(name + ".txt", data) self.idx += 1 diff --git a/pe/derbackdoorer.py b/pe/derbackdoorer.py index 50f13cd..3d6de2f 100644 --- a/pe/derbackdoorer.py +++ b/pe/derbackdoorer.py @@ -11,7 +11,7 @@ import keystone from enum import IntEnum import logging -from helper import hexdump +from utils import hexdump from pe.superpe import SuperPe from model.defs import * diff --git a/pe/r2helper.py b/pe/r2helper.py index d7cfb21..36a6f6d 100644 --- a/pe/r2helper.py +++ b/pe/r2helper.py @@ -2,7 +2,7 @@ import r2pipe import os from model.defs import * -from helper import hexdump +from utils import hexdump def r2_disas(data: bytes): filename = "r2_data.bin" diff --git a/pe/superpe.py b/pe/superpe.py index 413cbc8..dcc65b6 100644 --- a/pe/superpe.py +++ b/pe/superpe.py @@ -4,7 +4,7 @@ from enum import IntEnum import logging from typing import List -from helper import hexdump +from utils import hexdump from model.defs import * logger = logging.getLogger("superpe") diff --git a/phases/compiler.py b/phases/compiler.py index e8612b5..c5c95e3 100644 --- a/phases/compiler.py +++ b/phases/compiler.py @@ -16,6 +16,47 @@ from model.exehost import ExeHost logger = logging.getLogger("Compiler") use_templates = True +# NOTE: Mostly copy-pasted from compiler.py::compile() +def compile_dev( + c_in: FilePath, + asm_out: FilePath, + short_call_patching: bool = False, +): + logger.info("--[ Compile C to ASM: {} -> {} ".format(c_in, asm_out)) + + # Compile C To Assembly (text) + run_process_checkret([ + config.get("path_cl"), + "/c", + "/FA", + "/GS-", + "/Fa{}/".format(os.path.dirname(c_in)), + c_in, + ]) + if not os.path.isfile(asm_out): + raise Exception("Error: Compiling failed") + file_to_lf(asm_out) + observer.add_text("carrier_asm_orig", file_readall_text(asm_out)) + + # Assembly cleanup (masm_shc) + asm_clean_file = asm_out + ".clean" + logger.info("---[ ASM masm_shc: {} ".format(asm_out)) + params = Params(asm_out, asm_clean_file, + inline_strings=False, # not for DATA_REUSE + remove_crt=True, + append_rsp_stub=True) # required atm + process_file(params) + + if not os.path.isfile(asm_clean_file): + raise Exception("Error: Cleaned up ASM file {} was not created".format( + asm_clean_file + )) + + # Move to destination we expect + shutil.move(asm_clean_file, asm_out) + if config.debug: + observer.add_text("carrier_asm_cleanup", file_readall_text(asm_out)) + def compile( c_in: FilePath, diff --git a/supermega.py b/supermega.py index 0236245..2d6d3ae 100644 --- a/supermega.py +++ b/supermega.py @@ -18,7 +18,7 @@ from model.project import Project from model.settings import Settings from model.defs import * from log import setup_logging, writelog - +from utils import delete_all_files_in_directory def main(): """Argument parsing for when called from command line""" diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..ce3ad07 --- /dev/null +++ b/utils.py @@ -0,0 +1,49 @@ +import subprocess +import os +import pathlib +import glob +import logging + +from config import config +from model.defs import * + +logger = logging.getLogger("Utils") + + +def delete_all_files_in_directory(directory_path): + files = glob.glob(os.path.join(directory_path, '*')) + for file_path in files: + try: + os.remove(file_path) + #logger.info(f"Deleted {file_path}") + except Exception as e: + logger.info(f"Error deleting {file_path}: {e}") + + +def hexdump(data, addr = 0, num = 0): + s = '' + n = 0 + lines = [] + if num == 0: num = len(data) + + if len(data) == 0: + return '' + + for i in range(0, num, 16): + line = '' + line += '%04x | ' % (addr + i) + n += 16 + + for j in range(n-16, n): + if j >= len(data): break + line += '%02x ' % (data[j] & 0xff) + + line += ' ' * (3 * 16 + 7 - len(line)) + ' | ' + + for j in range(n-16, n): + if j >= len(data): break + c = data[j] if not (data[j] < 0x20 or data[j] > 0x7e) else '.' + line += '%c' % c + + lines.append(line) + return '\n'.join(lines)