From fc64068139ff52872534bb8d5d1ecfb36feef047 Mon Sep 17 00:00:00 2001 From: Dobin Rutishauser Date: Sun, 23 Jun 2024 17:07:21 +0200 Subject: [PATCH] refactor: put *manager back to superpe again --- model/rangemanager.py | 3 +- pe/superpe.py | 80 +++++++++++++++++++++++++++++++++++ phases/injector.py | 92 +++-------------------------------------- tests/test_datareuse.py | 4 +- 4 files changed, 90 insertions(+), 89 deletions(-) diff --git a/model/rangemanager.py b/model/rangemanager.py index 94b0f94..821e32f 100644 --- a/model/rangemanager.py +++ b/model/rangemanager.py @@ -11,7 +11,8 @@ class RangeManager: self.min = min self.max = max - self.intervals.add(Interval(0, min)) + if min > 0: + self.intervals.add(Interval(0, min)) def merge_overlaps(self): diff --git a/pe/superpe.py b/pe/superpe.py index e13f7be..d25e5d6 100644 --- a/pe/superpe.py +++ b/pe/superpe.py @@ -5,6 +5,7 @@ from typing import List, Dict import random from model.defs import * +from model.rangemanager import RangeManager logger = logging.getLogger("superpe") @@ -321,6 +322,66 @@ class SuperPe(): offset, func_name, new_name_bytes.decode())) self.pe.set_bytes_at_offset(offset, new_name_bytes) + ## .rdata manager + def get_rdata_rangemanager(self) -> RangeManager: + section = self.get_section_by_name(".rdata") + relocs = self.get_relocations_for_section(".rdata") + rm = RangeManager(section.virt_addr, section.virt_addr + section.virt_size) + for reloc in relocs: + # Reloc destination is probably 8 bytes + # But i add another 8 to skip over small holes (common in .rdata) + rm.add_range(reloc.rva, reloc.rva + 8 + 8) + + if True: # FIXME this is a hack which is sometimes necessary? + sect_data_copy = section.pefile_section.get_data() + string_off = find_first_utf16_string_offset(sect_data_copy) + if string_off == None: + raise Exception("Strings not found in .rdata section, abort") + if string_off < 128: + logging.debug("weird: Strings in .rdata section at offset {} < 100".format(string_off)) + string_off = 128 + rm.add_range(section.virt_addr, section.virt_addr + string_off) + + rm.merge_overlaps() + return rm + + + def get_relocations_for_section(self, section_name: str) -> List[PeRelocEntry]: + section: PeSection = self.get_section_by_name(section_name) + ret = [] + if section is None: + return ret + for reloc in self.get_base_relocs(): + reloc_addr = reloc.rva + if reloc_addr >= section.virt_addr and reloc_addr < section.virt_addr + section.virt_size: + #logger.info("ADDR: 0x{:X}".format(reloc_addr)) + ret.append(reloc) + return ret + + + def get_code_rangemanager(self) -> RangeManager: + code_section = self.get_code_section() + if code_section == None: + raise Exception('Could not find code section in input PE file!') + code_section_size = code_section.Misc_VirtualSize + + # Restrictions for putting data into .text: + # - enough space for carrier + payload + # - avoid overwriting entry point function + # - carrier should not generate much smaller holes in .text + + rm = RangeManager( + code_section.VirtualAddress, + code_section.VirtualAddress + code_section_size) + + # protect entrypoint a bit + entrypoint_rva = self.get_entrypoint() + rm.add_range( + entrypoint_rva - 0x100, + entrypoint_rva + 0x100) + + return rm + ## Helpers @@ -351,3 +412,22 @@ class SuperPe(): self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0 self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_SECURITY].Size = 0 + + + +def find_first_utf16_string_offset(data, min_len=8): + current_string = bytearray() + start_offset = None # To keep track of the start of the current string + for i in range(0, len(data) - 1, 2): + # Check if we have a valid character + if data[i] != 0 or data[i+1] != 0: + if start_offset is None: # Mark the start of a new string + start_offset = i + current_string += bytes([data[i], data[i+1]]) + else: + if len(current_string) >= min_len * 2: # Check if the current string meets the minimum length + return start_offset # Return the offset where the string starts + current_string = bytearray() + start_offset = None # Reset start offset for the next string + + return None # No string found that meets the criteria \ No newline at end of file diff --git a/phases/injector.py b/phases/injector.py index 87d9930..879505f 100644 --- a/phases/injector.py +++ b/phases/injector.py @@ -35,76 +35,15 @@ class Injector(): self.superpe = SuperPe(settings.inject_exe_in) self.function_backdoorer = FunctionBackdoorer(self.superpe) - # to find space for reference data of carrier (and possibly payload) - self.rdata_rangemanager = self.init_rdata_rangemanager() - # to find space for carrier and payload self.payload_rva = None self.carrier_rva = None - self.init_code_rangemanager() + self.init_addresses() - ## .rdata rangemanager (relocations and reference data) + def init_addresses(self): + rm = self.superpe.get_code_rangemanager() - def init_rdata_rangemanager(self) -> RangeManager: - section = self.superpe.get_section_by_name(".rdata") - relocs = self.get_relocations_for_section(".rdata") - rm = RangeManager(section.virt_addr, section.virt_addr + section.virt_size) - for reloc in relocs: - # Reloc destination is probably 8 bytes - # But i add another 8 to skip over small holes (common in .rdata) - rm.add_range(reloc.rva, reloc.rva + 8 + 8) - - if True: # FIXME this is a hack which is sometimes necessary? - sect_data_copy = section.pefile_section.get_data() - string_off = find_first_utf16_string_offset(sect_data_copy) - if string_off == None: - raise Exception("Strings not found in .rdata section, abort") - if string_off < 128: - logging.debug("weird: Strings in .rdata section at offset {} < 100".format(string_off)) - string_off = 128 - rm.add_range(section.virt_addr, section.virt_addr + string_off) - - rm.merge_overlaps() - return rm - - - def get_relocations_for_section(self, section_name: str) -> List[PeRelocEntry]: - section: PeSection = self.superpe.get_section_by_name(section_name) - ret = [] - if section is None: - return ret - for reloc in self.superpe.get_base_relocs(): - reloc_addr = reloc.rva - if reloc_addr >= section.virt_addr and reloc_addr < section.virt_addr + section.virt_size: - #logger.info("ADDR: 0x{:X}".format(reloc_addr)) - ret.append(reloc) - return ret - - - ## .text rangemanager - - def init_code_rangemanager(self) -> RangeManager: - code_section = self.superpe.get_code_section() - if code_section == None: - raise Exception('Could not find code section in input PE file!') - code_section_size = code_section.Misc_VirtualSize - - # Restrictions for putting data into .text: - # - enough space for carrier + payload - # - avoid overwriting entry point function - # - carrier should not generate much smaller holes in .text - - rm = RangeManager( - code_section.VirtualAddress, - code_section.VirtualAddress + code_section_size) - - # protect entrypoint a bit - entrypoint_rva = self.superpe.get_entrypoint() - rm.add_range( - entrypoint_rva - 0x100, - entrypoint_rva + 0x100) - # TECHNIQUE0: # assume payload is big, find a place for it, then prepend carrier (small) complete_size = len(self.carrier_shc) + len(self.payload.payload_data) + 4096 @@ -128,8 +67,6 @@ class Injector(): # prepend it a bit self.carrier_rva = offset - len(self.payload.payload_data) - 4096 - return rm - ## Inject @@ -293,13 +230,14 @@ class Injector(): datareuse_fixup.addr, payload_rva, datareuse_fixup.string_ref, len(datareuse_fixup.data))) else: # .rdata + rdata_manager = self.superpe.get_rdata_rangemanager() # get a hole in the .rdata section to put our data - hole_rva = self.rdata_rangemanager.find_hole(len(datareuse_fixup.data)) + hole_rva = rdata_manager.find_hole(len(datareuse_fixup.data)) if hole_rva == None: raise Exception("No suitable hole with size {} found in .rdata section, abort".format( len(datareuse_fixup.data) )) - self.rdata_rangemanager.add_range(hole_rva[0], hole_rva[1]+1) # mark it as used + rdata_manager.add_range(hole_rva[0], hole_rva[1]+1) # mark it as used var_data = datareuse_fixup.data data_rva = hole_rva[0] @@ -352,21 +290,3 @@ def verify_injected_exe(exefile: FilePath, dllfunc="") -> int: else: logger.error("---> Verify FAIL. Infected exe does not work (no file created)") return 1 - - -def find_first_utf16_string_offset(data, min_len=8): - current_string = bytearray() - start_offset = None # To keep track of the start of the current string - for i in range(0, len(data) - 1, 2): - # Check if we have a valid character - if data[i] != 0 or data[i+1] != 0: - if start_offset is None: # Mark the start of a new string - start_offset = i - current_string += bytes([data[i], data[i+1]]) - else: - if len(current_string) >= min_len * 2: # Check if the current string meets the minimum length - return start_offset # Return the offset where the string starts - current_string = bytearray() - start_offset = None # Reset start offset for the next string - - return None # No string found that meets the criteria \ No newline at end of file diff --git a/tests/test_datareuse.py b/tests/test_datareuse.py index 36a353a..0e1ef93 100644 --- a/tests/test_datareuse.py +++ b/tests/test_datareuse.py @@ -49,7 +49,7 @@ class DataReuseTest(unittest.TestCase): def test_relocmanager(self): """Test reference EXE reloc manager information""" superpe = SuperPe(PATH_EXES + "procexp64.exe") - rm = superpe.get_rdata_relocmanager() + rm = superpe.get_rdata_rangemanager() self.assertEqual(61, len(rm.intervals)) # 0x1ab0 is magic currently (should use find_first_utf16_string_offset() hole = rm.find_hole(20) @@ -58,7 +58,7 @@ class DataReuseTest(unittest.TestCase): def test_largestgap(self): superpe = SuperPe(PATH_EXES + "7z.exe") - rm = superpe.get_rdata_relocmanager() + rm = superpe.get_rdata_rangemanager() start, stop = rm.find_hole(100) self.assertEqual(394513, start) self.assertEqual(396511, stop)