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
# 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
# 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
Official Documentation