DEV Community

David Moya
David Moya

Posted on Originally published at malwareintel.es

ELF 101: Linux Executable Format for Malware Analysts

If you come from Windows malware analysis, PE is second nature. On Linux, the equivalent is ELF (Executable and Linkable Format).

With Linux malware surging (ESXi ransomware, IoT botnets, cloud cryptominers), analysts need ELF fluency at the same level as PE. This guide covers the structure, tools, and indicators that separate a legitimate ELF binary from a malicious one.

ELF Structure at a Glance

Offset 0x0000
┌─────────────────────────┐
│      ELF Header         │  52/64 bytes (32/64-bit)
│  Magic: 7f 45 4c 46     │  "\x7fELF"
├─────────────────────────┤
│  Program Header Table   │  Segments (for loading)
│  (PHT)                  │  Used by the kernel loader
├─────────────────────────┤
│                         │
│       Sections          │  .text, .data, .rodata,
│   (actual content)      │  .dynsym, .got, .plt ...
│                         │
├─────────────────────────┤
│  Section Header Table   │  Sections (for linking)
│  (SHT)                  │  Used by linker/debugger
└─────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Key difference vs PE: ELF has two views of the same content:

  • Segments (Program Headers): how the kernel loads the binary into memory
  • Sections (Section Headers): how the linker organizes content

A binary needs segments to execute but does not need sections. Malware often strips the Section Header Table to hinder analysis, and the binary still runs because the kernel only uses Program Headers.

ELF Header: What to Look For

Field Offset Meaning
e_ident[EI_MAG] 0x00 Magic: \x7fELF
e_ident[EI_CLASS] 0x04 1=32-bit, 2=64-bit
e_type 0x10 2=Executable, 3=Shared Object
e_machine 0x12 0x03=x86, 0x3E=x86-64, 0x28=ARM, 0xB7=AArch64
e_entry 0x18 Entry point address
e_shoff 0x20 Section Header Table offset (0 if stripped)
e_shnum 0x3C Number of Section Headers (0 if stripped)
$ readelf -h malware_sample

ELF Header:
  Class:                  ELF64
  Type:                   EXEC (Executable file)
  Machine:                Advanced Micro Devices X86-64
  Entry point address:    0x400a30
  Start of section headers: 0 (bytes into file)  ← STRIPPED!
  Number of section headers: 0                    ← STRIPPED!
Enter fullscreen mode Exit fullscreen mode

Malware Indicators in the ELF Header

Indicator Meaning
e_shoff = 0, e_shnum = 0 Section headers stripped. Common in malware to block analysis
e_machine = ARM/MIPS Possible IoT malware (Mirai variants)
e_type = ET_DYN PIE executable, not suspicious per se
Entry point at unusual address Possible packing or manipulation

Sections That Matter for Malware Analysis

Section Content Why it matters
.text Executable code The malware's main logic
.rodata Read-only data Strings: C2 URLs, messages, keys
.data Initialized globals Config, encryption keys
.dynsym Dynamic symbol table Imported/exported functions (like IAT/EAT)
.got Global Offset Table Function pointers (hooking target)
.plt Procedure Linkage Table Dynamic call stubs
.init_array Constructor functions Code that runs before main()

.init_array is particularly interesting for malware: code here executes before main(), making it a common persistence and anti-analysis point.

Essential Tools

readelf: Header and section inspection

# Full headers
readelf -a sample

# Just program headers (segments)
readelf -l sample

# Dynamic symbols (imports)
readelf --dyn-syms sample
Enter fullscreen mode Exit fullscreen mode

objdump: Disassembly

# Disassemble .text section
objdump -d -M intel sample

# All sections with content
objdump -s sample
Enter fullscreen mode Exit fullscreen mode

strings + FLOSS: String extraction

# Basic strings
strings -a sample | grep -iE 'http|\.onion|/bin/|socket|connect'

# FLOSS for obfuscated strings
floss sample
Enter fullscreen mode Exit fullscreen mode

file + checksec: Quick triage

$ file sample
sample: ELF 64-bit LSB executable, x86-64, statically linked, stripped

$ checksec --file=sample
RELRO    STACK CANARY  NX       PIE
No RELRO No canary     NX disabled  No PIE
Enter fullscreen mode Exit fullscreen mode

No RELRO + No canary + NX disabled + No PIE = likely malware. Legitimate software enables these protections.

Static vs Dynamic Linking: Why It Matters

Type Indicator Malware implication
Dynamically linked Has .dynsym, .plt, .got Imports visible, easier to analyze
Statically linked file says "statically linked" All library code embedded, harder to identify functions, common in Go/Rust malware

Go and Rust malware is almost always statically linked, producing large binaries (2-10 MB) with thousands of functions. Tools like GoReSym or redress help recover function names in stripped Go binaries.

Red Flags Checklist

When triaging an ELF binary, check for:

  • [ ] Stripped sections (e_shnum = 0)
  • [ ] UPX or custom packing (strings sample | grep UPX)
  • [ ] Anti-debug: ptrace(PTRACE_TRACEME) in imports
  • [ ] Network functions: socket, connect, send, recv
  • [ ] Process manipulation: fork, execve, prctl
  • [ ] File operations in /proc/, /etc/cron, /tmp/
  • [ ] Self-deletion: unlink on own path
  • [ ] Crypto imports: functions from OpenSSL, mbedTLS
  • [ ] .init_array with suspicious entries
  • [ ] Abnormally large .rodata (embedded configs/payloads)

What's Next

This is Part 1 of our Linux Malware series. Coming up:

  • Part 2: ELF Packers and Obfuscation
  • Part 3: Linux Rootkits (Diamorphine, Reptile, BPFDoor)
  • Part 4: Forensics on Linux Endpoints

Originally published at MalwareIntel. MalwareIntel is a free threat intelligence platform monitoring 13 public CTI sources.

Free resources:

Top comments (0)