PEASS-ng/linPEAS/builder/src/linpeasBaseBuilder.py
2024-12-05 01:24:35 +01:00

335 lines
13 KiB
Python

import os
from typing import List
from .linpeasModule import LinpeasModule, LinpeasModuleList
from .yamlGlobals import (
LINPEAS_PARTS,
TEMPORARY_LINPEAS_BASE_PATH,
PEAS_CHECKS_MARKUP
)
class LinpeasBaseBuilder:
def __init__(self, all_modules, all_no_fat_modules, no_network_scanning, small, include_modules, exclude_modules):
# Everything relevant
self.all_modules = self.get_modules(all_modules, all_no_fat_modules, no_network_scanning, small, include_modules, exclude_modules)
# Only base
self.base = self.get_base()
# Only checks
self.checks = self.get_checks()
print(f"[+] {len(self.checks)} checks located")
# Only functions sorted
self.functions = self.get_functions()
# Only variables sorted
self.variables = self.get_variables()
self.linpeas_base = ""
def build(self):
print("[+] Building temporary linpeas_base.sh with the indicated modules...")
# Add base code
for base in self.base:
self.linpeas_base += base.sh_code.strip() + "\n\n"
# Add variables
self.linpeas_base += "\n\n\n# Variables\n\n"
for variable in self.variables:
if "Checks pre-everything" in variable.sh_code:
a=1
self.linpeas_base += variable.sh_code.strip() + "\n\n"
self.linpeas_base += "\n\n\n# Functions\n\n"
# Add functions
for function in self.functions:
self.linpeas_base += function.sh_code.strip() + "\n\n"
self.linpeas_base += "\n\n\n# Checks\n\n"
section_checks = {}
check_names = []
for check in self.checks:
# Get the section of the check
for part_mod in LINPEAS_PARTS["modules"]:
if part_mod["folder_path"] in check.path:
if part_mod["name"] not in section_checks:
section_checks[part_mod["name"]] = part_mod
section_checks[part_mod["name"]]["checks"] = []
section_checks[part_mod["name"]]["checks"].append(check)
break
initial_functions = set()
for section_name, section_info in section_checks.items():
# Add 1 time the big section name to check_names to then put it inside linpeas in PEAS_CHECKS_MARKUP
if not section_info['name_check'] in check_names: check_names.append(section_info['name_check'])
self.linpeas_base += f"\nif echo $CHECKS | grep -q {section_info['name_check']}; then\n"
self.linpeas_base += f'print_title "{section_name}"\n'
# Sort checks alphabetically to get them in the same order as they are in the folder
section_info["checks"] = sorted(section_info["checks"], key=lambda x: int(os.path.basename(x.path).split('_')[0]) if os.path.basename(x.path).split('_')[0].isdigit() else 99)
for check in section_info["checks"]:
for func in check.initial_functions:
if not func in initial_functions:
self.linpeas_base += func + "\n"
initial_functions.add(func)
self.linpeas_base += check.sh_code.strip() + "\n\n"
self.linpeas_base += f"\nfi\necho ''\necho ''\n"
self.linpeas_base += 'if [ "$WAIT" ]; then echo "Press enter to continue"; read "asd"; fi\n'
self.linpeas_base = self.linpeas_base.replace(PEAS_CHECKS_MARKUP, ",".join(check_names))
with open(TEMPORARY_LINPEAS_BASE_PATH, "w") as f:
f.write(self.linpeas_base)
def find_func_module(self, func_name:str):
"""Given a function name and the list of modules return the module that contains the function"""
modules = []
for module in self.all_modules:
if func_name in module.defined_funcs:
modules.append(module)
if len(modules) == 0:
raise Exception(f"Function {func_name} not found in any module")
elif len(modules) > 1:
raise Exception(f"Function {func_name} found in more than 1 module: {modules}")
return modules[0]
def find_variable_module(self, var_name:str, orig_module:LinpeasModule):
"""Given a variable name and the list of modules return the module that contains the variable"""
modules = []
for module in self.all_modules:
if var_name in module.generated_global_variables:
modules.append(module)
if len(modules) == 0:
raise Exception(f"Variable '{var_name}' from {orig_module.path} not found in any module")
elif len(modules) > 1:
raise Exception(f"Variable {var_name} found in more than 1 module: {', '.join([m.path for m in modules])}")
return modules[0]
def sort_funcs(self, functions:List[LinpeasModule]):
"""Given a list of functions, return the list sorted by dependencies"""
sorted_funcs = functions.copy()
retry = False
for i,func in enumerate(functions):
for d_func in func.functions_used:
is_base = False
# If the dependant variable is defined in a module that is in the base, remove it from the list
if any (d_func in m.defined_funcs for m in self.base):
try:
sorted_funcs.index(d_func) # Check if it's there
sorted_funcs.remove(d_func) # Remove if it's
retry = True # After a failure, start again
except:
pass
is_base = True
if is_base:
continue
# If a dependant variable is after the current one, move it to the current position
try:
dp_index = functions.index(d_func)
except:
raise Exception(f"Variable {d_func} not found in {func.path}")
if dp_index > i:
sorted_funcs.remove(d_func)
sorted_funcs.insert(i, functions[dp_index])
retry = True
if retry:
return self.sort_funcs(sorted_funcs)
return sorted_funcs
def sort_variables(self, variables:List[LinpeasModule]):
"""Given a list of variables, return the list sorted by dependencies"""
sorted_vars = variables.copy()
retry = False
for i,var in enumerate(variables):
for d_var in var.global_variables:
is_base = False
# If the dependant variable is defined in a module that is in the base, remove it from the list
if any (d_var in m.generated_global_variables for m in self.base):
try:
sorted_vars.index(d_var) # Check if it's there
sorted_vars.remove(d_var) # Remove if it's
retry = True # After a failure, start again
except:
pass
is_base = True
if is_base:
continue
# If a dependant variable is after the current one, move it to the current position
try:
dp_index = variables.index(d_var)
except:
raise Exception(f"Variable {d_var} not found in {var.path}")
if dp_index > i:
sorted_vars.remove(d_var)
sorted_vars.insert(i, variables[dp_index])
retry = True
if retry:
return self.sort_variables(sorted_vars)
return sorted_vars
def get_funcs_deps(self, module, all_funcs):
"""Given 1 module and the list of modules return the functions recursively it depends on"""
module_funcs = list(set(module.initial_functions + module.functions_used))
for func in module_funcs:
func_module = self.find_func_module(func)
#print(f"{module.id} has found {func} in {func_module.id}") #To find circular dependencies
if not func_module.is_function:
continue
if func_module in all_funcs:
all_funcs.remove(func_module)
all_funcs.append(func_module)
all_funcs = self.get_funcs_deps(func_module, all_funcs)
return all_funcs
def get_vars_deps(self, module, all_vars):
"""Given 1 module and the list of modules return the variables recursively it depends on"""
for var in module.global_variables:
var_module = self.find_variable_module(var, module)
#print(f"{module.id} has found {var} in {var_module.id}") #To find circular dependencies
if not var_module.is_variable:
continue
if var_module in all_vars:
all_vars.remove(var_module)
all_vars.append(var_module)
all_vars = self.get_vars_deps(var_module, all_vars)
return all_vars
def get_functions(self):
"""Get all the functions used sorted, first the ones that don't depend on any other, then the ones that depend on the previous ones, etc."""
all_funcs = LinpeasModuleList()
for module in self.checks:
all_funcs = self.get_funcs_deps(module, all_funcs)
return self.sort_funcs(all_funcs)
def get_variables(self):
"""Get all the variables used sorted, first the ones that don't depend on any other, then the ones that depend on the previous ones, etc."""
all_variables = LinpeasModuleList()
for module in self.checks + self.functions:
all_variables = self.get_vars_deps(module, all_variables)
return self.sort_variables(all_variables)
def get_checks(self):
"""Given all the modules get only the checks"""
checks = LinpeasModuleList()
for module in self.all_modules:
if not module.is_check:
continue
checks.append(module)
return checks
def get_base(self):
"""Given all the modules get only the base"""
checks = LinpeasModuleList()
for module in self.all_modules:
if not module.is_base:
continue
checks.append(module)
return checks
def enumerate_directory(self, path):
"""Given a directory get the paths to all the files inside it"""
return sorted([os.path.join(path, f) for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))])
def get_modules(self, all_modules, all_no_fat_modules, no_network_scanning, small, include_modules, exclude_modules) -> LinpeasModuleList:
"""Get all the base, variable, function and specified modules to create the new linpeas"""
print("[+] Checking the syntax of the modules...")
parsed_modules = LinpeasModuleList()
all_module_paths = []
# Base modules
all_module_paths += self.enumerate_directory(LINPEAS_PARTS["base"])
# Function modules
all_module_paths += self.enumerate_directory(LINPEAS_PARTS["functions"])
# Variable modules
all_module_paths += self.enumerate_directory(LINPEAS_PARTS["variables"])
for module in LINPEAS_PARTS["modules"]:
for ex_module in exclude_modules:
if ex_module in module["folder_path"] or ex_module in [module["name"], module["name_check"]]:
continue
all_module_paths += self.enumerate_directory(module["folder_path"])
for module in all_module_paths:
m = LinpeasModule(module)
# If base, function or variable, add it as it will only be used if needed
if m.is_function or m.is_variable:
parsed_modules.append(m)
continue
# If base but no interested in network scanning, skip, else, add
if m.is_base:
if "check_network_jobs" in m.path and no_network_scanning:
continue
parsed_modules.append(m)
continue
# If explicitely excluded, skip
if m.id in exclude_modules:
continue
if all_no_fat_modules and m.is_fat:
continue
if small and not m.is_small:
continue
# If implicitly included, add
if all_modules or all_no_fat_modules or m.id in include_modules:
parsed_modules.append(m)
for in_module in include_modules:
if in_module.lower() in os.path.basename(m.path).lower() or in_module.lower() == m.id.lower() or in_module in [m.section_info["name"], m.section_info["name_check"]]:
parsed_modules.append(m)
break
return parsed_modules