{"id":21594090,"url":"https://github.com/ornl/systemd_snapshot","last_synced_at":"2025-07-04T23:33:01.233Z","repository":{"id":236291994,"uuid":"792316436","full_name":"ORNL/Systemd_Snapshot","owner":"ORNL","description":"A tool for helping users during forensic analysis of a systemd system","archived":false,"fork":false,"pushed_at":"2025-03-04T19:55:12.000Z","size":599,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-04T20:34:00.005Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ORNL.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-04-26T12:21:22.000Z","updated_at":"2025-03-04T19:55:15.000Z","dependencies_parsed_at":"2024-11-08T15:41:18.995Z","dependency_job_id":null,"html_url":"https://github.com/ORNL/Systemd_Snapshot","commit_stats":null,"previous_names":["ornl/systemd_snapshot"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ORNL%2FSystemd_Snapshot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ORNL%2FSystemd_Snapshot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ORNL%2FSystemd_Snapshot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ORNL%2FSystemd_Snapshot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ORNL","download_url":"https://codeload.github.com/ORNL/Systemd_Snapshot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244206840,"owners_count":20416090,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-24T17:15:59.432Z","updated_at":"2025-07-04T23:33:01.223Z","avatar_url":"https://github.com/ORNL.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Systemd Snapshot\n\n# About\n\nThis tool creates a systemd snapshot of either locally or remotely hosted file\nsystems, and records collected data for forensic analysis.  It does this by\nparsing all of the unit files contained within the default systemd unit paths\n(system paths only) and records all unit file data, implicit dependencies, and\nexplicit dependencies. Systemd Snapshot will then record all of this info in a\nmaster struct (ms) file, and create both an interactive graph file for use with\ncytoscape and a json-formatted dependency map.\n\nThe dependency map begins with one unit file (default.target by default) and\nmaps out all dependencies that are created by that unit.  After dependencies\nare recorded, this process repeats for each dependency that is created, until\nno more dependencies remain. Both forward and backward dependency relationships\nare maintained within the dependency map, but not all unit files in the master\nstructure will be recorded here since startup processes vary based on targets\n(aka runlevels). Systemd Snapshot also allows users to start at an arbitrary\npoint within the startup process and view dependencies, conditions, etc. that\nare required after a specific point during startup, or allow users to see what\nwill start up when a different runlevel is specified. This is extremely helpful\nfor debugging systems that fail to boot, in which case your typical `systemctl`\ncommands are not available.\n\nOptionally, you can create networkx graphs from the information in the master\nstructure that is built from the Systemd filesystem and files. These graphs are\nDIRECTED MULTIGRAPHS: each edge has a source and a target AND there can be\nmultiple edges between two vertices. The multiple edges are distinct and can be\ndistinguished by their labels.\n\nVertices in the graph represent the following information: UNIT files, ALIAS\nsymbolic links, COMMANDs executed via Systemd, EXECUTABLES within COMMANDS,\nLIBRARYs used by EXECUTABLES, and useful STRINGS within EXECUTABLES. The edges\nin the graph established NAMED relationships between these Systemd elements.\n\nA Tree can be created from the complete graph by designating a root node from\nany UNIT FILE within the systemd structure. This is actually VERY handy for\nfocusing your analysis efforts.\n\nGraphs can also be trasferred to Cytoscape for visualization via Cytoscape's\nREST api and the py4cytoscape library. There is a capability to build a\ncustom style for your graph -- that will require some additional documentation.\n\n# Installation\n---\ngit clone https://github.com/ornl/systemd_snapshot.git\n\ncd systemd_snapshot\n\npython systemd_snapshot.py -h\n\n## Optional Dependencies (for graphing)\n---\n\n- py4cytoscape\n- Cytoscape\n\nInstall optional graphing dependencies with `pip install py4cytoscape`. There\nare 3 python module dependencies for systemd_snapshot.py's graphing\nfunctionality to work. These are networkx, py4cytoscape and pandas, and are\nonly required if you are going to use the graphing functionality (which I\nhighly recommend!). The py4cytoscape library depends on networkx and pandas so\nyou only have to worry about installing py4cytoscape.\n\nTo render the graphs we use the open-source tool called Cytoscape, which also\nneeds to be installed via [Cytoscape's website](https://cytoscape.org/). I would\nalso highly recommend the yfiles addon for Cytoscape, which organizes all of\nyour nodes in super clean layouts. Systemd graphs are often quite massive and\nthese layouts make it far easier to visualize the information. You can get the\nyfiles addons from the \"cytoscape app store\" (for free).\n\n# Use Cases\n\n- Forensic investigations for linux systems\n    - Take a snapshot of all the startup services that are started on the\n        default target.\n    - View startup services that WOULD be started if the system were to boot\n        into another runlevel without having to modify the system.\n    - Compare current startup services with a baseline snapshot. You can use a\n        golden image to create the baseline if you are interested in a system\n        currently in production.\n- System Hardening\n    - Investigate and clean up unused/unwanted services with the dependency map\n    - See exactly what commands your startup services are all running\n    - See which config files are actually being referenced by startup services\n- SBOMs \n    - This capability identifies both components used and not used by a system,\n        as well as their dependencies.  This is additional information that is\n        not captured in current SBOMs easily.\n- Visualize all of the above with the graphing capability and cytoscape!\n- System Evolution tracking\n    - How does a new firmware version different from a previous version? A\n        top-level view may be discernable by taking a diff of the systemd\n        architecture.\n- Investigate configuration weaknesses or vulnerable startup service commands\n    with the master struct\n- Rehosting\n    - Aid identifying what could be \"cut out\" of a system to focus emulating\n        targeted system behavior.  Decrease overall complexity of rehosting.\n    - When a service doesn't start, this tool may help identifyvdependencies\n        whose failure to cause the failure of the target service.\n\n# Tutorial / Example\n\n## Getting Help\n\nView Systemd Snapshot's help menu with the `-h`, or `--help` option:\n\n`python3 systemd_snapshot.py -h`\n\n```\npython3 systemd_snapshot -h\nusage: systemd_snapshot [-h] [-a ACTION] [-o OUTPUT_FILE] [-f] [-p USER_PATH] [-t TARGET_UNIT] [-c COMP_FILE] [-l LOG_LEVEL] [-D DEPTH] [-S STYLE_FILE]\n\nThis tool creates a systemd snapshot of either locally or remotely hosted file systems, and records collected data for forensic analysis.\n\noptions:\n  -h, --help            show this help message and exit\n  -a, --action ACTION   Action to take. One of {'master', 'deps', 'graph', 'diff', 'all'}. If action is 'master' or 'all', path option will be used as a pointer to a remotely hosted fs.\n                        Otherwise, the path option will be used as a pointer to a master struct snapshot file.\n  -o, --output-file OUTPUT_FILE\n                        File path to save master struct as. All artifacts will be created in the same directory. (Default: working-directory/snapshot)\n  -f, --force-overwrite\n                        Allow systemd snapshot to overwrite files if they already exist.\n  -p, --path USER_PATH  Use -p followed by a path to set the root path to a remotely hosted filesystem if used with master or all actions, or the path to a ms.json file if used with other\n                        actions. Defaults to the locally hosted filesystem.\n  -t, --target-unit TARGET_UNIT\n                        Use -t followed by a unit name to start the dependency map from the specified unit file instead of default.target\n  -c, --comparison-file COMP_FILE\n                        Required if the action to take is \"diff\". Use -c followed by a filename in order to set a comparison file.\n  -l, --log-level LOG_LEVEL\n                        Change logging level if desired. Options from most to least verbose: [ vDEBUG, DEBUG, INFO, WARNING, ERROR, CRITICAL ]\n  -D, --depth DEPTH     the depth of the tree to produce.\n  -S, --style-file STYLE_FILE\n                        The style to use for the graph; if a path to a file it will be used as a json style specification; otherwise, it is assumed to be a current cytoscape style.\n\nExample usage:\n\nsystemd_snapshot.py -a master                                   - create a systemd snapshot of the local system, saved to working-dir/snapshot_ms.json\nsystemd_snapshot.py -a deps                                     - a master struct is required for dep map and graph.  Since no path is specified,\n                                                                  snapshot_ms.json will be created first, and then a dep map will be made from it\nsystemd_snapshot.py -a deps -p ss_ms.json -t runlevel2.target   - use ss_ms.json file to create a dep map originating from runlevel2.target\nsystemd_snapshot.py -a master -o new/snapshot                   - save snapshot to new/snapshot_ms.json\nsystemd_snapshot.py -a deps -p /path/to/snapshot_ms.json        - use specified master struct snapshot to build only the dep map\nsystemd_snapshot.py -a graph -p /path/to/snapshot_ms.json       - use specified master struct snapshot to build only the graph\nsystemd_snapshot.py -a all -p /remotely/hosted/fs/root          - create ms snapshot from remote fs and build dep map and graph from it\nsystemd_snapshot.py -a all -p /remote/fs -o new/snaps           - create ms snapshot from remote fs, build dep map and graph from master struct,\n                                                                  and save all files as new/snaps_*\nsystemd_snapshot.py -a diff -p snap1_ms.json -c snap2_ms.json   - compare the differences between snap1 and snap2. Both files need to be the same type.\n                                                                  If you try to compare a master struct file to a dependency map file you will get an error.\n```\n\nAs you can see, there are a couple of different options available, so let's\ncheck them out.\n\n## Creating a Snapshot File\n\nThe first thing to note is that everything is built off of the snapshot file,\na.k.a. the master structure, so let's build that before we do anything else.\n\n`python3 systemd_snapshot.py -a master`\n\nThis will create a master structure file, which is a big JSON file that lists\nand records descriptions of all Systemd artifacts that were encountered. With\njust the `-a`, or `--action` option being used above, Systemd Snapshot will be\nlooking at the Systemd files on the local filesystem.\n\nIf we want to record the Systemd artifacts from a filesystem other than the\none being used by the host system (a remote filesystem), we can use the `-p`\nor `--path` option as shown below:\n\n`python3 systemd_snapshot.py -a master -p /path/to/remote/filesystem`\n\nThis will parse a remote filesystem (one that the local system isn't using)\nand, by default, will save the resultant file in data/snapshot_ms.json. There\nare a few important things to note here:\n1. You MUST set the path option to the ROOT directory of the remote filesystem,\notherwise Systemd Snapshot will not be able to find any of the Systemd paths it\nneeds.\n2. If the dependency map or graphing options are used instead of the master\nstruct option, the `-p` option needs to point to the master struct file instead\nof the remote filesystem. (shown later)\n3. Systemd Snapshot will not overwrite files by default, so if this fails, make\nsure that you have renamed the output file or that the file you are trying to\ncreate does not already exist.\n\nIf you would like to name the file something more descriptive or applicable to\nthe fs you are taking a snapshot of, you can use the `-o`, or the\n`--output-file`, option:\n\n`python3 systemd_snapshot.py -a master -p remote/fs -o data/my_snap`\n\nThis command will create a master struct file from the remote fs, and record\nthe results in the file \"data/my_snap_ms.json\". The \"_ms\" appended to the end\nof the file name is done automatically in order to differentiate between the\ndifferent types of output files, which is especially important when creating\nmultiple types of files at once or referencing the master stuct file to build\nthe other file types.\n\nOne final note from this portion of the tutorial is that now that you have the\nsnapshot of the filesystem in question, everything else is built off of it.\nThis means that you no longer have to interact with the filesystem to use the\nrest of the functionality of Systemd Snapshot. This can be helpful if you are\nautomating the process of taking snapshots across an enterprise in order to\ncompare them all to a golden image for threat hunting, or if you are taking a\nsnapshot of an unpacked firmware image to document it with an SBOM.\n\nIn any case, once you have a copy of the master struct file, you no longer need\nto interact with the fs it was built from.\n\n## Creating a Dependency Map\n\nNow that we have a master structure file that tells us every unit file that is\non the remote filesystem, along with its contents, we can create a dependency\nmap. The dependency map shows us the relationships between all of the unit\nfiles during the startup process, and shows what types of dependencies each\nunit file has on another. Forward or backward, explicit or implicit, the\ndependency map gives you a good idea of what every startup process requires\nduring the system to bootup process.\n\nTo create a dependency map, we have to specify the action `-a`, or `--action`,\nargument to be `dep`. We also need to point Systemd Snapshot at the snapshot\nthat we want to build the dependency map from:\n\n`python3 systemd_snapshot.py -a dep -p data/my_snap_ms.json -o data/my_snap`\n\nThis command will create a dependency map from the \"my_snap_ms.json\" file that\nwas created in the previous section, and will save it to \"my_snap_dm.json\".\n\nThis will allow you to get a good idea of the dependencies that will actually\nbe expected during boot. This is important because not all unit files will be\ncalled by default during the boot process, so its important to know what will\nand won't be started.\n\nYou can also use the `-t`, or `--target-unit` to specify a unit file to start\nthe dependency mapping from, instead of using \"default.target\":\n\n`python3 systemd_snapshot.py -a dep -p data/my_snap_ms.json -o data/my_snap -t apache.target`\n\nThis command will do the same thing as we did before, except the dependency\nmap will start from \"apache.target\" and map all dependencies created from that\npoint onward.\n\nThis is useful if you want to only see what dependencies a specific service\nwill create during boot, or if you want to see what startup processes will\nstart if a different target (or runlevel) is specified as the default target.\n\nKeep in mind that Systemd Snapshot will not overwrite a file that already\nexists unless you include the `-f` or `--force-overwrite` option, so if you are\ncreating different dependency maps make sure to name them uniquely or use `-f`!\n\n## Creating a Graph\n\nThe graphing function of Systemd Snapshot allows users to use the Cytoscape UI\nas a front end to visualize the targeted systemd startup via a graph. This is\nalso the only feature that requires extra dependencies to be installed to use,\nso if you haven't already installed the graphing dependencies go back to the\ninstallation section and install them.\n\nOnce you have the dependencies installed, we can create a graph similar to how\nwe created a dependency map. Make sure you have Cytoscape open, and run:\n\n`python3 systemd_snapshot.py -a graph -p data/my_snap_ms.json`\n\nThis creates the graph based on the snapshot that is being pointed to by the\n`-p` option. Once the graph is created, it will automatically be ported over to\nCytoscape, and will be viewable once it finishes.\n\nBe warned! There is a LOT going on with Systemd and these graphs will be VERY\nlarge by default. Which is why it is suggested to use the yfiles plugin for\nCytoscape, and if you can use the `-t` option to narrow down your search, do\nit.\n\nAll of the vertices and edges of the graphs in Cytoscape are actually very\ncustomizable, so if you want to change the color of something feel free! This\ncan be done via style files. These style files are basically just config files\nto tell Cytoscape how to render the graph.\n\nThere is a default style file that is kept in the data folder of Systemd\nSnapshot. This file can be modified, or if you want to create another style\nfile you can just point Systemd Snapshot to that style file with the `-S`, or\n`--style-file`, option.\n\n## Comparing Results\n\nThe last thing we will go over in the tutorial is the ability to compare the\ndifferent output files we are creating. This comparison is very useful if we\nwant to do a threat hunt, or if we want to see the differences between two\ndifferent versions of firmware:\n\n`python3 systemd_snapshot.py -a compare -p data/snap1_ms.json -c data/snap2_ms.json -o data/comp`\n\nThis command will compare the \"snap1_ms.json\" and \"snap2_ms.json\" files and\nsave the differences to \"data/comp_df.json\". The comparison file will highlight\nall of the differences between the two snapshots, including libraries,\nbinaries, interesting file paths that are referenced, config files, and even\ndifferences between which command line arguments are used to start up a service\nduring the boot process.\n\nNote that you can compare either master structure files or dependency maps, we\ncan only compare the same types of files. No comparing a master structure file\nto a dependency map file.\n\nAs a last word, I hope this tool is helpful! If you have any ideas on how to\nimprove it, I would be happy to hear them!","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fornl%2Fsystemd_snapshot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fornl%2Fsystemd_snapshot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fornl%2Fsystemd_snapshot/lists"}