diff --git a/README.md b/README.md index 653eb97..06444a3 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - Supports both built-in files and external packages - Supports compressed mode -# Tested Versions +## Tested Versions | Packer Version | Notes | Unpack with Flags | | - | - | - | | 10.70 | Automatically tested in CI for x86/x64 binaries. | None | @@ -27,7 +27,9 @@ ## Usage - usage: evbunpack [-h] [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-l] [--ignore-fs] [--ignore-pe] [--legacy-fs] [--legacy-pe] [--out-dir OUT_DIR] [--out-pe OUT_PE] file + usage: evbunpack [-h] [--log-level {DEBUG,INFO,WARNING,ERROR,CRITICAL}] [-l] [--ignore-fs] [--ignore-pe] + [--legacy-fs] [--legacy-pe] [--out-pe OUT_PE] + file output Enigma Virtual Box Unpacker @@ -43,12 +45,35 @@ --legacy-fs Use legacy mode for filesystem extraction --legacy-pe Use legacy mode for PE restoration - Output: - --out-dir OUT_DIR Output folder - --out-pe OUT_PE (If the executable is to be recovered) Where the unpacked EXE is saved. Leave as-is to save it in the output folder. + Overrides: + --out-pe OUT_PE (If the executable is to be recovered) Where the unpacked EXE is saved. Leave as-is + to save it in the output folder. Input: file File to be unpacked + output Output folder + +### Example Usage ([test file available here](https://github.com/mos9527/evbunpack/blob/main/tests/x64_PackerTestApp_packed_20240522.exe)) +Input: +```bash +evbunpack x64_PackerTestApp_packed_20240522.exe output +``` +Output: +```bash +INFO: Enigma Virtual Box Unpacker v0.2.1 +INFO: Extracting virtual filesystem +Filesystem: + └─── output + └─── output/README.txt +Writing File [size=0x11, offset=0x3465]: total= 11h read= 0h +INFO: Extraction complete +INFO: Restoring executable +INFO: Using default executable save path: output\x64_PackerTestApp_packed_20240522.exe +Saving PE: total= 3211h read= 0h +INFO: Unpacked PE saved: output\x64_PackerTestApp_packed_20240522.exe +``` +## TODO +- Automatically detect packer version ## Credits - [evb-extractor](https://github.com/EVBExtractor/evb-extractor) diff --git a/evbunpack/__init__.py b/evbunpack/__init__.py index 9c874af..46d9455 100644 --- a/evbunpack/__init__.py +++ b/evbunpack/__init__.py @@ -1,3 +1,3 @@ #-*- coding: utf-8 -- -__version__ = '0.2.0' +__version__ = '0.2.1' __author__ = 'mos9527' diff --git a/evbunpack/__main__.py b/evbunpack/__main__.py index b3099b9..94754e2 100644 --- a/evbunpack/__main__.py +++ b/evbunpack/__main__.py @@ -203,8 +203,8 @@ def restore_pe(input_file : str, output_file : str, legcay_pe : bool): find_data_directory('IMPORT').Size = hdr['IMPORT_SIZE'] find_data_directory('RELOC').VirtualAddress = hdr['RELOC_ADDRESS'] find_data_directory('RELOC').Size = hdr['RELOC_SIZE'] - logger.info('Import -> VA=0x%x Size=0x%x' % (hdr['IMPORT_ADDRESS'],hdr['IMPORT_SIZE'])) - logger.info('Reloc -> VA=0x%x Size=0x%x' % (hdr['RELOC_ADDRESS'],hdr['RELOC_SIZE'])) + logger.debug('Import -> VA=0x%x Size=0x%x' % (hdr['IMPORT_ADDRESS'],hdr['IMPORT_SIZE'])) + logger.debug('Reloc -> VA=0x%x Size=0x%x' % (hdr['RELOC_ADDRESS'],hdr['RELOC_SIZE'])) if hdr['RELOC_SIZE'] == 0 or hdr['IMPORT_SIZE'] == 0: warnings_issued += 1 logger.warning('Import/Reloc table size is zero. This may indicate that the header is incorrectly parsed.') @@ -269,7 +269,7 @@ def restore_pe(input_file : str, output_file : str, legcay_pe : bool): new_file_data = pe.write() with open(output_file,'wb+') as f: write_bytes(BytesIO(new_file_data),f,len(new_file_data),desc='Saving PE') - logger.info('Original PE saved: %s' % output_file) + logger.info('Unpacked PE saved: %s' % output_file) if warnings_issued: logger.warning('There were %d warning(s) issued during the restoration process.' % warnings_issued) logger.warning('Please try toggling the --legacy-pe flag if the unpacked EXE is corrupt.') @@ -332,9 +332,9 @@ def traverse_next_node(node,pfx=out_dir,depth=0): return logger.info('Extraction complete') -def main(file : str, out_dir : str = '.', out_pe : str = '', ignore_fs: bool = False, ignore_pe: bool = False, legacy_fs: bool = False, legacy_pe: bool = False, fs_listing_only: bool = False): +def main(in_file : str, out_dir : str = '.', out_pe : str = '', ignore_fs: bool = False, ignore_pe: bool = False, legacy_fs: bool = False, legacy_pe: bool = False, fs_listing_only: bool = False): logger.info('Enigma Virtual Box Unpacker v%s' % __version__) - logger.debug('File: %s' % file) + logger.debug('File: %s' % in_file) os.makedirs(out_dir,exist_ok=True) if legacy_fs: logger.warning('Legacy mode for filesystem extraction enabled') @@ -347,7 +347,7 @@ def main(file : str, out_dir : str = '.', out_pe : str = '', ignore_fs: bool = F else: logger.info('Extracting virtual filesystem') try: - unpack_files(file,out_dir,legacy_fs,fs_listing_only) + unpack_files(in_file,out_dir,legacy_fs,fs_listing_only) except Exception as e: logger.error('Unhandled exception occured while extracting virtual filesystem: %s' % e) raise e @@ -356,10 +356,10 @@ def main(file : str, out_dir : str = '.', out_pe : str = '', ignore_fs: bool = F else: logger.info('Restoring executable') if not out_pe: - out_pe = os.path.join(out_dir, os.path.basename(file)) - logger.warning('Using default executable save path: %s' % out_pe) + out_pe = os.path.join(out_dir, os.path.basename(in_file)) + logger.info('Using default executable save path: %s' % out_pe) try: - restore_pe(file,out_pe,legacy_pe) + restore_pe(in_file,out_pe,legacy_pe) except Exception as e: logger.error('Unhandled exception occured while restoring executable: %s' % e) @@ -372,14 +372,14 @@ def __main__(): group.add_argument('--ignore-pe',help='Don\'t restore the executable',action='store_true') group.add_argument('--legacy-fs',help='Use legacy mode for filesystem extraction',action='store_true') group.add_argument('--legacy-pe',help='Use legacy mode for PE restoration',action='store_true') - group = parser.add_argument_group('Output') - group.add_argument('--out-dir', help='Output folder',default='.') + group = parser.add_argument_group('Overrides') group.add_argument('--out-pe', help='(If the executable is to be recovered) Where the unpacked EXE is saved. Leave as-is to save it in the output folder.',default='') group = parser.add_argument_group('Input') group.add_argument('file', help='File to be unpacked') + group.add_argument('output', help='Output folder') args = parser.parse_args() - logging.basicConfig(level=args.log_level) - sys.exit(main(args.file,args.out_dir,args.out_pe,args.ignore_fs,args.ignore_pe,args.legacy_fs,args.legacy_pe,args.list)) + logging.basicConfig(level=args.log_level, format='%(levelname)s: %(message)s') + sys.exit(main(args.file,args.output,args.out_pe,args.ignore_fs,args.ignore_pe,args.legacy_fs,args.legacy_pe,args.list)) if __name__ == "__main__": __main__() \ No newline at end of file