name: Linux Malware Analysis description: Expert ELF malware analysis — packing, toolchain ID, kill chain, persistence, C2, rootkits, cryptominers, Go/Rust/Mirai patterns, MITRE ATT&CK mapping tags: [malware, linux, elf, iot, botnet, rootkit, miner, go, rust]
Task: Linux Malware Analysis. You are a senior malware analyst examining a potentially malicious ELF binary. Defang ALL IOCs in output (hxxps://, [.] notation). Work methodically through each phase. Do not skip phases.
Phase 0: Binary Triage
Run these first — the answers change every subsequent decision.
get_binary_info— ELF class (32/64), architecture, endianness, linkage type, entry point, section count, function countlist_segments— check PT_INTERP (non-standard interpreter path = backdoor dropper), segment permissions (W+X = suspicious), entropy per segment
Determine compiler toolchain:
search_stringsfor:runtime.,go.itab,panicked at→ Go binarysearch_stringsfor:core::panicking,_ZN(Rust name mangling),.L__unnamed→ Rust binary- Import of
pthread_create+libc.so→ standard C/C++ search_stringsfor UPX magic, check function count vs binary size
Packing triage:
| Signal | Meaning |
|---|---|
"UPX!" string present |
UPX (may have corrupted header) |
| <5 functions in a binary >50KB | Packer stub only |
| Entry point at high address / near end of segment | Self-extracting loader |
| W+X segment | Runtime code decryption / shellcode loading |
No .symtab, no section headers |
Stripped (normal) but also packing indicator |
search_strings returns very few results (<10) in large binary |
String encryption |
UPX header corruption (common in IoT/Mirai variants): The UPX magic 0x55505821 is replaced with random bytes to prevent automated unpacking, but the binary still executes. Note in report, recommend dynamic analysis for full unpacking.
Architecture-specific expectations:
x86_64: Desktop/server malware, containers, cloud; most tools work nativelyaarch64/arm: IoT, embedded, mobile-adjacent; check for ARM debug register checksmipsel/mipseb: Router/DVR malware (Mirai family); MIPS delay slots complicate disassemblyppc/ppc64: Rare; router firmware (old Synology, QNAP)
Phase 1: ELF Structure Analysis
This phase reveals backdoors in the loader mechanism that pure string/import analysis misses.
PT_INTERP (Dynamic Linker)
Check the interpreter path via list_segments. Standard values:
- x86_64:
/lib64/ld-linux-x86-64.so.2 - aarch64:
/lib/ld-linux-aarch64.so.1 - mipsel:
/lib/ld-uClibc.so.0(or musl on newer IoT)
Non-standard interpreter = binary uses a rogue dynamic linker. High confidence malware.
.init_array / .fini_array (Constructor/Destructor Chains)
Use get_sections or search_binary for .init_array. Decompile each function pointer in the array — malware hides initialization logic here (anti-debug setup, self-decryption, first-stage drops) that runs before main().
Pattern: Array at fixed VA with multiple function pointers → decompile each one individually.
PLT/GOT Analysis
In stripped binaries, PLT entries are often the only way to identify library calls. Each PLT stub is a jmp [GOT_entry] — the target GOT slot tells you which library function.
When IDA fails to resolve GOT entries automatically:
- Find
.pltsection start - Each stub is 16 bytes (x86_64):
FF 25 XX XX XX XX=jmp [rip+offset] - Calculate GOT address =
stub_addr + 6 + offset - Mark the GOT slot and add a comment with the function name
IFUNC Resolvers (GNU_IFUNC)
Symbol type STT_GNU_IFUNC — resolver function called at load time returns the actual implementation. XZ backdoor (CVE-2024-3094) abused this mechanism. If you see IFUNC symbols for unexpected functions, decompile the resolver immediately.
Hidden Init Vectors
.ctors/.dtors(legacy): same purpose as init/fini_array, older GCCpreinit_array: Runs before init_array, even before_startin some configurationsDT_INITentry in dynamic section: Single init function pointer, separate from array
Phase 2: Reconnaissance
Batch these calls — they can run in parallel:
list_imports— group by category:Import cluster Capability socket,connect,bind,listen,accept,send,recvNetwork C2 execve,execvp,fork,clone,posix_spawnProcess execution ptraceAnti-debug OR debugger (rare in malware) init_module,finit_moduleKernel module loading = rootkit memfd_createFileless execution mmap+mprotect(both present)Shellcode loading / runtime decryption dlopen,dlsymDynamic library loading / hooking inotify_*File system monitoring prctlProcess name spoofing ( PR_SET_NAME) or capabilitiescapset,setuid,setreuid,setgidPrivilege manipulation bpfeBPF rootkit or monitoring list_exports— exported symbols reveal shared library role or hooking targetssearch_strings— look for Linux-specific IOC clusters:Persistence paths:
/etc/crontab /var/spool/cron /etc/cron.d /etc/systemd/system /etc/init.d /etc/rc.local /etc/profile.d /etc/ld.so.preload ~/.bashrc ~/.profile ~/.ssh/authorized_keys /etc/modules /etc/udev/rules.d /proc/sys/kernel/modules_disabledEvasion and anti-analysis:
/proc/self/status (TracerPid check) /proc/self/exe (self-path / unlink-self) /proc/self/maps (memory layout inspection) /proc/1/cgroup (container detection) /sys/class/dmi/id (VM detection: VirtualBox, VMware, QEMU, KVM) /sys/hypervisor (Xen detection) LD_PRELOAD (self-injection or hook evasion)Credential targets:
/etc/passwd /etc/shadow /etc/sudoers ~/.ssh/id_rsa ~/.ssh/id_ed25519 ~/.ssh/known_hosts ~/.aws/credentials ~/.gcp ~/.kube/config .mozilla/firefox google-chrome Login Data CookiesFileless execution signals:
memfd: /proc/self/mem /dev/shm /tmp/Rootkit signals:
kallsyms /proc/kallsyms sys_call_table ftrace kprobe uprobe /dev/kmem
Phase 3: Malware Classification
Based on Phase 2 findings, determine the primary classification before deep-diving:
Mirai / IoT Botnet
Fingerprints: MIPS/ARM architecture, UPX-packed, table.c encrypted string pattern, process killer, scanner loop.
Key analysis steps:
- Find the string decryption function — original Mirai XORs with
0xDEADBEEF; variants use XOR with other keys, XXTEA, RC4, or TEA. Look for a loop with XOR and a fixed constant in.rodata. - Decrypt all table entries — each is a null-terminated string XOR'd with the key
- Identify the three core modules by function pattern:
- Scanner: TCP SYN probe loop + credential dictionary lookup
- Killer: Process enumeration + SIGKILL loop (kills other malware / debuggers)
- Attacker: Switch on attack type (UDP flood=0, TCP flood, ACK flood, HTTP flood, DNS flood)
- Extract C2 IP/domain from decrypted strings; look for
connect()call chain from attacker init - Check for
fork()+daemon()→ background persistence without file
2024+ variants also add: SSH brute force (NoaBot), crypto mining (stratum beacon), RC4 string table, anti-VM checks.
Cryptominer
Fingerprints: stratum+tcp:// or stratum2+tcp:// strings, pool domain strings, nice() + sched_setaffinity() calls, XMRig-specific strings (RandomX, xmrig, donate.v2.xmrig.com).
Key analysis steps:
- Search strings for stratum protocol:
search_strings "stratum"+search_strings "pool" - Find the CPU affinity call chain:
sched_setaffinity→ check which cores are claimed - Check for
nice(-20)— stealing maximum CPU priority - Embedded XMRig: May be base64-encoded or gzip'd in
.rodata; look for large blob + decode routine - GPU miner: Look for
cudaLaunchKernel/clEnqueueNDRangeKernelorlibcuda.so/libOpenCL.sodlopen
Linux Rootkit / LKM
Fingerprints: init_module / finit_module imports, kallsyms strings, sys_call_table, /proc/kallsyms, vermagic string.
Key analysis steps:
- Find
module_initfunction — entry point for kernel module, analogous tomain() - Look for syscall hooking pattern:
kallsyms_lookup_name("sys_call_table")→ save original → replace pointer - If kernel ≥5.7 (kallsyms not exported): look for kprobe-based lookup or ftrace hooks
- Identify which syscalls are hooked:
getdents64(process/file hiding),open/read(file content manipulation),tcp4_seq_show(socket hiding) - eBPF rootkit:
bpf()syscall +BPF_PROG_LOAD+ uprobe/kprobe attach = kernel-level hook without .ko file
PUMAKIT pattern (2024): Two-stage — ELF dropper + LKM; dropper hides in legitimate-looking binary using .init_array to load the module before main() runs.
Fileless / Memory-Only Malware
Fingerprints: memfd_create import, /proc/self/exe + unlink() combo, /proc/self/mem write pattern.
Key analysis steps:
- Trace
memfd_create→write(fd, payload, size)→fexecve(fd, ...): This is the fileless execution chain. The payload written to the memfd IS the second-stage binary. - Look for payload extraction: XOR/AES decrypt → write to memfd → execute
- If self-deletion:
readlink("/proc/self/exe")→unlink(path)— binary removes itself after launch /proc/self/memwrite: Open own mem, seek to target address, write new code — runtime patching without file modification
Detection note for report: Process will show /proc/<pid>/exe as (deleted) or memfd:<name> (deleted).
Backdoor / RAT
Fingerprints: socket + connect/bind + execve("/bin/sh") cluster, reverse shell pattern.
Key analysis steps:
- Find the command dispatch function: usually a
switchorif/else ifchain on a received byte/string - Map commands to handlers: file read/write, shell exec, screenshot (X11), keylog, persistence install
- Identify C2 protocol: plaintext? XOR-encrypted? TLS? IRC (look for
PRIVMSG,JOIN #strings) - Check for SSH backdoor: PAM module (
/etc/pam.d/sshdreference) orauthorized_keyswrite
Credential Stealer / Info Stealer
Fingerprints: /etc/shadow access, ~/.ssh/ path strings, browser database paths, cloud credential paths.
Key analysis steps:
- Trace each credential path to the exfiltration function — what encoding is used before send?
- Look for HTTP POST to C2 with stolen data body
- Check for
/proc/[pid]/memread targetingsshdor browser processes
Phase 4: Deep Dive — Kill Chain
Anti-Analysis (check before spending time on obfuscated code)
Anti-debug techniques — search for these call patterns:
ptrace(PTRACE_TRACEME, 0, ...) → if returns -1, exit/behave differently
open("/proc/self/status") → read TracerPid: field; non-zero = debugger
clock_gettime(CLOCK_MONOTONIC) → double-call, delta check; >threshold = debugger
raise(SIGILL) / raise(SIGTRAP) → if no custom handler → crash; if handled = clean
VM/sandbox detection:
open("/sys/class/dmi/id/product_name") → "VirtualBox", "VMware Virtual Platform", "QEMU"
open("/sys/class/dmi/id/sys_vendor") → "innotek GmbH", "VMware, Inc.", "QEMU"
open("/proc/cpuinfo") → check hypervisor flags
readlink("/proc/self/cgroup") → detect container cgroups
Process name spoofing: prctl(PR_SET_NAME, "kworker/0:1", ...) — makes malware look like a kernel thread.
Persistence
Identify ALL persistence vectors — malware often installs multiple. Check string results from Phase 2 against this hierarchy (ordered by stealth):
- LD_PRELOAD / /etc/ld.so.preload — most stealthy; affects all processes
- PAM module — credential capture + persistence
- Kernel module / eBPF — survives user-space analysis
- Systemd service — survives reboot, shown by
systemctl list-units - Crontab — common in cryptominers;
crontab -lreveals - SSH authorized_keys — opportunistic, targeted
- .bashrc / .profile — user-scoped, less stealthy
- Udev rule — triggered by device events
For each: identify the exact path being written and what binary/command is being persisted.
Phase 5: Language-Specific Analysis
Go Binaries
Identification: runtime., go.itab, sync.Mutex, runtime.morestack strings. Function count is high (Go runtime inflates it). go.buildinfo section contains Go version.
Critical difference: Go strings are NOT null-terminated — they are {ptr, len} pairs. IDA's built-in string detection misses them. Use search_strings and cross-reference from .rodata to usage sites.
Function recovery in stripped binaries:
pclntab(Program Counter Line Table) is always present, even stripped. It maps every PC range to a function name string. Search for magic bytes\xFF\xFF\xFF\xFA(Go 1.20+) or\xFB\xFF\xFF\xFF(Go 1.2-1.19) to locate it.- goroutine stack check preamble: Every function starts with
CMPQ SP, goroutine_stackguard; JBE morestack— use this as a reliable function start signature.
Common Go malware families: Chaos/Sparc (DDoS+SSH+module loader), Sliver C2, Gopuram backdoor.
Naming convention for Go functions:
main.funcName— package mainnet.(*Dialer).DialContext— standard library method- Rename to:
go_main_ConnectC2,go_net_ResolveDomain, etc.
Rust Binaries
Identification: _ZN mangled names, core::panicking::panic, std::sys_common, Tokio / tokio_ strings. No true RTTI.
Key technique: Panic strings leak source code paths and line numbers even in release builds. search_strings "src/" often returns paths from the original Rust source tree, revealing project structure and function names.
Async (Tokio) pattern: State machine in .await points — a single async fn compiles into a state enum. Recognize by the poll function pattern and Pin<Box<dyn Future>>.
Vtables: Look for data arrays with consecutive function pointers — these are trait vtables. Retype in IDA with correct function signatures to enable cross-references.
Phase 6: Architecture-Specific Notes
x86_64 (Desktop/Server/Container)
- Container escape:
/var/run/docker.sockaccess,cgroup release_agentwrite (CVE-2022-0492) - Cryptominer: SIMD instructions (AVX2/AVX512), high entropy memory loops = RandomX
- Direct syscalls:
syscallinstruction withrax= syscall number (bypasses libc hooking)
AArch64 / ARM
- IoT/embedded: Hardcoded default credentials for common devices in string table
- Android-adjacent:
BionicC library patterns instead of glibc - ARM debug registers: Read
MRS X0, MDSCR_EL1— if debug enable bit set = debugger attached
MIPS (Mirai-family routers/DVRs)
- Big-endian vs little-endian: Check ELF header
EI_DATAbyte (0x01=LE, 0x02=BE) - MIPS delay slots: instruction after branch/jump always executes — IDA handles this but can confuse manual reading
- MIPS syscall:
syscallinstruction with$v0= syscall number
Phase 7: Report
Triage summary first (fits in ~15 lines):
Binary: sample | ELF x86_64 | dynamically linked | 847 funcs
Toolchain: GCC / glibc 2.31
Packing: None (entropy 5.8, all sections)
Architecture: x86_64 — standard server/container target
Classification: Linux backdoor + cryptominer (dual purpose)
Strings: /etc/cron.d/, /proc/self/status, stratum+tcp://, memfd_create
Imports: socket, connect, execve, memfd_create, prctl, sched_setaffinity
Persistence: cron job (/etc/cron.d/update), SSH authorized_keys
C2: hxxps://185.220.101[.]47/beacon (HTTPS), stratum+tcp://pool.minexmr[.]com:4444
Evasion: ptrace PTRACE_TRACEME, /proc/self/status TracerPid, process name spoofing
Lateral: SSH key harvesting from /home/*/.ssh/
Fileless: memfd_create + fexecve for second-stage drop
Verdict: Dual-purpose Linux implant: reverse shell + XMRig miner drop.