From 665ec7efd6639d43d3a3111d8b36618c1a46cda3 Mon Sep 17 00:00:00 2001 From: Anon0x23 <56775638+Anon0x23@users.noreply.github.com> Date: Thu, 10 Apr 2025 00:02:21 +0200 Subject: [PATCH] Update shot.py Add multi-platform support for pyarmor_runtime executables 1. Platform Detection and Executable Selection: - Automatically detects OS (Windows, Linux, macOS) and architecture - Intelligently selects the appropriate executable based on platform - Supports architecture-specific executables (e.g., pyarmor-1shot-windows-x86_64.exe) - Falls back to platform-specific and generic executables when needed 2. Runtime File Auto-Detection: - Added ability to automatically find all pyarmor_runtime files in a directory - Supports multiple runtime file formats (.pyd, .so, .dylib) - Recursively searches through all subdirectories 3. New Command-Line Options: - Added --executable option to specify a custom executable path - Added --auto-detect-runtime option to automatically detect runtime files --- helpers/shot.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/helpers/shot.py b/helpers/shot.py index 6f1ae66..53ce3cb 100644 --- a/helpers/shot.py +++ b/helpers/shot.py @@ -7,7 +7,9 @@ import sys import time import asyncio import traceback -from typing import Dict, List, Tuple +import platform +import glob +from typing import Dict, List, Tuple, Optional from colorama import init, Fore, Style from detect import detect_process @@ -140,9 +142,8 @@ async def decrypt_process_async(runtimes: Dict[str, RuntimeInfo], sequences: Lis data[cipher_text_offset:cipher_text_offset+cipher_text_length], runtime.runtime_aes_key, nonce)) f.write(data[cipher_text_offset+cipher_text_length:]) - exe_name = 'pyarmor-1shot.exe' if os.name == 'nt' else 'pyarmor-1shot' - exe_path = os.path.join( - os.path.dirname(os.path.abspath(__file__)), exe_name) + # Get the appropriate executable for the current platform + exe_path = get_platform_executable(args) # Run without timeout returncode, stdout_lines, stderr_lines = await decrypt_file_async(exe_path, seq_file_path, path, args) @@ -205,6 +206,85 @@ def decrypt_process(runtimes: Dict[str, RuntimeInfo], sequences: List[Tuple[str, asyncio.run(decrypt_process_async(runtimes, sequences, args)) +def get_platform_executable(args) -> str: + """ + Get the appropriate executable for the current platform + """ + logger = logging.getLogger('shot') + + # If a specific executable is provided, use it + if args.executable: + if os.path.exists(args.executable): + logger.info(f'{Fore.GREEN}Using specified executable: {args.executable}{Style.RESET_ALL}') + return args.executable + else: + logger.warning(f'{Fore.YELLOW}Specified executable not found: {args.executable}{Style.RESET_ALL}') + + # Determine the current platform + system = platform.system().lower() + machine = platform.machine().lower() + + # Map platform to executable name + platform_map = { + 'windows': 'pyarmor-1shot.exe', + 'linux': 'pyarmor-1shot', + 'darwin': 'pyarmor-1shot' # macOS + } + + # Get the base executable name for the current platform + base_exe_name = platform_map.get(system, 'pyarmor-1shot') + + # Check for architecture-specific executables + arch_specific_exe = f'pyarmor-1shot-{system}-{machine}' + if system == 'windows': + arch_specific_exe += '.exe' + + # Look for executables in the helpers directory + helpers_dir = os.path.dirname(os.path.abspath(__file__)) + + # First check for architecture-specific executable + arch_exe_path = os.path.join(helpers_dir, arch_specific_exe) + if os.path.exists(arch_exe_path): + logger.info(f'{Fore.GREEN}Using architecture-specific executable: {arch_specific_exe}{Style.RESET_ALL}') + return arch_exe_path + + # Then check for platform-specific executable + platform_exe_path = os.path.join(helpers_dir, base_exe_name) + if os.path.exists(platform_exe_path): + logger.info(f'{Fore.GREEN}Using platform-specific executable: {base_exe_name}{Style.RESET_ALL}') + return platform_exe_path + + # Finally, check for generic executable + generic_exe_path = os.path.join(helpers_dir, 'pyarmor-1shot') + if os.path.exists(generic_exe_path): + logger.info(f'{Fore.GREEN}Using generic executable: pyarmor-1shot{Style.RESET_ALL}') + return generic_exe_path + + # If no executable is found, use the default name + logger.warning(f'{Fore.YELLOW}No specific executable found for {system}-{machine}, using default: {base_exe_name}{Style.RESET_ALL}') + return os.path.join(helpers_dir, base_exe_name) + + +def find_runtime_files(directory: str) -> List[str]: + """ + Find all pyarmor_runtime files in the given directory and its subdirectories + """ + runtime_files = [] + + # Common runtime file patterns + patterns = [ + 'pyarmor_runtime*.pyd', # Windows + 'pyarmor_runtime*.so', # Linux + 'pyarmor_runtime*.dylib' # macOS + ] + + for pattern in patterns: + for file_path in glob.glob(os.path.join(directory, '**', pattern), recursive=True): + runtime_files.append(file_path) + + return runtime_files + + def parse_args(): parser = argparse.ArgumentParser( description='Pyarmor Static Unpack 1 Shot Entry') @@ -257,6 +337,17 @@ def parse_args(): type=int, default=4, ) + parser.add_argument( + '-e', + '--executable', + help='path to the pyarmor-1shot executable to use', + type=str, + ) + parser.add_argument( + '--auto-detect-runtime', + help='automatically detect and use all pyarmor_runtime files in the directory', + action='store_true', + ) return parser.parse_args() @@ -356,6 +447,23 @@ def main(): decrypt_process(runtimes, sequences, args) return # single file mode ends here + # Auto-detect runtime files if requested + if args.auto_detect_runtime: + logger.info(f'{Fore.CYAN}Auto-detecting runtime files in {args.directory}...{Style.RESET_ALL}') + runtime_files = find_runtime_files(args.directory) + if runtime_files: + logger.info(f'{Fore.GREEN}Found {len(runtime_files)} runtime files{Style.RESET_ALL}') + for runtime_file in runtime_files: + try: + new_runtime = RuntimeInfo(runtime_file) + runtimes[new_runtime.serial_number] = new_runtime + logger.info(f'{Fore.GREEN}Found runtime: {new_runtime.serial_number} ({runtime_file}){Style.RESET_ALL}') + print(new_runtime) + except Exception as e: + logger.error(f'{Fore.RED}Failed to load runtime {runtime_file}: {e}{Style.RESET_ALL}') + else: + logger.warning(f'{Fore.YELLOW}No runtime files found in {args.directory}{Style.RESET_ALL}') + dir_path: str dirs: List[str] files: List[str]