ELF File Analysis

Overview

The Executable and Linkable Format (ELF) is the standard binary format for Linux executables, shared libraries (.so), object files (.o), and core dumps. Analyzing ELF headers, sections, segments, symbols, and dynamic linking information reveals a Linux malware sample's capabilities, dependencies, and anomalies.

ELF Structure Overview

┌──────────────────────┐
│     ELF Header       │  (magic, class, type, machine, entry point)
├──────────────────────┤
│  Program Headers     │  (segments for runtime loading)
├──────────────────────┤
│  Section Headers     │  (sections for linking/debugging)
├──────────────────────┤
│     .text            │  (executable code)
├──────────────────────┤
│     .rodata          │  (read-only data, strings)
├──────────────────────┤
│     .data            │  (initialized data)
├──────────────────────┤
│     .bss             │  (uninitialized data)
├──────────────────────┤
│     .dynamic         │  (dynamic linking info)
├──────────────────────┤
│     .symtab/.dynsym  │  (symbol tables)
├──────────────────────┤
│     .strtab/.dynstr  │  (string tables)
└──────────────────────┘

Analysis with readelf

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/

# Display ELF file header
readelf -h sample

# Display program headers (segments)
readelf -l sample

# Display section headers
readelf -S sample

# Display symbol table
readelf -s sample

# Display dynamic symbol table
readelf --dyn-syms sample

# Display dynamic section (shared library dependencies)
readelf -d sample

# Display relocations
readelf -r sample

# Display notes (build ID, ABI tag)
readelf -n sample

# Display all headers (equivalent to -h -l -S)
readelf -e sample

# Display everything
readelf -a sample

ELF Header Fields

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/

# Key fields from readelf -h:
#   Class:        ELF32 or ELF64
#   Type:         EXEC (executable), DYN (shared object/PIE), REL (relocatable)
#   Machine:      Advanced Micro Devices X86-64, ARM, etc.
#   Entry point:  address where execution begins
#   Flags:        architecture-specific flags

readelf -h sample
Field What It Tells You
Class 32-bit vs 64-bit binary
Type EXEC = fixed address, DYN = position-independent (PIE or .so)
Machine Target architecture (x86-64, ARM, MIPS, etc.)
Entry point Where execution starts (verify it points into .text)
Section header count 0 may indicate stripped or packed binary

Analyzing Sections

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/

# List all sections with sizes and flags
readelf -S sample

# Key flags:
#   W (write), A (alloc), X (execute)
#   .text should be AX (alloc + execute, NOT writable)
#   .data should be WA (write + alloc)
#   .rodata should be A (alloc only, read-only)

Section Anomalies to Look For

Anomaly Indicator
.text is writable (WAX) Self-modifying code
Missing .symtab Binary is stripped (common for malware)
No section headers at all Packed or section headers stripped
Unusual section names Packer artifacts (.upx, custom names)
Entry point outside .text Possible packer or code injection
Very large .data or .rodata May contain embedded payloads
.bss much larger than .data Runtime unpacking buffer

Analyzing Dynamic Linking

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/

# Show shared library dependencies
readelf -d sample | grep NEEDED

# Show the RPATH/RUNPATH (custom library search paths)
readelf -d sample | grep -E 'RPATH|RUNPATH'

# Show all dynamic section entries
readelf -d sample

Suspicious RPATH/RUNPATH values (e.g., /tmp, writable directories) may indicate library hijacking attempts.

Analyzing Symbols

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/

# List all symbols (if not stripped)
readelf -s sample

# List dynamic symbols (survives stripping)
readelf --dyn-syms sample

# Demangle C++ symbol names
readelf -s --demangle sample

Analysis with objdump

# GNU Binutils (objdump)
# https://www.gnu.org/software/binutils/

# Display all headers
objdump -x sample

# Display section headers with sizes
objdump -h sample

# Disassemble executable sections
objdump -d sample | head -100

# Disassemble all sections (including data)
objdump -D sample | head -100

# Show dynamic relocations
objdump -R sample

