chore
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Build
|
||||
on:
|
||||
push:
|
||||
branches: [ci-build]
|
||||
branches: [main]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
|
@@ -16,7 +16,7 @@ You don't need to execute the encrypted script. We decrypt them using the same a
|
||||
|
||||
### Universal
|
||||
|
||||
Currently we are trying to support Pyarmor 8.0 to 9.1.2 (latest), Python 3.7 - 3.13, on all operating systems, with obfuscating options as many as possible. (However, we only have limited tests.)
|
||||
Currently we are trying to support Pyarmor 8.0 to 9.1.3 (latest), Python 3.7 - 3.13, on all operating systems, with obfuscating options as many as possible. (However, we only have limited tests.)
|
||||
|
||||
You can run this tool in any environment, no need to be the same with obfuscated scripts or runtime.
|
||||
|
||||
@@ -67,5 +67,4 @@ Feel free to open an issue if you have any questions, suggestions, or problems.
|
||||
|
||||
- [ ] Multi-platform pyarmor_runtime executable
|
||||
- [ ] Support more obfuscating options
|
||||
- [ ] Use asyncio for concurrency
|
||||
- [ ] Pyarmor 7 and before (Later or never.)
|
||||
- [ ] Regenerate pyc for other backends
|
||||
|
106
helpers/shot.py
106
helpers/shot.py
@@ -2,15 +2,17 @@ import argparse
|
||||
from Crypto.Cipher import AES
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import asyncio
|
||||
import traceback
|
||||
import platform
|
||||
import glob
|
||||
from typing import Dict, List, Tuple, Optional
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
try:
|
||||
from colorama import init, Fore, Style
|
||||
except ImportError:
|
||||
def init(**kwargs): pass
|
||||
class Fore: CYAN = RED = YELLOW = GREEN = ''
|
||||
class Style: RESET_ALL = ''
|
||||
|
||||
from detect import detect_process
|
||||
from runtime import RuntimeInfo
|
||||
@@ -47,7 +49,7 @@ def bcc_fallback_method(seq_file_path: str, output_path: str = None):
|
||||
with open(output_path, 'wb') as f:
|
||||
f.write(one_shot_header)
|
||||
f.write(bytecode_part[:64])
|
||||
f.write(AES.new(aes_key, AES.MODE_CTR, nonce=aes_nonce, initial_value=2).decrypt(bytecode_part[64:]))
|
||||
f.write(general_aes_ctr_decrypt(bytecode_part[64:], aes_key, aes_nonce))
|
||||
|
||||
logger.info(f'{Fore.GREEN}Successfully created BCC fallback file: {output_path}{Style.RESET_ALL}')
|
||||
return output_path
|
||||
@@ -92,7 +94,12 @@ async def decrypt_file_async(exe_path, seq_file_path, path, args):
|
||||
elif line.startswith('Unsupported opcode:'):
|
||||
if args.show_err_opcode or args.show_all:
|
||||
logger.error(f'PYCDC: {line} ({path})')
|
||||
elif line.startswith('Something TERRIBLE happened'):
|
||||
elif line.startswith((
|
||||
'Something TERRIBLE happened',
|
||||
'Unsupported argument',
|
||||
'Unsupported Node type',
|
||||
'Unsupported node type',
|
||||
)): # annoying wont-fix errors
|
||||
if args.show_all:
|
||||
logger.error(f'PYCDC: {line} ({path})')
|
||||
else:
|
||||
@@ -113,6 +120,9 @@ async def decrypt_process_async(runtimes: Dict[str, RuntimeInfo], sequences: Lis
|
||||
# Create a semaphore to limit concurrent processes
|
||||
semaphore = asyncio.Semaphore(args.concurrent) # Use the concurrent argument
|
||||
|
||||
# Get the appropriate executable for the current platform
|
||||
exe_path = get_platform_executable(args)
|
||||
|
||||
async def process_file(path, data):
|
||||
async with semaphore:
|
||||
try:
|
||||
@@ -142,9 +152,6 @@ 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:])
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -152,6 +159,7 @@ async def decrypt_process_async(runtimes: Dict[str, RuntimeInfo], sequences: Lis
|
||||
should_try_bcc = False
|
||||
error_message = ""
|
||||
|
||||
# FIXME: Probably false positive
|
||||
if returncode != 0:
|
||||
error_message = f"PYCDC returned {returncode}"
|
||||
# Check for specific error patterns that suggest BCC mode
|
||||
@@ -220,34 +228,28 @@ def get_platform_executable(args) -> str:
|
||||
else:
|
||||
logger.warning(f'{Fore.YELLOW}Specified executable not found: {args.executable}{Style.RESET_ALL}')
|
||||
|
||||
# Determine the current platform
|
||||
helpers_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
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
|
||||
|
||||
platform_map = {
|
||||
'windows': 'pyarmor-1shot.exe',
|
||||
'linux': 'pyarmor-1shot',
|
||||
'darwin': 'pyarmor-1shot',
|
||||
}
|
||||
base_exe_name = platform_map.get(system, 'pyarmor-1shot')
|
||||
|
||||
# Then check for platform-specific executable
|
||||
platform_exe_path = os.path.join(helpers_dir, base_exe_name)
|
||||
if os.path.exists(platform_exe_path):
|
||||
@@ -260,29 +262,8 @@ def get_platform_executable(args) -> str:
|
||||
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
|
||||
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}')
|
||||
exit(1)
|
||||
|
||||
|
||||
def parse_args():
|
||||
@@ -343,11 +324,6 @@ def parse_args():
|
||||
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()
|
||||
|
||||
|
||||
@@ -394,8 +370,8 @@ def main():
|
||||
)
|
||||
logger = logging.getLogger('shot')
|
||||
|
||||
print(f'''
|
||||
{Fore.CYAN} ____ ____
|
||||
print(Fore.CYAN + r'''
|
||||
____ ____
|
||||
( __ ) ( __ )
|
||||
| |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| |
|
||||
| | ____ _ ___ _ _ | |
|
||||
@@ -409,8 +385,7 @@ def main():
|
||||
|
||||
For technology exchange only. Use at your own risk.
|
||||
GitHub: https://github.com/Lil-House/Pyarmor-Static-Unpack-1shot
|
||||
{Style.RESET_ALL}
|
||||
''')
|
||||
''' + Style.RESET_ALL)
|
||||
|
||||
# If menu option is selected or no directory is provided, show the menu
|
||||
if args.menu or not args.directory:
|
||||
@@ -447,23 +422,6 @@ 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]
|
||||
|
Reference in New Issue
Block a user