wip: cleanup: don't fallback on bcc, remove menu

This commit is contained in:
2025-09-12 18:25:03 +08:00
parent 490f6afae7
commit eca034ca45

View File

@@ -60,22 +60,17 @@ def bcc_fallback_method(seq_file_path: str, output_path: str = None):
return None
async def run_subprocess_async(cmd, cwd=None):
process = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
cwd=cwd
)
stdout, stderr = await process.communicate()
return process.returncode, stdout, stderr
async def decrypt_file_async(exe_path, seq_file_path, path, args):
logger = logging.getLogger('shot')
try:
# Run without timeout
returncode, stdout, stderr = await run_subprocess_async([exe_path, seq_file_path])
process = await asyncio.create_subprocess_exec(
exe_path,
seq_file_path,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
stdout_lines = stdout.decode('latin-1').splitlines()
stderr_lines = stderr.decode('latin-1').splitlines()
@@ -105,12 +100,13 @@ async def decrypt_file_async(exe_path, seq_file_path, path, args):
else:
logger.error(f'PYCDC: {line} ({path})')
return returncode, stdout_lines, stderr_lines
if process.returncode != 0:
logger.warning(f'{Fore.YELLOW}PYCDC returned 0x{process.returncode:x} ({path}){Style.RESET_ALL}')
except Exception as e:
error_details = traceback.format_exc()
logger.error(f'{Fore.RED}Error during async deobfuscation: {e}{Style.RESET_ALL}')
logger.error(f'{Fore.RED}Exception: {e} ({path}){Style.RESET_ALL}')
logger.error(f'{Fore.RED}Error details: {error_details}{Style.RESET_ALL}')
return -1, [], []
async def decrypt_process_async(runtimes: Dict[str, RuntimeInfo], sequences: List[Tuple[str, bytes]], args):
@@ -153,51 +149,8 @@ async def decrypt_process_async(runtimes: Dict[str, RuntimeInfo], sequences: Lis
f.write(data[cipher_text_offset+cipher_text_length:])
# Run without timeout
returncode, stdout_lines, stderr_lines = await decrypt_file_async(exe_path, seq_file_path, path, args)
# Check for specific errors that indicate BCC mode
should_try_bcc = False
error_message = ""
await decrypt_file_async(exe_path, seq_file_path, path, args)
# FIXME: Probably false positive
if returncode != 0:
error_message = f"PYCDC returned {returncode}"
# Check for specific error patterns that suggest BCC mode
for line in stderr_lines:
if ("Unsupported opcode" in line or
"Something TERRIBLE happened" in line or
"Unknown opcode 0" in line or
"Got unsupported type" in line):
should_try_bcc = True
error_message += f" - {line}"
break
if should_try_bcc:
logger.warning(f'{Fore.YELLOW}{error_message} ({path}) - Attempting BCC fallback{Style.RESET_ALL}')
# Try BCC fallback method
bcc_file_path = bcc_fallback_method(seq_file_path)
if bcc_file_path:
logger.info(f'{Fore.GREEN}Running deobfuscator on BCC fallback file{Style.RESET_ALL}')
try:
# Run without timeout
returncode, stdout_lines, stderr_lines = await decrypt_file_async(exe_path, bcc_file_path, path, args)
if returncode == 0:
logger.info(f'{Fore.GREEN}Successfully deobfuscated using BCC fallback method{Style.RESET_ALL}')
print(f"{Fore.GREEN} BCC Decrypted: {path}{Style.RESET_ALL}")
else:
logger.error(f'{Fore.RED}BCC fallback deobfuscation failed with return code {returncode}{Style.RESET_ALL}')
for line in stderr_lines:
logger.error(f'{Fore.RED}BCC Error: {line}{Style.RESET_ALL}')
except Exception as e:
error_details = traceback.format_exc()
logger.error(f'{Fore.RED}BCC fallback deobfuscation failed with error: {e}{Style.RESET_ALL}')
logger.error(f'{Fore.RED}Error details: {error_details}{Style.RESET_ALL}')
elif returncode == 0:
# Successfully decrypted
logger.info(f'{Fore.GREEN}Successfully decrypted: {path}{Style.RESET_ALL}')
print(f"{Fore.GREEN} Decrypted: {path}{Style.RESET_ALL}")
else:
logger.warning(f'{Fore.YELLOW}{error_message} ({path}){Style.RESET_ALL}')
except Exception as e:
error_details = traceback.format_exc()
logger.error(f'{Fore.RED}Decrypt failed: {e} ({path}){Style.RESET_ALL}')
@@ -253,13 +206,13 @@ def get_platform_executable(args) -> str:
# 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}')
logger.info(f'{Fore.GREEN}Using 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}')
logger.info(f'{Fore.GREEN}Using executable: pyarmor-1shot{Style.RESET_ALL}')
return generic_exe_path
logger.critical(f'{Fore.RED}Executable {base_exe_name} not found, please build it first or download on https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot/releases {Style.RESET_ALL}')
@@ -273,7 +226,6 @@ def parse_args():
'directory',
help='the "root" directory of obfuscated scripts',
type=str,
nargs='?',
)
parser.add_argument(
'-r',
@@ -307,11 +259,6 @@ def parse_args():
help='show pycdc stack related warnings',
action='store_true',
)
parser.add_argument(
'--menu',
help='show interactive menu to select folder to unpack',
action='store_true',
)
parser.add_argument(
'--concurrent',
help='number of concurrent deobfuscation processes (default: 4)',
@@ -327,41 +274,6 @@ def parse_args():
return parser.parse_args()
def display_menu():
to_unpack_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'to_unpack')
if not os.path.exists(to_unpack_dir):
os.makedirs(to_unpack_dir)
folders = [d for d in os.listdir(to_unpack_dir)
if os.path.isdir(os.path.join(to_unpack_dir, d))]
if not folders:
print(f"{Fore.YELLOW}No folders found in {to_unpack_dir}{Style.RESET_ALL}")
return None
print(f"\n{Fore.CYAN}=== Available Folders to Unpack ==={Style.RESET_ALL}")
for i, folder in enumerate(folders, 1):
print(f"{Fore.GREEN}[{i}]{Style.RESET_ALL} {folder}")
while True:
try:
choice = input(f"\n{Fore.YELLOW}Enter the number of the folder to unpack (or 'q' to quit): {Style.RESET_ALL}")
if choice.lower() == 'q':
return None
choice_idx = int(choice) - 1
if 0 <= choice_idx < len(folders):
selected_folder = folders[choice_idx]
full_path = os.path.join(to_unpack_dir, selected_folder)
print(f"{Fore.GREEN}Selected: {selected_folder}{Style.RESET_ALL}")
return full_path
else:
print(f"{Fore.RED}Invalid choice. Please enter a number between 1 and {len(folders)}{Style.RESET_ALL}")
except ValueError:
print(f"{Fore.RED}Please enter a valid number{Style.RESET_ALL}")
def main():
args = parse_args()
logging.basicConfig(
@@ -387,15 +299,6 @@ def main():
GitHub: https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot
''' + Style.RESET_ALL)
# If menu option is selected or no directory is provided, show the menu
if args.menu or not args.directory:
selected_dir = display_menu()
if selected_dir:
args.directory = selected_dir
else:
print(f"{Fore.YELLOW}No directory selected. Exiting.{Style.RESET_ALL}")
return
if args.runtime:
specified_runtime = RuntimeInfo(args.runtime)
print(specified_runtime)