with one click
linux-malware-analysis
// Expert ELF malware analysis — packing, toolchain ID, kill chain, persistence, C2, rootkits, cryptominers, Go/Rust/Mirai patterns, MITRE ATT&CK mapping
// Expert ELF malware analysis — packing, toolchain ID, kill chain, persistence, C2, rootkits, cryptominers, Go/Rust/Mirai patterns, MITRE ATT&CK mapping
Systematic binary deobfuscation — string decryption, control flow flattening (CFF) removal, opaque predicate elimination, mixed boolean-arithmetic (MBA) simplification, bogus control flow, instruction substitution reversal, dead code removal, and anti-disassembly fixes. Trigger: deobfuscate, unobfuscate, deobfuscation, CFF, flatten, opaque predicate, MBA, obfuscated, OLLVM, Tigress, VMProtect, string decryption, junk code, bogus control flow, instruction substitution, anti-disassembly
Write and execute Binary Ninja Python scripts — full API reference included
Write and execute IDAPython scripts — full API reference included
Modify binary behavior using natural language — explore, plan, patch, save
Patch binary code in Binary Ninja using natural language — read, assemble, write, verify
Patch binary code in IDA Pro using natural language — read, assemble, write, verify
| 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.
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 segmentDetermine compiler toolchain:
search_strings for: runtime., go.itab, panicked at → Go binarysearch_strings for: core::panicking, _ZN (Rust name mangling), .L__unnamed → Rust binarypthread_create + libc.so → standard C/C++search_strings for UPX magic, check function count vs binary sizePacking 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)This phase reveals backdoors in the loader mechanism that pure string/import analysis misses.
Check the interpreter path via list_segments. Standard values:
/lib64/ld-linux-x86-64.so.2/lib/ld-linux-aarch64.so.1/lib/ld-uClibc.so.0 (or musl on newer IoT)Non-standard interpreter = binary uses a rogue dynamic linker. High confidence malware.
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.
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:
.plt section startFF 25 XX XX XX XX = jmp [rip+offset]stub_addr + 6 + offsetSymbol 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.
.ctors / .dtors (legacy): same purpose as init/fini_array, older GCCpreinit_array: Runs before init_array, even before _start in some configurationsDT_INIT entry in dynamic section: Single init function pointer, separate from arrayBatch these calls — they can run in parallel:
list_imports — group by category:
| Import cluster | Capability |
|---|---|
socket, connect, bind, listen, accept, send, recv | Network C2 |
execve, execvp, fork, clone, posix_spawn | Process execution |
ptrace | Anti-debug OR debugger (rare in malware) |
init_module, finit_module | Kernel module loading = rootkit |
memfd_create | Fileless execution |
mmap + mprotect (both present) | Shellcode loading / runtime decryption |
dlopen, dlsym | Dynamic library loading / hooking |
inotify_* | File system monitoring |
prctl | Process name spoofing (PR_SET_NAME) or capabilities |
capset, setuid, setreuid, setgid | Privilege manipulation |
bpf | eBPF rootkit or monitoring |
list_exports — exported symbols reveal shared library role or hooking targets
search_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_disabled
Evasion 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 Cookies
Fileless execution signals:
memfd: /proc/self/mem /dev/shm /tmp/
Rootkit signals:
kallsyms /proc/kallsyms sys_call_table
ftrace kprobe uprobe /dev/kmem
Based on Phase 2 findings, determine the primary classification before deep-diving:
Fingerprints: MIPS/ARM architecture, UPX-packed, table.c encrypted string pattern, process killer, scanner loop.
Key analysis steps:
0xDEADBEEF; variants use XOR with other keys, XXTEA, RC4, or TEA. Look for a loop with XOR and a fixed constant in .rodata.connect() call chain from attacker initfork() + daemon() → background persistence without file2024+ variants also add: SSH brute force (NoaBot), crypto mining (stratum beacon), RC4 string table, anti-VM checks.
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 "stratum" + search_strings "pool"sched_setaffinity → check which cores are claimednice(-20) — stealing maximum CPU priority.rodata; look for large blob + decode routinecudaLaunchKernel / clEnqueueNDRangeKernel or libcuda.so / libOpenCL.so dlopenFingerprints: init_module / finit_module imports, kallsyms strings, sys_call_table, /proc/kallsyms, vermagic string.
Key analysis steps:
module_init function — entry point for kernel module, analogous to main()kallsyms_lookup_name("sys_call_table") → save original → replace pointergetdents64 (process/file hiding), open/read (file content manipulation), tcp4_seq_show (socket hiding)bpf() syscall + BPF_PROG_LOAD + uprobe/kprobe attach = kernel-level hook without .ko filePUMAKIT pattern (2024): Two-stage — ELF dropper + LKM; dropper hides in legitimate-looking binary using .init_array to load the module before main() runs.
Fingerprints: memfd_create import, /proc/self/exe + unlink() combo, /proc/self/mem write pattern.
Key analysis steps:
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.readlink("/proc/self/exe") → unlink(path) — binary removes itself after launch/proc/self/mem write: Open own mem, seek to target address, write new code — runtime patching without file modificationDetection note for report: Process will show /proc/<pid>/exe as (deleted) or memfd:<name> (deleted).
Fingerprints: socket + connect/bind + execve("/bin/sh") cluster, reverse shell pattern.
Key analysis steps:
switch or if/else if chain on a received byte/stringPRIVMSG, JOIN # strings)/etc/pam.d/sshd reference) or authorized_keys writeFingerprints: /etc/shadow access, ~/.ssh/ path strings, browser database paths, cloud credential paths.
Key analysis steps:
/proc/[pid]/mem read targeting sshd or browser processesAnti-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.
Identify ALL persistence vectors — malware often installs multiple. Check string results from Phase 2 against this hierarchy (ordered by stealth):
systemctl list-unitscrontab -l revealsFor each: identify the exact path being written and what binary/command is being persisted.
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.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 methodgo_main_ConnectC2, go_net_ResolveDomain, etc.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.
/var/run/docker.sock access, cgroup release_agent write (CVE-2022-0492)syscall instruction with rax = syscall number (bypasses libc hooking)Bionic C library patterns instead of glibcMRS X0, MDSCR_EL1 — if debug enable bit set = debugger attachedEI_DATA byte (0x01=LE, 0x02=BE)syscall instruction with $v0 = syscall numberTriage 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.