Skip to content

Commit

Permalink
Refactored for LIEF 0.14.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dhondta committed Feb 5, 2024
1 parent 4526a33 commit 43ddb58
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 46 deletions.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ classifiers = [
"Topic :: Security",
]
dependencies = [
"lief",
"lief>=0.14.0",
]
dynamic = ["version"]

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
lief
lief>=0.14.0
2 changes: 1 addition & 1 deletion src/reminder/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.1.1
1.2.0
92 changes: 49 additions & 43 deletions src/reminder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import lief
import os

lief.logging.disable()


__all__ = ["REMINDer"]

Expand All @@ -15,55 +17,16 @@


class REMINDer:
logger = None

def __init__(self, entropy_threshold=None, logger=None, **kwargs):
""" Configure the detector with various parameters. """
self.__entropy = entropy_threshold
self.logger = logger

def _get_ep_and_section(self):
""" Helper for computing the entry point and finding its section for each supported format.
:param binary: LIEF-parsed binary object
:return: (binary_type, ep_file_offset, name_of_ep_section)
"""
bn = self.binary
btype, fn = bn.format.name, os.path.basename(bn.name)
try:
if btype in ["ELF", "MACHO"]:
ep = bn.virtual_address_to_offset(bn.entrypoint)
# e.g. with UPX, the section table header gets packed too, hence LIEF gives 0 section parsed
ep_section = bn.section_from_offset(ep) if len(bn.sections) > 0 else None
# when #sections=0, the sample will be considered as packed anyway, so set wflag=False
wflag = ep_section.has(lief.ELF.SECTION_FLAGS.WRITE) if len(bn.sections) > 0 else False
elif btype == "PE":
ep_addr = bn.optional_header.addressof_entrypoint
ep, ep_section = bn.rva_to_offset(ep_addr), bn.section_from_rva(ep_addr)
wflag = ep_section.has_characteristic(lief.PE.SECTION_CHARACTERISTICS.MEM_WRITE)
else:
if self.logger:
self.logger.warning("%s has an unsupported format" % fn)
else:
raise OSError("%s has an unsupported format" % fn)
return None, None, None, False
return btype, ep, ep_section, wflag
except (AttributeError, lief.not_found, lief.conversion_error):
return btype, None, None, False
REMINDer.logger = logger

def detect(self, executable):
""" Analyze the input executable file using the custom heuristic. """
# parse the input executable using LIEF (catch warnings from stderr)
tmp_fd, null_fd = os.dup(2), os.open(os.devnull, os.O_RDWR)
os.dup2(null_fd, 2)
self.binary = lief.parse(str(executable))
os.dup2(tmp_fd, 2) # restore stderr
os.close(null_fd)
if self.binary is None:
if self.logger:
self.logger.error("%s is not an executable" % executable)
return
else:
raise TypeError("%s is not an executable" % executable)
# get the EP, EP's section and the WRITE flag for EP's section
btype, ep, ep_section, ep_section_writable = self._get_ep_and_section()
btype, ep, ep_section, ep_section_writable = REMINDer._get_ep_and_section(str(executable))
# in rare cases, it may occur that the EP could not be determiend with LIEF, then output accordingly
if ep is None:
return "?"
Expand All @@ -86,4 +49,47 @@ def detect(self, executable):
if ep_section.entropy > threshold:
return True
return False

@staticmethod
def _log(msg, exception, level="error"):
""" Helper for either logging a message if REMINDer.logger is defined or raising an exception. """
l = REMINDer.logger
if l:
getattr(l, level)(msg)
else:
raise exception(msg)

@staticmethod
def _get_ep_and_section(path):
""" Helper for computing the entry point and finding its section for each supported format.
:param binary: LIEF-parsed binary object
:return: (binary_type, ep_file_offset, name_of_ep_section)
"""
# parse the input executable using LIEF (catch warnings from stderr)
bn =lief.parse(path)
if bn is None:
return REMINDer._log(f"{path} is not an executable", TypeError)
btype, fn = bn.format.__name__, os.path.basename(path)
# get the EP, EP's section and the WRITE flag for EP's section
try:
if btype in ["ELF", "MACHO"]:
ep = bn.virtual_address_to_offset(bn.entrypoint)
# e.g. with UPX, the section table header gets packed too, hence LIEF gives 0 section parsed
ep_section = bn.section_from_offset(ep) if len(bn.sections) > 0 else None
# when #sections=0, the sample will be considered as packed anyway, so set wflag=False
wflag = ep_section.has(lief.ELF.SECTION_FLAGS.WRITE if btype == "ELF" else \
lief.MachO.SECTION_FLAGS.PURE_INSTRUCTIONS) if len(bn.sections) > 0 else False
elif btype == "PE":
ep_addr = bn.optional_header.addressof_entrypoint
ep, ep_section = bn.rva_to_offset(ep_addr), bn.section_from_rva(ep_addr)
wflag = ep_section.has_characteristic(lief.PE.Section.CHARACTERISTICS.MEM_WRITE)
else:
REMINDer._log(f"{fn} has an unsupported format", OSError, "warning")
return None, None, None, False
return btype, ep, ep_section, wflag
#except AttributeError:
# return btype, None, None, False
except Exception as e:
REMINDer._log(str(e), RuntimeError)
return btype, None, None, False

0 comments on commit 43ddb58

Please sign in to comment.