From 9ff677310e0460f496f76a642723903d8d081163 Mon Sep 17 00:00:00 2001 From: Dobin Date: Thu, 22 Feb 2024 19:45:35 +0000 Subject: [PATCH] feature: use my own masmshc implementation --- phases/compiler.py | 29 +++++--- phases/masmshc.py | 168 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 10 deletions(-) create mode 100644 phases/masmshc.py diff --git a/phases/compiler.py b/phases/compiler.py index ff91f7a..3452c53 100644 --- a/phases/compiler.py +++ b/phases/compiler.py @@ -45,7 +45,7 @@ def compile( # Assembly cleanup (masm_shc) asm_clean_file = asm_out + ".clean" logger.info("---[ ASM masm_shc: {} ".format(asm_out)) - if False: + if True: params = Params(asm_out, asm_clean_file, True, True, True) process_file(params) else: @@ -87,7 +87,7 @@ def fixup_asm_file(filename: FilePath, payload_len: int, short_call_patching: bo # Remove EXTRN, we dont need it # Even tho it is part of IAT_REUSE process (see fixup_iat_reuse()) if "EXTRN __imp_" in lines[idx]: - lines[idx] = "; " + lines[idx] + lines[idx] = "; " + lines[idx] +"\r\n" # replace external reference with shellcode reference for idx, line in enumerate(lines): @@ -104,14 +104,14 @@ def fixup_asm_file(filename: FilePath, payload_len: int, short_call_patching: bo ) lines[idx] = lines[idx].replace( "QWORD PTR supermega_payload", - "[shcstart] ; get payload shellcode address" + "[shcstart] ; get payload shellcode address\r\n" ) # add label at end of code for idx, line in enumerate(lines): if lines[idx].startswith("END"): logger.info(" > Add end of code label at line: {}".format(idx)) - lines.insert(idx-1, "shcstart: ; start of payload shellcode") + lines.insert(idx-1, "shcstart: ; start of payload shellcode"+"\r\n") break with open(filename, 'w', newline='\r\n') as asmfile: # write back with CRLF @@ -128,16 +128,25 @@ def get_function_stubs(asm_in: FilePath): # EXTRN __imp_GetEnvironmentVariableW:PROC for line in lines: - if "EXTRN __imp_" in line: + if "QWORD PTR __imp_" in line: a = line a = a.split("__imp_")[1] - a = a.split(":PROC")[0] - func_name = a - #func_name = line.strip("\r\n ") - #func_name = line.replace("EXTRN\t__imp_", "") - #func_name = line.replace(":PROC", "") + func_name = a.strip("\r\n") + print("-----> {}".format(func_name)) functions.append(func_name) + if False: + if "EXTRN __imp_" in line: + a = line + a = a.split("__imp_")[1] + a = a.split(":PROC")[0] + func_name = a + #func_name = line.strip("\r\n ") + #func_name = line.replace("EXTRN\t__imp_", "") + #func_name = line.replace(":PROC", "") + print("-----> {}".format(func_name)) + functions.append(func_name) + return functions diff --git a/phases/masmshc.py b/phases/masmshc.py new file mode 100644 index 0000000..e925d6f --- /dev/null +++ b/phases/masmshc.py @@ -0,0 +1,168 @@ +import re +import os + +VERSION = "0.3" +g_is32bit = False + +class Params: + def __init__(self, infile, outfile, inline_strings, remove_crt, append_rsp_stub): + self.infile = infile + self.outfile = outfile + self.inline_strings = inline_strings + self.remove_crt = remove_crt + self.append_rsp_stub = append_rsp_stub + +def has_token(tokens, token): + return token in tokens + +def get_constant(consts_lines, tokens_line): + for const_name, line in consts_lines.items(): + if any(token in tokens_line for token in [const_name]): + return const_name + return "" + +def split_to_tokens(line): + line = re.sub(r"[\t]", " ", line) + tokens = line.split() + for token in tokens: + token = token.lstrip("FLAT:") + return tokens + +def append_align_rsp(ofile): + stub = """ +PUBLIC AlignRSP +_TEXT SEGMENT +; AlignRSP - by @mattifestation (http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html) +; AlignRSP is a simple call stub that ensures that the stack is 16-byte aligned prior +; to calling the entry point of the payload.This is necessary because 64-bit functions +; in Windows assume that they were called with 16-byte stack alignment.When amd64 +; shellcode is executed, you can't be assured that you stack is 16-byte aligned. For example, +; if your shellcode lands with 8-byte stack alignment, any call to a Win32 function will likely +; crash upon calling any ASM instruction that utilizes XMM registers(which require 16-byte) +; alignment. + +AlignRSP PROC +push rsi ; Preserve RSI since we're stomping on it +mov rsi, rsp ; Save the value of RSP so it can be restored +and rsp, 0FFFFFFFFFFFFFFF0h ; Align RSP to 16 bytes +sub rsp, 020h ; Allocate homing space for ExecutePayload +call main ; Call the entry point of the payload +mov rsp, rsi ; Restore the original value of RSP +pop rsi ; Restore RSI +ret ; Return to caller + +AlignRSP ENDP + +_TEXT ENDS + +""" + ofile.write(stub) + +def process_file(params): + global g_is32bit + try: + with open(params.infile, "r") as file, open(params.outfile, "w") as ofile: + consts_lines = {} + seg_name = "" + const_name = "" + code_start = False + + line_count = 0 + for line in file.readlines(): + #for line_count, line in enumerate(file): + tokens = split_to_tokens(line) + + #print("Tokens: {}".format(" ".join(tokens))) + + if not tokens: + ofile.write(line) + continue + + if tokens[0] == ".686P": + g_is32bit = True + + if tokens[0] == "EXTRN": + print(f"[ERROR] Line {line_count + 1}: External dependency detected:\n{line}") + + in_skipped = False + in_const = False + + if len(tokens) >= 2: + if tokens[1] == "SEGMENT": + seg_name = tokens[0] + if not code_start and seg_name == "_TEXT": + code_start = True + if g_is32bit: + ofile.write("assume fs:nothing\n") + elif params.append_rsp_stub: + append_align_rsp(ofile) + print("[INFO] Entry Point: AlignRSP") + + if seg_name == "_BSS": + print(f"[ERROR] Line {line_count + 1}: _BSS segment detected! Remove all global and static variables!\n") + + if seg_name in ("pdata", "xdata", "voltbl"): + in_skipped = True + elif seg_name in ("CONST", "_DATA"): + in_const = True + elif tokens[1] == "ENDS" and tokens[0] == seg_name: + seg_name = "" + if in_const: + continue + + if in_skipped: + continue + + if params.remove_crt and tokens[0] == "INCLUDELIB": + if tokens[1] in ("LIBCMT", "OLDNAMES"): + ofile.write(f"; {line}\n") # copy commented out line + continue + print(f"[ERROR] Line {line_count + 1}: INCLUDELIB detected! Remove all external dependencies!\n") + + if params.inline_strings and in_const: + if tokens[1] == "DB": + const_name = tokens[0] + if const_name != "": + if const_name not in consts_lines: + consts_lines[const_name] = line + else: + consts_lines[const_name] += "\n" + line + continue + + if tokens[0] == "rex_jmp": + line = re.sub(r"rex_jmp", "JMP", line) + + curr_const = get_constant(consts_lines, tokens) + if params.inline_strings and curr_const != "": + label_after = f"after_{curr_const}" + ofile.write(f"\tCALL {label_after}\n") + ofile.write(consts_lines[curr_const] + "\n") + ofile.write(f"{label_after}:\n") + if len(tokens) > 2 and (tokens[0] in ("lea", "mov")): + offset_index = tokens.index("OFFSET", 1) + instructions = tokens[1] + if offset_index == 4: + instructions = f"{tokens[1]} {tokens[2]} {tokens[3]}" + ofile.write(f"\tPOP {instructions}\n") + ofile.write("\n") + ofile.write(f"; {line}\n") # copy commented out line + continue + + if not g_is32bit and any(token in tokens for token in ["gs:96"]): + #line = re.sub(r"gs:96", "gs[96]\r\n", line) + line = line.replace("gs:96", "gs:[96]") + + ofile.write(line) # copy line + + except FileNotFoundError as e: + print(f"[ERROR] {e}") + return False + + if params.inline_strings: + print("[INFO] Strings have been inlined. It may require to change some short jumps (jmp SHORT) into jumps (jmp)") + return True + +if __name__ == "__main__": + # Example usage + params = Params("test.asm", "testout.asm", True, True, True) + process_file(params) \ No newline at end of file