# Show private headers (ELF-specific)
objdump -p sample

Analysis with rabin2

# radare2
# https://github.com/radareorg/radare2

# Show binary info (type, arch, protections)
rabin2 -I sample

# List imports
rabin2 -i sample

# List exports
rabin2 -E sample

# List sections with entropy
rabin2 -S sample

# List symbols
rabin2 -s sample

# List strings
rabin2 -z sample

# Show entry point
rabin2 -e sample

# Show shared libraries
rabin2 -l sample

# Show header fields
rabin2 -H sample

# Calculate hashes
rahash2 -a md5,sha256 sample

# Show security features (canary, NX, PIC, stripped)
rabin2 -I sample | grep -E 'canary|crypto|nx|pic|stripped|static'

Security Feature Checks

# radare2
# https://github.com/radareorg/radare2

# rabin2 -I output includes:
#   canary   true/false  — stack canary protection
#   crypto   true/false  — encrypted sections
#   nx       true/false  — non-executable stack (DEP)
#   pic      true/false  — position-independent code (ASLR support)
#   stripped true/false  — debug symbols removed
#   static   true/false  — statically linked (no shared libraries)

rabin2 -I sample
Feature Malware Significance
nx = false Binary can execute code on the stack
pic = false No ASLR support (fixed addresses, easier exploitation)
stripped = true Debug symbols removed (common for malware)
static = true No library dependencies (self-contained, portable)
canary = false No stack protection (easier to exploit)

Analysis with radare2

# radare2
# https://github.com/radareorg/radare2

# Open binary in analysis mode
r2 -A sample

# Common commands inside r2:
#   afl          — list all functions
#   ii           — list imports
#   iE           — list exports
#   iz           — list strings in data sections
#   izz          — list all strings in the binary
#   iS           — list sections
#   ie           — show entry point
#   axt @sym.func — show cross-references to a function
#   pdf @main    — disassemble main function
#   VV @main     — visual graph mode

Suspicious ELF Characteristics

Common Suspicious Imports

Library / Function Capability
execve, system, popen Process execution
fork, clone Process creation
socket, connect, bind Network communication
send, recv, sendto, recvfrom Data transfer
open, read, write, unlink File operations
mmap, mprotect Memory mapping / permission changes
ptrace Anti-debugging / process injection
dlopen, dlsym Dynamic library loading
getenv, setenv Environment manipulation
chmod, chown Permission changes
kill, signal Signal handling
inotify_init, inotify_add_watch File monitoring

Statically Linked Binaries

Malware is often statically linked to avoid dependency on target system libraries. This makes the binary larger but more portable.

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/

# Check if statically linked (no NEEDED entries)
readelf -d sample | grep NEEDED
# Empty output = statically linked

# Alternatively
file sample
# "statically linked" appears in output for static binaries

Stripped Binaries

# Check if binary is stripped
file sample
# "stripped" or "not stripped" in output

# GNU Binutils (readelf)
# https://www.gnu.org/software/binutils/
readelf -s sample | wc -l
# Very few symbols = stripped

Python Analysis with pyelftools

# pyelftools
# https://github.com/eliben/pyelftools
from elftools.elf.elffile import ELFFile

with open('sample', 'rb') as f:
    elf = ELFFile(f)

    # ELF header info
    print(f"Class:       {elf.elfclass}-bit")
    print(f"Machine:     {elf['e_machine']}")
    print(f"Type:        {elf['e_type']}")
    print(f"Entry point: {hex(elf['e_entry'])}")

    # List sections
    for section in elf.iter_sections():
        print(f"  {section.name:20s}  Size: {section['sh_size']:8d}  "
              f"Flags: {section['sh_flags']:#06x}")

    # List dynamic dependencies
    from elftools.elf.dynamic import DynamicSection
    for section in elf.iter_sections():
        if isinstance(section, DynamicSection):
            for tag in section.iter_tags():
                if tag.entry.d_tag == 'DT_NEEDED':
                    print(f"  Depends on: {tag.needed}")

References

Tools

Official Documentation