Files
volatility3/test/plugins/linux/linux.py
T

655 lines
21 KiB
Python

import contextlib
import tempfile
import os
import re
from test import test_volatility, LinuxSamples
class TestLinuxVolshell:
def test_linux_volshell(self, image, volatility, python):
out = test_volatility.basic_volshell_test(
image, volatility, python, volshellargs=("-l",)
)
assert out.count(b"<task_struct") > 100
class TestLinuxPslist:
def test_linux_generic_pslist(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pslist.PsList", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.find(b"watchdog") != -1
assert out.count(b"\n") > 10
class TestLinuxCheckIdt:
def test_linux_generic_check_idt(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.check_idt.Check_idt", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"__kernel__") >= 10
assert out.count(b"\n") > 10
class TestLinuxCheckSyscall:
def test_linux_generic_check_syscall(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.check_syscall.Check_syscall", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"sys_close") != -1
assert out.find(b"sys_open") != -1
assert out.count(b"\n") > 100
class TestLinuxLsmod:
def test_linux_generic_lsmod(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.lsmod.Lsmod", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"\n") > 10
class TestLinuxLsof:
def test_linux_generic_lsof(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.lsof.Lsof", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"socket:") >= 10
assert out.count(b"\n") > 35
class TestLinuxProcMaps:
def test_linux_generic_proc_maps(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.proc.Maps", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"anonymous mapping") >= 10
assert out.count(b"\n") > 100
class TestLinuxTtyCheck:
def test_linux_generic_tty_check(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.tty_check.Tty_Check", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.find(b"__kernel__") != -1
assert out.count(b"\n") >= 5
class TestLinuxSockstat:
def test_linux_generic_sockstat(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.sockstat.Sockstat", image, volatility, python
)
assert rc == 0
assert out.count(b"AF_UNIX") >= 354
assert out.count(b"AF_BLUETOOTH") >= 5
assert out.count(b"AF_INET") >= 32
assert out.count(b"AF_INET6") >= 20
assert out.count(b"AF_PACKET") >= 1
assert out.count(b"AF_NETLINK") >= 43
class TestLinuxLibraryList:
def test_linux_specific_library_list(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.library_list.LibraryList",
image,
volatility,
python,
pluginargs=("--pids", "2363"),
)
assert rc == 0
assert re.search(
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
out,
)
assert out.count(b"\n") > 10
class TestLinuxPstree:
def test_linux_generic_pstree(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pstree.PsTree", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.count(b"\n") > 10
class TestLinuxPidhashtable:
def test_linux_generic_pidhashtable(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pidhashtable.PIDHashTable", image, volatility, python
)
assert rc == 0
out = out.lower()
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
assert out.count(b"\n") > 10
class TestLinuxBash:
def test_linux_bash(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.bash.Bash", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxBoottime:
def test_linux_generic_boottime(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.boottime.Boottime", image, volatility, python
)
assert rc == 0
out = out.lower()
assert out.count(b"utc") >= 1
class TestLinuxCapabilities:
def test_linux_generic_capabilities(self, image, volatility, python):
rc, out, err = test_volatility.runvol_plugin(
"linux.capabilities.Capabilities",
image,
volatility,
python,
globalargs=("-vvv",),
)
if rc != 0 and err.count(b"Unsupported kernel capabilities implementation") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxCheckCreds:
def test_linux_generic_check_creds(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.check_creds.Check_creds", image, volatility, python
)
# linux-sample-1.bin has no processes sharing credentials.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxElfs:
def test_linux_generic_elfs(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.elfs.Elfs", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxEnvars:
def test_linux_generic_envars(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.envars.Envars", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxKthreads:
def test_linux_generic_kthreads(self, image, volatility, python):
rc, out, err = test_volatility.runvol_plugin(
"linux.kthreads.Kthreads",
image,
volatility,
python,
globalargs=("-vvv",),
)
if rc != 0 and err.count(b"Unsupported kthread implementation") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxMalfind:
def test_linux_generic_malfind(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.malfind.Malfind", image, volatility, python
)
# linux-sample-1.bin has no process memory ranges with potential injected code.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxMountinfo:
def test_linux_generic_mountinfo(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.mountinfo.MountInfo", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxPsaux:
def test_linux_generic_psaux(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.psaux.PsAux", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 50
class TestLinuxPtrace:
def test_linux_generic_ptrace(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.ptrace.Ptrace", image, volatility, python
)
# linux-sample-1.bin has no processes being ptraced.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxVmaregexscan:
def test_linux_generic_vmaregexscan(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.vmaregexscan.VmaRegExScan",
image,
volatility,
python,
pluginargs=("--pid", "1", "--pattern", "\\x7fELF"),
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxVmayarascanYaraRule:
def test_linux_specific_vmayarascan_yara_rule(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
yara_rule_01 = r"""
rule fullvmayarascan
{
strings:
$s1 = "_nss_files_parse_grent"
$s2 = "/lib64/ld-linux-x86-64.so.2"
$s3 = "(bufferend - (char *) 0) % sizeof (char *) == 0"
condition:
all of them
}
"""
# FIXME: When the minimum Python version includes 3.12, replace the following with:
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
fd, filename = tempfile.mkstemp(suffix=".yar")
try:
with os.fdopen(fd, "w") as f:
f.write(yara_rule_01)
rc, out, _err = test_volatility.runvol_plugin(
"linux.vmayarascan.VmaYaraScan",
image,
volatility,
python,
pluginargs=("--pid", "8600", "--yara-file", filename),
)
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(filename)
assert rc == 0
assert out.count(b"\n") > 4
class TestLinuxVmayarascanYaraString:
def test_linux_generic_vmayarascan_yara_string(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.vmayarascan.VmaYaraScan",
image,
volatility,
python,
pluginargs=("--pid", "1", "--yara-string", "ELF"),
)
assert rc == 0
assert out.count(b"\n") > 10
class TestLinuxPageCacheFiles:
def test_linux_specific_page_cache_files(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.pagecache.Files",
image,
volatility,
python,
pluginargs=("--find", "/etc/passwd"),
)
assert rc == 0
assert out.count(b"\n") > 4
# inode_num inode_addr ... file_path
assert re.search(
rb"146829\s0x88001ab5c270.*?/etc/passwd",
out,
)
class TestLinuxPageCacheInodepages:
def test_linux_specific_page_cache_inodepages(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
inode_address = hex(0x88001AB5C270)
inode_dump_filename = f"inode_{inode_address}.dmp"
rc, out, _err = test_volatility.runvol_plugin(
"linux.pagecache.InodePages",
image,
volatility,
python,
pluginargs=("--inode", inode_address),
)
assert rc == 0
assert out.count(b"\n") > 4
# PageVAddr PagePAddr MappingAddr .. DumpSafe
assert re.search(
rb"0xea000054c5f8\s0x18389000\s0x88001ab5c3b0.*?True",
out,
)
try:
rc, out, _err = test_volatility.runvol_plugin(
"linux.pagecache.InodePages",
image,
volatility,
python,
pluginargs=("--inode", inode_address, "--dump"),
)
assert rc == 0
assert out.count(b"\n") >= 4
assert os.path.exists(inode_dump_filename)
with open(inode_dump_filename, "rb") as fp:
inode_contents = fp.read()
assert inode_contents.count(b"\n") > 30
assert inode_contents.count(b"root:x:0:0:root:/root:/bin/bash") > 0
finally:
with contextlib.suppress(FileNotFoundError):
os.remove(inode_dump_filename)
class TestLinuxCheckAfinfo:
def test_linux_generic_check_afinfo(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.check_afinfo.Check_afinfo", image, volatility, python
)
# linux-sample-1.bin has no suspicious results.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxCheckModules:
def test_linux_generic_check_modules(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.check_modules.Check_modules", image, volatility, python
)
# linux-sample-1.bin has no suspicious results.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxEbpf:
def test_linux_generic_ebpf_progs(self, image, volatility, python):
rc, out, err = test_volatility.runvol_plugin(
"linux.ebpf.EBPF",
image,
volatility,
python,
globalargs=("-vvv",),
)
if rc != 0 and err.count(b"Unsupported kernel") > 0:
# The linux-sample-1.bin kernel implementation isn't supported.
# However, we can still check that the plugin requirements are met.
return None
assert rc == 0
assert out.count(b"\n") > 4
class TestLinuxIomem:
def test_linux_generic_iomem(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.iomem.IOMem", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 100
class TestLinuxKeyboardNotifiers:
def test_linux_generic_keyboard_notifiers(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.keyboard_notifiers.Keyboard_notifiers",
image,
volatility,
python,
)
# linux-sample-1.bin has no suspicious results for this plugin.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxKmesg:
def test_linux_generic_kmesg(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.kmsg.Kmsg", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 100
class TestLinuxNetfilter:
def test_linux_generic_netfilter(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.netfilter.Netfilter", image, volatility, python
)
# linux-sample-1.bin has no suspicious results for this plugin.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxPsscan:
def test_linux_generic__psscan(self, image, volatility, python):
rc, out, _err = test_volatility.runvol_plugin(
"linux.psscan.PsScan", image, volatility, python
)
assert rc == 0
assert out.count(b"\n") > 100
class TestLinuxHiddenModules:
def test_linux_specific_hidden_modules(self, volatility, python):
# TODO: this check should be specific, against a distinct infected sample
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.malware.hidden_modules.Hidden_modules", image, volatility, python
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") >= 4
class TestLinuxIpAddr:
def test_linux_specific_ip_addr(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, err = test_volatility.runvol_plugin(
"linux.ip.Addr", image, volatility, python
)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+192.168.201.161\s+24\s+global\s+UP",
out,
)
assert re.search(
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+fe80::20c:29ff:fe8f:edca\s+64\s+link\s+UP",
out,
)
assert out.count(b"\n") >= 8
assert rc == 0
class TestLinuxIpLink:
def test_linux_specific_ip_link(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, err = test_volatility.runvol_plugin(
"linux.ip.Link", image, volatility, python
)
assert re.search(
rb"-\s+lo\s+00:00:00:00:00:00\s+UNKNOWN\s+16436\s+noqueue\s+0\s+LOOPBACK,LOWER_UP,UP",
out,
)
assert re.search(
rb"-\s+eth0\s+00:0c:29:8f:ed:ca\s+UP\s+1500\s+pfifo_fast\s+1000\s+BROADCAST,LOWER_UP,MULTICAST,UP",
out,
)
assert out.count(b"\n") >= 6
assert rc == 0
class TestLinuxKallsyms:
def test_linux_specific_kallsyms(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.kallsyms.Kallsyms",
image,
volatility,
python,
pluginargs=("--modules",),
)
# linux-sample-1.bin has no hidden modules.
# This validates that plugin requirements are met and exceptions are not raised.
assert rc == 0
assert out.count(b"\n") > 1000
# Addr Type Size Exported SubSystem ModuleName SymbolName Description
# 0xffffa009eba9 t 28 False module usbcore usb_mon_register Symbol is in the text (code) section
assert re.search(
rb"0xffffa009eba9\s+t\s+28\s+False\s+module\s+usbcore\s+usb_mon_register\s+Symbol is in the text \(code\) section",
out,
)
class TestLinuxPscallstack:
def test_linux_specific_pscallstack(self, volatility, python):
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"linux.pscallstack.PsCallStack",
image,
volatility,
python,
pluginargs=("--pid", "1"),
)
assert rc == 0
assert out.count(b"\n") > 30
# TID Comm Position Address Value Name Type Module
# 1 init 39 0x88001f999a40 0xffff81109039 do_select T kernel
assert re.search(
rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel",
out,
)
class TestLinuxSockscan:
def test_linux_sockscan(self, volatility, python):
# designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6
image = LinuxSamples.LINUX_GENERIC.value.path
rc, out, err = test_volatility.runvol_plugin(
"linux.sockscan.Sockscan", image, volatility, python
)
# ensure that multiple unix paths for sockets have been found
assert (
len(
re.findall(
rb"(/[ -~]+?){1,8}",
out,
)
)
>= 10
)
# ensure that multiple IPv4 addresses have been found
assert (
len(
re.findall(
rb"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}",
out,
)
)
>= 10
)
assert out.count(b"\n") >= 50
assert rc == 0