toolkit/bin/installer/generate-sections.py
2023-07-31 20:45:12 -03:00

474 lines
18 KiB
Python

# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 DSR! <xchwarze@gmail.com>
# Released under the terms of the MIT License
# Developed for Python 3.6+
# pip install py7zr pefile colorama
import argparse
import pathlib
import os
import re
import shutil
import py7zr
import pefile
import colorama
def get_pe_info(file_path):
IMAGE_FILE_MACHINE_AMD64 = 34404
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
try:
pe = pefile.PE(file_path, fast_load=True)
return {
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#machine-types
'is_x64': pe.FILE_HEADER.Machine == IMAGE_FILE_MACHINE_AMD64,
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#windows-subsystem
'is_cli': pe.OPTIONAL_HEADER.Subsystem == IMAGE_SUBSYSTEM_WINDOWS_CUI,
}
except:
return False
def component_name(name):
return re.sub('[^a-zA-Z0-9]', '', name).lower()
class GenerateInstall:
def __init__(self):
self.base_path = ''
self.section_name = ''
self.tool_name = ''
self.section_list = []
self.cli_list = []
self.valid_folders = [
'analysis', 'decompilers', 'dissasembler', 'hex editor', 'monitor',
'nfomaker', 'other', 'reverse', 'rootkits detector', 'unpacking'
]
self.fix_tool_exe_list = {
# fix to support main executable
'[dotnet] dnspyex': ['dnspy.exe'],
'ollydbg 1.10': ['ollydbg.exe'],
'astrogrep': ['astrogrep.exe'],
'rl!depacker': ['rl!depacker.exe'],
'sysanalyzer': ['sysanalyzer.exe'],
'system informer': ['systeminformer.exe'],
'keygener assistant': ['keyassistant.exe'],
'asm calculator': ['calc32.exe'],
# support also the x64 versions
'[autoit] unautoit': ['unautoit-windows-i686.exe', 'unautoit-windows-amd64.exe'],
'hxd': ['hxd32.exe', 'hxd64.exe'],
'api monitor': ['apimonitor-x86.exe', 'apimonitor-x64.exe'],
'autoruns': ['autoruns.exe', 'autoruns64.exe'],
'process explorer': ['procexp.exe', 'procexp64.exe'],
'procmon': ['procmon.exe', 'procmon64.exe'],
'regshot': ['regshot-x86-ansi.exe', 'regshot-x64-ansi.exe'],
'tcpview': ['tcpview.exe', 'tcpview64.exe'],
'process-dump': ['pd32.exe', 'pd64.exe'],
'scylla': ['scylla_x86.exe', 'scylla_x64.exe'],
'strings': ['strings.exe', 'strings64.exe'],
'dlest': ['dlest32.exe', 'dlest64.exe'],
'extremedumper': ['extremedumper-x86.exe', 'extremedumper.exe'],
'winapi search': ['winapisearch32.exe', 'winapisearch64.exe'],
'de4dot': ['de4dot.exe', 'de4dot-x64.exe'],
'netunpack': ['netunpack.exe', 'netunpack-64.exe'],
}
self.fix_tool_exe_link_creation = [
# analysis
'die', 'xapkdetector',
# dissasembler
'immunity debugger', 'ollydbg 1.10',
# other
'astrogrep', 'indetectables offset locator',
# reverse
'asm calculator', 'at4re patcher', 'dlest', 'dup', 'extremedumper', 'x64dbgpluginmanager',
# unpacking
'netunpack', 'qunpack', 'uniextract',
]
self.compact_tool_list = [
# analysis
'capa', 'die', 'exeinfope', 'pestudio', 'xapkdetector',
# decompilers
'[android] jadx', '[dotnet] ilspy', '[java] jd-gui', '[java] recaf',
# dissasembler
'x64dbg',
# hex editor
'hxd', 'imhex',
# monitor
'api monitor', 'pe-sieve', 'process explorer', 'system informer', 'procmon', 'tcpview',
# other
'apkstudio', 'floss', 'hashmyfiles', 'rawcap', 'resource hacker', 'virustotaluploader',
# reverse
'asm calculator', 'cryptotester', 'getsymbol', 'scylla', 'winapi search', 'x64dbgpluginmanager',
# rootkits detector
'gmer', 'sysinspector',
# unpacking
'uniextract', 'xvolkolak',
]
self.disable_unpack = [
# decompilers
'graywolf - 1.83.7z',
# dissasembler
'[++] w32dasm - 8.93.7z', '[10] w32dasm - 8.93.7z', '[original] w32dasm - 8.93.7z',
'[bradpach] w32dasm - 8.93.7z',
# other
'imprec - 1.7e.7z',
# patcher
'at4re patcher - 0.6.3.7z', 'skins.7z',
# unpacking
'qunpack - 2.2.7z', 'qunpack - 3.4.7z', 'qunpack - src.7z',
'vmunpacker - 1.2.7z', 'vmunpacker - 1.3.7z',
]
# helpers
def absolute_to_local_path(self, path):
return str(path).replace(f'{str(self.base_path)}\\', '')
def get_tool_icon(self):
tool_name_list = self.tool_name.split()
name = tool_name_list[1].lower() if self.tool_name[0] == '[' else self.tool_name.lower()
return f'{{#MyAppToolsIconsFolder}}\\{name}.ico'
def iss_types(self):
types = 'full'
if self.tool_name.lower() in self.compact_tool_list:
types += ' compact'
return types
# script steps
def iterate_sections(self, folder_path, output_path):
self.base_path = folder_path
for item in pathlib.Path(folder_path).iterdir():
# check that the folder is valid for analysis
if item.is_dir() & (item.name.lower() in self.valid_folders):
print(colorama.Fore.WHITE + f'[*] Analyzing folder: {item.name}')
self.section_name = item.name
self.section_list = []
self.iterate_folder(item)
# generate the iss file with everything I processed
section_name = item.name.lower().replace(' ', '-')
with open(f'{output_path}\\sections\\{section_name}.iss', 'w') as file:
file.writelines('\n'.join(self.section_list))
# I add a few line breaks to indicate in the summary that I have finished analyzing a section
print("\n")
def iterate_folder(self, folder_path):
for item in pathlib.Path(folder_path).iterdir():
if item.is_dir():
print(colorama.Fore.YELLOW + f'[+] Process: {item.name}')
self.tool_name = item.name
self.iterate_tool(item)
self.section_list.append('')
self.section_list.append('')
def iterate_tool(self, folder_path, is_sub_folder=False):
# unpack
for item in folder_path.glob('*.7z'):
self.iterate_tool_unpack(item, folder_path)
# main data
if not is_sub_folder:
self.section_list.append(f'; {self.tool_name}')
self.section_list.append('[Components]')
self.section_list.append(
f'Name: "{component_name(self.section_name)}\\{component_name(self.tool_name)}"; '
f'Description: "{self.tool_name}"; '
f'Types: {self.iss_types()}; '
)
self.section_list.append('')
self.section_list.append('[Files]')
self.section_list.append(
f'Source: "{{#MySrcDir}}\\toolkit\\{self.absolute_to_local_path(folder_path.absolute())}\\*"; '
f'DestDir: "{{#MyAppToolsFolder}}\\{self.section_name}\\{self.tool_name}"; '
f'Components: "{component_name(self.section_name)}\\{component_name(self.tool_name)}"; '
'Flags: ignoreversion recursesubdirs createallsubdirs; '
)
self.section_list.append('')
# generate tool info
tool_exe_total = self.iterate_tool_exe(folder_path)
tool_jar_total = self.iterate_tool_jar(folder_path)
tool_py_total = self.iterate_tool_py(folder_path)
# iterate sub folders
for item in pathlib.Path(folder_path).iterdir():
if item.is_dir() & (tool_exe_total == 0) & (tool_jar_total == 0) & (tool_py_total == 0):
print(colorama.Fore.MAGENTA + f' [!] Iterate sub folder: "{item}"')
self.iterate_tool(item, True)
def iterate_tool_unpack(self, file_path, folder_path):
if file_path.name.lower() not in self.disable_unpack:
if len(os.listdir(folder_path)) > 1:
# In addition to creating the new directory, change the path of the folder_path
folder_path = pathlib.Path(folder_path).joinpath(file_path.stem)
pathlib.Path(folder_path).mkdir(exist_ok=True)
with py7zr.SevenZipFile(file_path, 'r') as compressed:
compressed.extractall(folder_path)
file_path.unlink()
def iterate_tool_exe(self, folder_path):
is_first_set = False
force_link_creation = self.tool_name.lower() in self.fix_tool_exe_link_creation
exe_list_len = len(list(folder_path.glob('*.exe')))
for item in folder_path.glob('*.exe'):
if exe_list_len > 1:
if self.tool_name.lower() in self.fix_tool_exe_list.keys():
if item.name.lower() in self.fix_tool_exe_list[self.tool_name.lower()]:
self.iterate_tool_exe_gen(item, force_link_creation)
elif not is_first_set:
print(colorama.Fore.MAGENTA + f' [!!!] Find multiple exes. Grabbing the first!')
is_first_set = True
self.iterate_tool_exe_gen(item, force_link_creation)
else:
self.iterate_tool_exe_gen(item, True)
return exe_list_len
def iterate_tool_exe_gen(self, exe_path, force_link_creation = False):
print(colorama.Fore.GREEN + f' [*] Adding: "{str(pathlib.Path(exe_path).name)}"')
working_dir = str(pathlib.Path(exe_path).parent)
pe_data = get_pe_info(exe_path)
# iss variables
iss_name = f'{self.tool_name} x64' if pe_data['is_x64'] else self.tool_name
iss_filename = f'{{#MyAppToolsFolder}}\\{self.absolute_to_local_path(exe_path)}'
iss_working_dir = f'{{#MyAppToolsFolder}}\\{self.absolute_to_local_path(working_dir)}'
iss_components = f'{component_name(self.section_name)}\\{component_name(self.tool_name)}'
iss_parameters = f'Parameters: "/K ""{iss_filename}""";' if pe_data['is_cli'] else ''
iss_icon = f'IconFilename: "{iss_filename}";' if pe_data['is_cli'] else ''
iss_check = 'Check: Is64BitInstallMode;' if pe_data['is_x64'] else 'Check: not Is64BitInstallMode;'
if force_link_creation:
print(colorama.Fore.MAGENTA + f' [!] force link creation')
iss_check = 'Check: Is64BitInstallMode;' if pe_data['is_x64'] else ''
if pe_data['is_x64']:
print(colorama.Fore.MAGENTA + f' [!] x64 exe')
if pe_data['is_cli']:
print(colorama.Fore.MAGENTA + f' [!] CLI exe')
iss_filename = f'{{sys}}\\cmd.exe'
self.cli_list_append(
iss_components,
f'\\{self.absolute_to_local_path(working_dir)}',
)
self.section_list.append('[Icons]')
self.section_list.append(
f'Name: "{{group}}\\{iss_name}"; '
f'Filename: "{iss_filename}"; '
f'WorkingDir: "{iss_working_dir}"; '
f'Components: "{iss_components}"; '
f'{iss_parameters} '
f'{iss_icon} '
f'{iss_check} '
)
self.section_list.append(
f'Name: "{{#MyAppBinsFolder}}\\sendto\\sendto\\{self.section_name}\\{iss_name}"; '
f'Filename: "{iss_filename}"; '
f'WorkingDir: "{iss_working_dir}"; '
f'Components: "{iss_components}"; '
f'{iss_parameters} '
f'{iss_icon} '
f'{iss_check} '
)
self.section_list.append('')
def cli_list_append(self, component, working_dir):
for item in self.cli_list:
if item['component'] == component:
return False
self.cli_list.append({
'component': component,
'working_dir': working_dir,
})
def iterate_tool_jar(self, folder_path):
jar_list = list(folder_path.glob('*.jar'))
# for now there is always 1
if len(jar_list) == 0:
return 0
self.iterate_tool_jar_gen(jar_list[0])
return len(jar_list)
def iterate_tool_jar_gen(self, jar_path):
print(colorama.Fore.GREEN + f' [*] Adding jar: "{str(pathlib.Path(jar_path).name)}"')
tool_jar_path = self.absolute_to_local_path(jar_path)
working_dir = str(pathlib.Path(jar_path).parent)
self.section_list.append('[Icons]')
self.section_list.append(
f'Name: "{{group}}\\{self.tool_name}"; '
# f'Filename: "java -jar {{#MyAppToolsFolder}}\\{tool_jar_path}"; '
f'Filename: "{{#MyAppToolsFolder}}\\{tool_jar_path}"; '
f'WorkingDir: "{{#MyAppToolsFolder}}\\{self.absolute_to_local_path(working_dir)}"; '
f'Components: "{component_name(self.section_name)}\\{component_name(self.tool_name)}"; '
f'IconFilename: "{self.get_tool_icon()}";'
)
self.section_list.append(
f'Name: "{{#MyAppBinsFolder}}\\sendto\\sendto\\{self.section_name}\\{self.tool_name}"; '
# f'Filename: "java -jar {{#MyAppToolsFolder}}\\{tool_jar_path}"; '
f'Filename: "{{#MyAppToolsFolder}}\\{tool_jar_path}"; '
f'WorkingDir: "{{#MyAppToolsFolder}}\\{self.absolute_to_local_path(working_dir)}"; '
f'Components: "{component_name(self.section_name)}\\{component_name(self.tool_name)}"; '
f'IconFilename: "{self.get_tool_icon()}";'
)
self.section_list.append('')
def iterate_tool_py(self, folder_path):
py_list = list(folder_path.glob('*.py'))
# for now there is always 1
if len(py_list) == 0:
return 0
self.iterate_tool_py_gen(py_list[0])
return len(py_list)
def iterate_tool_py_gen(self, py_path):
print(colorama.Fore.GREEN + f' [*] Adding py: "{str(pathlib.Path(py_path).name)}"')
tool_py_path = self.absolute_to_local_path(py_path)
working_dir = str(pathlib.Path(py_path).parent)
self.section_list.append('[Icons]')
self.section_list.append(
f'Name: "{{group}}\\{self.tool_name}"; '
f'Filename: "{{sys}}\\cmd.exe"; '
f'Parameters: "/K python ""{{#MyAppToolsFolder}}\\{tool_py_path}"""; '
f'WorkingDir: "{{#MyAppToolsFolder}}\\{self.absolute_to_local_path(working_dir)}"; '
f'Components: "{component_name(self.section_name)}\\{component_name(self.tool_name)}"; '
f'IconFilename: "{self.get_tool_icon()}";'
)
self.section_list.append(
f'Name: "{{#MyAppBinsFolder}}\\sendto\\sendto\\{self.section_name}\\{self.tool_name}"; '
f'Filename: "{{sys}}\\cmd.exe"; '
f'Parameters: "/K python ""{{#MyAppToolsFolder}}\\{tool_py_path}"""; '
f'WorkingDir: "{{#MyAppToolsFolder}}\\{self.absolute_to_local_path(working_dir)}"; '
f'Components: "{component_name(self.section_name)}\\{component_name(self.tool_name)}"; '
f'IconFilename: "{self.get_tool_icon()}";'
)
self.section_list.append('')
def cli_env_extra_code(self, output_path):
# first check if content exist
if len(self.cli_list) == 0:
return;
print('')
print(colorama.Fore.YELLOW + f'[+] Generate cli register code')
lines_list = []
lines_list.append('{')
lines_list.append(';;;;; AUTOGENERATED!')
lines_list.append(';;;;;;;;;;;;;;;;;;;;;;;;;')
lines_list.append('}')
# install hook
lines_list.append('procedure CurStepChanged(CurStep: TSetupStep);')
lines_list.append('begin')
lines_list.append(' if CurStep = ssPostInstall then')
lines_list.append(' begin')
for item in self.cli_list:
lines_list.append(f' if WizardIsComponentSelected(\'{item["component"]}\') then EnvAddPath(ExpandConstant(\'{{#MyAppToolsFolder}}\') + \'{item["working_dir"]}\');')
lines_list.append(' end')
lines_list.append('end;')
lines_list.append('')
# uninstall hook
lines_list.append('procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);')
lines_list.append('begin')
lines_list.append(' if CurUninstallStep = usPostUninstall then')
lines_list.append(' begin')
for item in self.cli_list:
lines_list.append(f' EnvRemovePath(ExpandConstant(\'{{#MyAppToolsFolder}}\') + \'{item["working_dir"]}\');')
lines_list.append(' end')
lines_list.append('end;')
lines_list.append('')
# save
shutil.copy('cli.iss.base', f'{output_path}\\sections\\cli.iss')
with open(f'{output_path}\\sections\\cli.iss', 'a') as file:
file.writelines('\n'.join(lines_list))
def main(self):
colorama.init(autoreset=True)
parser = argparse.ArgumentParser(
usage='%(prog)s [ARGUMENTS]',
)
parser.add_argument(
'-f',
'--folder',
dest='toolkit_folder',
help='path to toolkit folder',
type=pathlib.Path,
required=True,
)
parser.add_argument(
'-o',
'--output',
dest='output_folder',
help='path to output folder',
type=pathlib.Path,
required=True,
)
arguments = parser.parse_args()
toolkit_folder = arguments.toolkit_folder
output_folder = arguments.output_folder
if not toolkit_folder.is_dir():
print(colorama.Fore.RED + 'toolkit_folder is not a valid folder')
return 0
if not output_folder.is_dir():
print(colorama.Fore.RED + 'output_folder is not a valid folder')
return 0
self.iterate_sections(toolkit_folder, output_folder)
self.cli_env_extra_code(output_folder)
# se fini
if __name__ == '__main__':
GenerateInstall().main()