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
└─────────────────────────┘
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!
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
objdump: Disassembly
# Disassemble .text section
objdump -d -M intel sample
# All sections with content
objdump -s sample
strings + FLOSS: String extraction
# Basic strings
strings -a sample | grep -iE 'http|\.onion|/bin/|socket|connect'
# FLOSS for obfuscated strings
floss sample
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
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:
unlinkon own path - [ ] Crypto imports: functions from OpenSSL, mbedTLS
- [ ]
.init_arraywith 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:
- Defense Kit — Sigma + YARA + IOCs per malware family
- Detection Gap Analyzer — Check your MITRE ATT&CK coverage
- CTI Assessment — Rate your threat intel maturity in 5 min
Top comments (0)