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:
Willi Ballenthin
2023-04-03 08:41:47 +02:00
committed by GitHub
2 changed files with 106 additions and 5 deletions
+89 -5
View File
@@ -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"
+17
View File
@@ -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"