mirror of
https://github.com/mandiant/capa.git
synced 2026-07-11 22:51:52 -07:00
Merge pull request #1422 from yelhamer/feature-symtab-os-guess
ELF OS detection: add support for guessing that's based on .symtab entries
This commit is contained in:
@@ -88,6 +88,7 @@ class Shdr:
|
||||
offset: int
|
||||
size: int
|
||||
link: int
|
||||
entsize: int
|
||||
buf: bytes
|
||||
|
||||
|
||||
@@ -320,12 +321,12 @@ class ELF:
|
||||
shent = self.shbuf[shent_offset : shent_offset + self.e_shentsize]
|
||||
|
||||
if self.bitness == 32:
|
||||
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link = struct.unpack_from(
|
||||
self.endian + "IIIIIII", shent, 0x0
|
||||
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, _, _, sh_entsize = struct.unpack_from(
|
||||
self.endian + "IIIIIIIIII", shent, 0x0
|
||||
)
|
||||
elif self.bitness == 64:
|
||||
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link = struct.unpack_from(
|
||||
self.endian + "IIQQQQI", shent, 0x0
|
||||
sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, _, _, sh_entsize = struct.unpack_from(
|
||||
self.endian + "IIQQQQIIQQ", shent, 0x0
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
@@ -337,7 +338,7 @@ class ELF:
|
||||
if len(buf) != sh_size:
|
||||
raise ValueError("failed to read section header content")
|
||||
|
||||
return Shdr(sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, buf)
|
||||
return Shdr(sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link, sh_entsize, buf)
|
||||
|
||||
@property
|
||||
def section_headers(self):
|
||||
@@ -603,6 +604,48 @@ class SHNote:
|
||||
return ABITag(os, kmajor, kminor, kpatch)
|
||||
|
||||
|
||||
class SymTab:
|
||||
def __init__(self, endian: str, bitness: int, symtab_buf: bytes, symtab_entsize: int, symtab_sz: int, strtab_buf: bytes, strtab_sz: int) -> None:
|
||||
self.symbols = []
|
||||
self.symnum = int(symtab_sz / symtab_entsize)
|
||||
self.entsize = symtab_entsize
|
||||
|
||||
self.strings = strtab_buf
|
||||
self.strings_sz = strtab_sz
|
||||
|
||||
self._parse(endian, bitness, symtab_buf)
|
||||
|
||||
def _parse(self, endian: str, bitness: int, symtab_buf: bytes) -> None:
|
||||
"""
|
||||
return the symbol's information in
|
||||
the order specified by sys/elf32.h
|
||||
"""
|
||||
for i in range(self.symnum):
|
||||
if bitness == 32:
|
||||
name, value, size, info, other, shndx = struct.unpack_from(endian+"IIIBBH", symtab_buf, i*self.entsize)
|
||||
elif bitness == 64:
|
||||
name, info, other, shndx, value, size = struct.unpack_from(endian+"IBBBQQ", symtab_buf, i*self.entsize)
|
||||
|
||||
self.symbols.append((name, value, size, info, other, shndx))
|
||||
|
||||
def fetch_str(self, offset) -> str:
|
||||
"""
|
||||
fetch a symbol's name from symtab's
|
||||
associated strings' section (SHT_STRTAB)
|
||||
"""
|
||||
for i in range(offset, self.strings_sz):
|
||||
if self.strings[i] == 0:
|
||||
return self.strings[offset:i].decode()
|
||||
|
||||
def get_symbols(self) -> Iterator[Tuple[int, int, int, int, int, int]]:
|
||||
"""
|
||||
return a tuple: (name, value, size, info, other, shndx)
|
||||
for each symbol contained in the symbol table
|
||||
"""
|
||||
for symbol in self.symbols:
|
||||
yield symbol
|
||||
|
||||
|
||||
def guess_os_from_osabi(elf) -> Optional[OS]:
|
||||
return elf.ei_osabi
|
||||
|
||||
@@ -730,6 +773,41 @@ def guess_os_from_needed_dependencies(elf) -> Optional[OS]:
|
||||
return None
|
||||
|
||||
|
||||
def guess_os_from_symtab(elf) -> Optional[OS]:
|
||||
SHT_SYMTAB = 0x2
|
||||
SHT_STRTAB = 0x3
|
||||
strtab_buf = symtab_buf = None
|
||||
|
||||
for shdr in elf.section_headers:
|
||||
if shdr.type == SHT_STRTAB:
|
||||
strtab_buf, strtab_sz= shdr.buf, shdr.size
|
||||
|
||||
elif shdr.type == SHT_SYMTAB:
|
||||
symtab_buf, symtab_entsize, symtab_sz = shdr.buf, shdr.entsize, shdr.size
|
||||
|
||||
if None in (strtab_buf, symtab_buf):
|
||||
# executable does not contain a symbol table
|
||||
# or the symbol's names are stripped
|
||||
return None
|
||||
|
||||
symtab = SymTab(
|
||||
elf.endian, elf.bitness, symtab_buf, symtab_entsize, symtab_sz, strtab_buf, strtab_sz
|
||||
)
|
||||
|
||||
keywords = {
|
||||
OS.LINUX: ['linux', '/linux/',],
|
||||
}
|
||||
|
||||
for name, *_ in symtab.get_symbols():
|
||||
sym_name = symtab.fetch_str(name)
|
||||
|
||||
for os, hints in keywords.items():
|
||||
if any(map(lambda x: x in sym_name, hints)):
|
||||
return os
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def detect_elf_os(f) -> str:
|
||||
"""
|
||||
f: type Union[BinaryIO, IDAIO]
|
||||
@@ -754,6 +832,9 @@ def detect_elf_os(f) -> str:
|
||||
needed_dependencies_guess = guess_os_from_needed_dependencies(elf)
|
||||
logger.debug("guess: needed dependencies: %s", needed_dependencies_guess)
|
||||
|
||||
symtab_guess = guess_os_from_symtab(elf)
|
||||
logger.debug("guess: pertinent symbol name: %s", symtab_guess)
|
||||
|
||||
ret = None
|
||||
|
||||
if osabi_guess:
|
||||
@@ -774,6 +855,9 @@ def detect_elf_os(f) -> str:
|
||||
elif needed_dependencies_guess:
|
||||
ret = needed_dependencies_guess
|
||||
|
||||
elif symtab_guess:
|
||||
ret = symtab_guess
|
||||
|
||||
return ret.value if ret is not None else "unknown"
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ def test_elf_sh_notes():
|
||||
# guess: sh notes: OS.LINUX
|
||||
# guess: linker: None
|
||||
# guess: ABI versions needed: None
|
||||
# guess: symtab: None
|
||||
# guess: needed dependencies: None
|
||||
path = get_data_path_by_name("2f7f5f")
|
||||
with open(path, "rb") as f:
|
||||
@@ -32,6 +33,7 @@ def test_elf_pt_notes():
|
||||
# guess: sh notes: OS.LINUX
|
||||
# guess: linker: OS.LINUX
|
||||
# guess: ABI versions needed: OS.LINUX
|
||||
# guess: symtab: None
|
||||
# guess: needed dependencies: None
|
||||
path = get_data_path_by_name("7351f.elf")
|
||||
with open(path, "rb") as f:
|
||||
@@ -44,6 +46,7 @@ def test_elf_so_needed():
|
||||
# guess: sh notes: OS.HURD
|
||||
# guess: linker: None
|
||||
# guess: ABI versions needed: OS.HURD
|
||||
# guess: symtab: None
|
||||
# guess: needed dependencies: OS.HURD
|
||||
path = get_data_path_by_name("b5f052")
|
||||
with open(path, "rb") as f:
|
||||
@@ -56,7 +59,21 @@ def test_elf_abi_version_hurd():
|
||||
# guess: sh notes: OS.HURD
|
||||
# guess: linker: None
|
||||
# guess: ABI versions needed: OS.HURD
|
||||
# guess: symtab: None
|
||||
# guess: needed dependencies: None
|
||||
path = get_data_path_by_name("bf7a9c")
|
||||
with open(path, "rb") as f:
|
||||
assert capa.features.extractors.elf.detect_elf_os(f) == "hurd"
|
||||
|
||||
|
||||
def test_elf_symbol_table():
|
||||
# guess: osabi: None
|
||||
# guess: ph notes: None
|
||||
# guess: sh notes: None
|
||||
# guess: linker: None
|
||||
# guess: ABI versions needed: None
|
||||
# guess: symtab: OS.LINUX
|
||||
# guess: needed dependencies: None
|
||||
path = get_data_path_by_name("2bf18d")
|
||||
with open(path, "rb") as f:
|
||||
assert capa.features.extractors.elf.detect_elf_os(f) == "linux"
|
||||
|
||||
Reference in New Issue
Block a user