diff --git a/fact_extractor/plugins/unpacking/sfx/code/sfx.py b/fact_extractor/plugins/unpacking/sfx/code/sfx.py index b5b3b9a8..4413b681 100644 --- a/fact_extractor/plugins/unpacking/sfx/code/sfx.py +++ b/fact_extractor/plugins/unpacking/sfx/code/sfx.py @@ -1,7 +1,10 @@ from __future__ import annotations +import logging from pathlib import Path +import lief + from plugins.unpacking.sevenz.code.sevenz import unpack_function as sevenz NAME = 'SFX' @@ -11,7 +14,7 @@ 'application/x-executable', 'application/x-pie-executable', ] -VERSION = '0.2.1' +VERSION = '0.3.0' EXCLUDED_FILE_NAMES_1 = {'.bss', '.data', '.text'} EXCLUDED_FILE_NAMES_2 = {str(i) for i in range(20)} @@ -31,6 +34,8 @@ def unpack_function(file_path, tmp_dir): if _extraction_result_is_invalid(extraction_dir): clean_directory(extraction_dir) + if output := _try_to_extract_piggy_data(file_path, tmp_dir): + return {'output': output} meta['output'] = ( "Normal executable files will not be extracted.\n\nPlease report if it's a self extracting archive" ) @@ -46,6 +51,27 @@ def clean_directory(directory: Path): clean_directory(child) +def _try_to_extract_piggy_data(file_path: str, tmp_dir: str) -> str | None: + """ + Some build tools put the compressed kernel image in a .piggydata section in the ELF file + """ + try: + if (elf := lief.ELF.parse(file_path)) is None: + return None + sections = {s.name: s for s in elf.sections} + if '.piggydata' not in sections: + return None + piggy = sections['.piggydata'] + output_file = Path(tmp_dir) / '.piggydata' + with Path(file_path).open('rb') as fp_in, output_file.open('wb') as fp_out: + fp_in.seek(piggy.offset) + fp_out.write(fp_in.read(piggy.size)) + return f'Saved .piggydata section from offset {piggy.offset} with size {piggy.size} to .piggydata' + except Exception as err: + logging.exception(f'Error when trying to extract .piggydata section from ELF: {err}') + return None + + # ----> Do not edit below this line <---- def setup(unpack_tool): for item in MIME_PATTERNS: diff --git a/fact_extractor/plugins/unpacking/sfx/test/data/piggy.elf b/fact_extractor/plugins/unpacking/sfx/test/data/piggy.elf new file mode 100755 index 00000000..2e256ef7 Binary files /dev/null and b/fact_extractor/plugins/unpacking/sfx/test/data/piggy.elf differ diff --git a/fact_extractor/plugins/unpacking/sfx/test/test_sfx.py b/fact_extractor/plugins/unpacking/sfx/test/test_sfx.py index c9ac1ed9..aa77a623 100644 --- a/fact_extractor/plugins/unpacking/sfx/test/test_sfx.py +++ b/fact_extractor/plugins/unpacking/sfx/test/test_sfx.py @@ -33,3 +33,9 @@ def test_self_extracting_archives(self): self.check_unpacking_of_standard_unpack_set( TEST_DATA_DIR / file, additional_prefix_folder='get_files_test', output=True ) + + def test_piggy_extraction(self): + test_file = TEST_DATA_DIR / 'piggy.elf' + files, meta_data = self.unpacker.extract_files_from_file(str(test_file), self.tmp_dir.name) + assert len(files) == 1 + assert Path(files[0]).read_bytes() == b'foobar\\ntest 1234\n' diff --git a/requirements-unpackers.txt b/requirements-unpackers.txt index 8056fe40..0ac6f44b 100644 --- a/requirements-unpackers.txt +++ b/requirements-unpackers.txt @@ -34,3 +34,5 @@ pylibfdt ~= 1.7.1 structlog~=25.1.0 # unblob unblob~=25.1.8 +# SFX unpacker +lief~=0.17.1