Skip to content

Commit

Permalink
Add plugins for volatile Linux artefacts (fox-it#241)
Browse files Browse the repository at this point in the history
Co-authored-by: pyrco <105293448+pyrco@users.noreply.github.com>
Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com>
  • Loading branch information
3 people authored and Zawadidone committed Apr 5, 2024
1 parent 1984d3c commit d471551
Show file tree
Hide file tree
Showing 22 changed files with 1,469 additions and 9 deletions.
8 changes: 7 additions & 1 deletion dissect/target/plugins/os/unix/linux/_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@ def __init__(self, target: Target):
@classmethod
def detect(cls, target: Target) -> Optional[Filesystem]:
for fs in target.filesystems:
if fs.exists("/var") and fs.exists("/etc") and fs.exists("/opt") and not fs.exists("/Library"):
if (
fs.exists("/var")
and fs.exists("/etc")
and fs.exists("/opt")
or (fs.exists("/sys") or fs.exists("/proc"))
and not fs.exists("/Library")
):
return fs
return None

Expand Down
48 changes: 48 additions & 0 deletions dissect/target/plugins/os/unix/linux/cmdline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from typing import Iterator

from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export

CmdlineRecord = TargetRecordDescriptor(
"linux/proc/cmdline",
[
("datetime", "ts"),
("string", "name"),
("varint", "pid"),
("string", "state"),
("string", "cmdline"),
],
)


class CmdlinePlugin(Plugin):
def check_compatible(self) -> None:
self.target.proc

@export(record=CmdlineRecord)
def cmdline(self) -> Iterator[CmdlineRecord]:
"""Return the complete command line for all processes.
If, after an execve(2), the process modifies its argv strings, those changes will show up here. This is not the
same thing as modifying the argv array.
Think of this output as the command line that the process wants you to see.
Yields CmdlineRecord with the following fields:
hostname (string): The target hostname.
domain (string): The target domain.
ts (datetime): The starttime of the process.
name (string): The name of the process.
pid (int): The process ID of the process.
cmdline (string): The complete commandline of the process.
"""

for process in self.target.proc.processes():
yield CmdlineRecord(
ts=process.starttime,
name=process.name,
pid=process.pid,
state=process.state,
cmdline=process.cmdline,
_target=self.target,
)
47 changes: 47 additions & 0 deletions dissect/target/plugins/os/unix/linux/environ.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import Iterator

from dissect.target.helpers.record import TargetRecordDescriptor
from dissect.target.plugin import Plugin, export

EnvironmentVariableRecord = TargetRecordDescriptor(
"linux/proc/environ",
[
("datetime", "ts"),
("string", "name"),
("varint", "pid"),
("string", "variable"),
("string", "content"),
],
)


class EnvironPlugin(Plugin):
def check_compatible(self) -> None:
self.target.proc

@export(record=EnvironmentVariableRecord)
def environ(self) -> Iterator[EnvironmentVariableRecord]:
"""Return the initial environment for all processes when they were started via execve(2).
If the process modified its environment (e.g., by calling functions such as putenv(3) or modifying
the environ(7) variable directly), this plugin will not reflect those changes.
Yields EnvironmentVariableRecord with the following fields:
hostname (string): The target hostname.
domain (string): The target domain.
ts (datetime): The modification timestamp of the processes' environ file.
name (string): The name associated to the pid.
pid (varint): The process id (pid) of the process.
variable (string): The name of the environment variable.
content (string): The contents of the environment variable.
"""
for process in self.target.proc.processes():
for environ in process.environ():
yield EnvironmentVariableRecord(
ts=process.get("environ").stat().st_mtime,
name=process.name,
pid=process.pid,
variable=environ.variable,
content=environ.contents,
_target=self.target,
)
41 changes: 41 additions & 0 deletions dissect/target/plugins/os/unix/linux/netstat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from itertools import chain
from typing import Iterator

from dissect.target.plugin import Plugin, export

NETSTAT_HEADER = f"Active Internet connections (only servers)\n{'Proto':<10}{'Recv-Q':^10}{'Send-Q':^10}{'Local Address':^20}{'Foreign Address':^20}{'State':^10}{'User':^15}{'Inode':^10}{'PID/Program name':^10}{'Command':>10}" # noqa
NETSTAT_TEMPLATE = "{protocol:<12}{receive_queue:<10}{transmit_queue:<11}{local_addr:<19}{remote_addr:<20}{state:<13}{owner:<12}{inode:<8}{pid_program:<19}{cmdline}" # noqa


class NetstatPlugin(Plugin):
def check_compatible(self) -> None:
self.target.proc

@export(output="yield")
def netstat(self) -> Iterator[str]:
"""This plugin mimics the output `netstat -tunelwap` would generate on a Linux machine."""
sockets = chain(
self.target.sockets.tcp(),
self.target.sockets.udp(),
self.target.sockets.raw(),
)

yield NETSTAT_HEADER

for record in sockets:
local_addr = f"{record.local_ip}:{record.local_port}"
remote_addr = f"{record.remote_ip}:{record.remote_port}"
pid_program = f"{record.pid}/{record.name}"

yield NETSTAT_TEMPLATE.format(
protocol=record.protocol,
receive_queue=record.rx_queue,
transmit_queue=record.tx_queue,
local_addr=local_addr,
remote_addr=remote_addr,
state=record.state,
owner=record.owner,
inode=record.inode,
pid_program=pid_program,
cmdline=record.cmdline,
)
Loading

0 comments on commit d471551

Please sign in to comment.