A C++ POC for advanced process memory scanning that attempts to detect a number of malicious techniques used by threat actors & those which have been incorporated into open-source user-mode rootkits.

ELFieScanner inspects every running process (both x86/x64) and its corresponding loaded libraries to look for evil. It then outputs the resultant telemetry into a NDJSON file. ELFieScanner offers four main scanner capabilities to look for:

  • Shared Object injection techniques.
  • Entry point manipulation techniques.
  • Shellcode injection & Process hollowing.
  • API hooking.

Each technique ELFieScanner looks for is displayed within config.json.

This config file provides the user with the ability to switch on/off each heuristic and also modify the fuzzy hash thresholds required to generate any output files.

ELFieScanner will produce three output files, one for each scan type in the format hostname_scannerType_output_arch.json.

Events will only be generated for processes that have fired on one or more of the heuristics.

Kibana mappings have also been provided for the output files should one wish to index the data to make threat hunting analysis easier.

These can be found in the elk_mappings folder.

A more detailed description of each scanning type can be found in the Scanner descriptions section of this README.

Usage:

Configuration And Heuristics

Each heuristic is prepended with the initials of which scanner type it belongs to:

  • es Entrypoint scanner.
  • ls Library scanner.
  • ss Shellcode scanner.

To turn on a heuristic set the value to true. To turn off a heuristic set the value to false.

HeuristicDescription
es_section_hdr_missingSection headers can been stripped from a binary (this is suspicious but not necessarily malicious). Stripping the section headers makes reverse engineering of the binary more difficult. However it could be done make the binary smaller. The e_shoff This member holds the section header table’s file offset in bytes. If the file has no section header table, this member holds zero.
es_phdr_wrong_locationCheck to see if if the program headers start in the expected place (immediately after the ELF32_Ehdr/ELF64_Ehdr) e.g. 64 bytes offset for 64-bit, or 52 bytes offset for 32-bit.
es_proc_missing_disk_backingCheck the process is not backed by disk executable. More of an anomaly rather than a detection.
es_proc_text_segment_missing_diskCheck to see if the .text segment is present on disk. This should always be present unless the binary is still packed/obfuscated in memory.
es_proc_text_segment_missing_memIs the .text segment is present in memory. This should always be present unless the disk backed binary is packed/obfuscated.
es_proc_entry_points_not_in_textCheck to see if the e_entry field does NOT point within the .text segment. This should always be the case apart from special cases such as ‘VBoxService’.
es_proc_entry_points_not_matchingCheck to see if the e_entry values for process & disk binary match.
es_proc_entry_fuzzy_scoreCheck the e_entry for the libc linked process matches the expected initialization code for ‘libc_start_main’. Highly suspicious unless this is for an interpreter process e.g. ‘/usr/bin/python’ OR container processes ‘/usr/sbin/VBoxService’. If the real score is below es_proc_entry_fuzzy_score then result will be generated. Set fuzzy score threshold (0-100)
es_proc_init_fini_not_in_textIf either:
1. A process init/fini sections that don’t appear in .text segment
2. A process preinit/init/fini array functions that don’t point within the .text segment.
es_proc_init_not_at_text_startFor processes it is expected the .init code block should begin at the start of the .text segment. NOTE: this is not expected for modules.
es_mod_missing_disk_backingCheck to see if module is backed by disk executable. More of an anomaly rather than a detection. Check against every module.
es_mod_entry_points_not_in_textCheck the e_entry field points within .text segment of the module. This should always be the case for modules. Check against every module.
es_mod_entry_points_not_matchingCheck to see the e_entry values for module and disk match. Check against every module.
es_mod_init_fini_not_in_textChecks every module for :
1. module init/fini sections that don’t appear in .text segment
2. module preinit/init/fini array functions that don’t point within the .text segment.
ls_elf_in_anonymous_mappingAn ELF header found in an anonymous memory mapping.
ls_executable_anonymous_mappingExecutable anonymous memory mapping present.
ls_phdr_wrong_locationProgram headers in wrong location.
ls_mod_missing_disk_backingA module doesn’t have disk backing. Checks for every module.
ls_module_not_in_procmapsA module doesn’t exist in /proc/pid/maps. Checks for every module.
ls_module_not_in_linkmapA module doesn’t exist in link_map structure. Checks for every module.
ls__libc_dlopen_mode_in_gotA Global Offset table (GOT) address points __libc_dlopen_mode func.
ls__libc_dlopen_mode_in_rodata__libc_dlopen_mode string in rodata section.
ls_dtnull_missingDT_NULL missing from dynamic section.
ls_dtdebug_missingDT_DEBUG missing from dynamic section.
ls_dtneeded_incorrect_orderDT_NEEDED in non-sequential (incorrect) order in dynamic section.
ls_dynstr_manipulatedDynamic string table manually manipulated.
ls_ldpreload_setLD_PRELOAD populated.
ls_ldpreload_hookingLD_PRELOAD hooking present.
ls_ldconfig_setLD_CONFIG populated.
ls_ldpath_setLD_PATH manipulated.
ls_dynamic_segment_missingDynamic segment missing.
ss_proc_missing_disk_backingProcess missing disk backed binary.
ss_proc_phdr_memory_disk_mismatchThe number of process program headers in memory should equal that of its corresponding disk binary. Any mismatch indicates a segment has either been added or taken away in memory.
ss_rwx_present_diskProcess memory contains a segment with Read/write & execute permissions.
ss_rwx_present_memProcess binary contains a segment with Read/write & execute permissions.
ss_dynamic_segment_missingDynamic segment missing. Can indicate packing.
ss_memfd_mapping_foundProcess loaded directly from memory using memfd_create()
ss_mod_missing_disk_backingmodule missing disk backed binary. Check for all modules
ss_mod_phdr_memory_disk_mismatchThe number of module program headers in memory should equal that of its corresponding disk binary. Any mismatch indicates a segment has either been added or taken away in memory. Check for all modules.
ss_mod_rwx_header_present_diskModule binary contains a segment with Read/write & execute permissions. Check for all modules.
ss_mod_rwx_header_present_memModule memory contains a segment with Read/write & execute permissions. Checks against all modules.
ss_proc_scoreThis measures the similarity between process disk & memory text (RX) segments. A low score indicates significant changes (and thus possible injection of code). If the real score is below ss_proc_score then result will be generated. Set fuzzy score threshold (0-100)
ss_lowest_mod_scoreThis measures the similarity between module disk & memory text (RX) segments. A low score indicates significant changes (and thus possible injection of code). If the real score is below ss_lowest_mod_score then result will be generated. Set fuzzy score threshold (0-100)

LEAVE A REPLY

Please enter your comment!
Please enter your name here