From 80fe96109b34780396dfde7bb25959f329e56b3b Mon Sep 17 00:00:00 2001 From: Ruben Groenewoud <78494512+Aegrah@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:58:43 +0100 Subject: [PATCH] [New & Tuning] Persistence via GRUB Bootloader (#4401) * [New & Tuning] Persistence via GRUB Bootloader * testing github version code workflow update * testing github version code workflow re-order --------- Co-authored-by: terrancedejesus --- .../workflows/version-code-and-release.yml | 8 +- hunting/index.md | 1 + hunting/index.yml | 5 + .../docs/persistence_via_git_hook_pager.md | 2 +- .../docs/persistence_via_grub_bootloader.md | 120 ++++++++++++++++++ ...nce_via_pluggable_authentication_module.md | 2 +- hunting/linux/docs/persistence_via_udev.md | 2 +- ...istence_via_xdg_autostart_modifications.md | 2 +- .../persistence_via_git_hook_pager.toml | 2 +- .../persistence_via_grub_bootloader.toml | 101 +++++++++++++++ .../persistence_via_package_manager.toml | 2 +- ...e_via_pluggable_authentication_module.toml | 2 +- .../linux/queries/persistence_via_udev.toml | 2 +- ...tence_via_xdg_autostart_modifications.toml | 2 +- 14 files changed, 240 insertions(+), 13 deletions(-) create mode 100644 hunting/linux/docs/persistence_via_grub_bootloader.md create mode 100644 hunting/linux/queries/persistence_via_grub_bootloader.toml diff --git a/.github/workflows/version-code-and-release.yml b/.github/workflows/version-code-and-release.yml index 65b517f97..dbea182fd 100644 --- a/.github/workflows/version-code-and-release.yml +++ b/.github/workflows/version-code-and-release.yml @@ -3,17 +3,17 @@ name: Version Code Check and Draft Release on: pull_request: paths: + - '**/*.md' - 'lib/**' - 'hunting/**/*.py' - - '!hunting/**/*.md' - - '!hunting/index.md' - - '!hunting/**/*.toml' - 'pyproject.toml' - 'Makefile' - 'docs/**' - 'detection_rules/**' - 'tests/**' - - '**/*.md' + - '!hunting/**/*.md' + - '!hunting/index.md' + - '!hunting/**/*.toml' types: [opened, reopened, synchronize, labeled, closed] permissions: diff --git a/hunting/index.md b/hunting/index.md index 81070ab03..170547c3a 100644 --- a/hunting/index.md +++ b/hunting/index.md @@ -43,6 +43,7 @@ Here are the queries currently available: - [Persistence via DPKG/RPM Package](./linux/docs/persistence_via_rpm_dpkg_installer_packages.md) (ES|QL) - [Persistence via Docker Container](./linux/docs/persistence_via_malicious_docker_container.md) (ES|QL) - [Persistence via Dynamic Linker Hijacking](./linux/docs/persistence_via_dynamic_linker_hijacking.md) (ES|QL) +- [Persistence via GRUB Bootloader](./linux/docs/persistence_via_grub_bootloader.md) (ES|QL) - [Persistence via Loadable Kernel Modules](./linux/docs/persistence_via_loadable_kernel_modules.md) (ES|QL) - [Persistence via Message-of-the-Day](./linux/docs/persistence_via_message_of_the_day.md) (ES|QL) - [Persistence via Package Manager](./linux/docs/persistence_via_package_manager.md) (ES|QL) diff --git a/hunting/index.yml b/hunting/index.yml index 053f73f22..78aae1df3 100644 --- a/hunting/index.yml +++ b/hunting/index.yml @@ -250,6 +250,11 @@ linux: path: ./linux/queries/persistence_via_malicious_docker_container.toml mitre: - T1610 + 7adc1a69-3962-4f84-a46d-0b68f69e45a8: + name: Persistence via GRUB Bootloader + path: ./linux/queries/persistence_via_grub_bootloader.toml + mitre: + - T1542 okta: 0b936024-71d9-11ef-a9be-f661ea17fbcc: name: Failed OAuth Access Token Retrieval via Public Client App diff --git a/hunting/linux/docs/persistence_via_git_hook_pager.md b/hunting/linux/docs/persistence_via_git_hook_pager.md index b219a2da1..bfa1c8430 100644 --- a/hunting/linux/docs/persistence_via_git_hook_pager.md +++ b/hunting/linux/docs/persistence_via_git_hook_pager.md @@ -54,7 +54,7 @@ SELECT g.groupname AS group_owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status change time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change time, datetime(f.btime, 'unixepoch') AS file created time, f.size AS size bytes FROM diff --git a/hunting/linux/docs/persistence_via_grub_bootloader.md b/hunting/linux/docs/persistence_via_grub_bootloader.md new file mode 100644 index 000000000..170ac6dc7 --- /dev/null +++ b/hunting/linux/docs/persistence_via_grub_bootloader.md @@ -0,0 +1,120 @@ +# Persistence via GRUB Bootloader + +--- + +## Metadata + +- **Author:** Elastic +- **Description:** This hunt identifies potential persistence mechanisms leveraging the GRUB bootloader on Linux systems. GRUB, as the primary bootloader on many Linux distributions, can be manipulated by attackers to gain persistent access or control over the boot process. By monitoring file creations, modifications, and GRUB-related process executions, this hunt helps detect unauthorized changes that could indicate malicious activity. It also provides metadata about critical GRUB configuration files, supporting forensic analysis of potential threats. + +- **UUID:** `7adc1a69-3962-4f84-a46d-0b68f69e45a8` +- **Integration:** [endpoint](https://docs.elastic.co/integrations/endpoint) +- **Language:** `[ES|QL, SQL]` +- **Source File:** [Persistence via GRUB Bootloader](../queries/persistence_via_grub_bootloader.toml) + +## Query + +```sql +sql +from logs-endpoint.events.file-* +| keep @timestamp, host.os.type, event.type, event.action, file.path, process.executable, agent.id +| where @timestamp > now() - 30 day +| where host.os.type == "linux" and event.type == "creation" and ( + file.path like "/etc/default/*" or + file.path like "/etc/grub.d/*" or + file.path like "/boot/grub2/*" or + file.path like "/boot/grub/*" or + file.path like "/boot/efi/EFI/*" or + file.path like "/etc/sysconfig/*" +) +| stats cc = count(), agent_count = count_distinct(agent.id) by file.path, process.executable +| where agent_count <= 3 and cc <= 10 +| sort cc asc +| limit 100 +``` + +```sql +sql +from logs-endpoint.events.process-* +| keep @timestamp, host.os.type, event.type, event.action, process.name, process.executable, process.parent.executable, agent.id +| where @timestamp > now() - 30 day +| where host.os.type == "linux" and event.action == "exec" and event.type == "start" and process.name in ("grub-mkconfig", "grub2-mkconfig", "update-grub") +| stats cc = count(), agent_count = count_distinct(agent.id) by process.executable, process.parent.executable +| where agent_count <= 3 and cc < 15 +| sort cc asc +| limit 100 +``` + +```sql +sql +SELECT + f.filename, + f.path, + u.username AS file_owner, + g.groupname AS group_owner, + datetime(f.atime, 'unixepoch') AS file_last_access_time, + datetime(f.mtime, 'unixepoch') AS file_last_modified_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, + datetime(f.btime, 'unixepoch') AS file_created_time, + f.size AS size_bytes +FROM + file f +LEFT JOIN + users u ON f.uid = u.uid +LEFT JOIN + groups g ON f.gid = g.gid +WHERE + f.path = '/etc/default/grub' + OR f.path LIKE '/etc/default/grub.d/%' + OR f.path LIKE '/etc/grub.d/%' + OR f.path = '/boot/grub2/grub.cfg' + OR f.path = '/boot/grub/grub.cfg' + OR f.path = '/boot/efi/EFI/%/grub.cfg' + OR f.path = '/etc/sysconfig/grub' +``` + +```sql +sql +SELECT + f.filename, + f.path, + u.username AS file_owner, + g.groupname AS group_owner, + datetime(f.atime, 'unixepoch') AS file_last_access_time, + datetime(f.mtime, 'unixepoch') AS file_last_modified_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, + datetime(f.btime, 'unixepoch') AS file_created_time, + f.size AS size_bytes +FROM + file f +LEFT JOIN + users u ON f.uid = u.uid +LEFT JOIN + groups g ON f.gid = g.gid +WHERE ( + f.path = '/etc/default/grub' + OR f.path LIKE '/etc/default/grub.d/%' + OR f.path LIKE '/etc/grub.d/%' + OR f.path = '/boot/grub2/grub.cfg' + OR f.path = '/boot/grub/grub.cfg' + OR f.path = '/boot/efi/EFI/%/grub.cfg' + OR f.path = '/etc/sysconfig/grub' + ) +AND (mtime > strftime('%s', 'now') - (7 * 86400)); -- Modified in the last 7 days +``` + +## Notes + +- Tracks the creation of files in GRUB configuration directories such as /etc/default, /etc/grub.d, and /boot/grub* to identify unauthorized additions. +- Monitors the execution of GRUB-related commands like grub-mkconfig and update-grub, which may indicate attempts to modify bootloader settings. +- Queries metadata for GRUB configuration files to identify changes to ownership, access times, and file sizes, which may suggest tampering. +- Detects recent modifications to critical GRUB configuration files, including grub.cfg and related paths, to flag potential persistence mechanisms. +- Helps correlate events across endpoints to identify rare or anomalous activities related to GRUB, enhancing detection capabilities for bootloader persistence. + +## MITRE ATT&CK Techniques + +- [T1542](https://attack.mitre.org/techniques/T1542) + +## License + +- `Elastic License v2` diff --git a/hunting/linux/docs/persistence_via_pluggable_authentication_module.md b/hunting/linux/docs/persistence_via_pluggable_authentication_module.md index 0818005cf..af159ef35 100644 --- a/hunting/linux/docs/persistence_via_pluggable_authentication_module.md +++ b/hunting/linux/docs/persistence_via_pluggable_authentication_module.md @@ -41,7 +41,7 @@ SELECT g.groupname AS group_owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status_change_time + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, datetime(f.btime, 'unixepoch') AS file_created_time, f.size AS size_bytes FROM diff --git a/hunting/linux/docs/persistence_via_udev.md b/hunting/linux/docs/persistence_via_udev.md index 78ecfb6dc..6cef1ad36 100644 --- a/hunting/linux/docs/persistence_via_udev.md +++ b/hunting/linux/docs/persistence_via_udev.md @@ -63,7 +63,7 @@ SELECT g.groupname AS group owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status change_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, datetime(f.btime, 'unixepoch') AS file_created_time, f.size AS size bytes, h.md5 diff --git a/hunting/linux/docs/persistence_via_xdg_autostart_modifications.md b/hunting/linux/docs/persistence_via_xdg_autostart_modifications.md index 794d6bd21..e63a71126 100644 --- a/hunting/linux/docs/persistence_via_xdg_autostart_modifications.md +++ b/hunting/linux/docs/persistence_via_xdg_autostart_modifications.md @@ -98,7 +98,7 @@ SELECT g.groupname AS group_owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status change_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, datetime(f.btime, 'unixepoch') AS file_created_time, f.size AS size_bytes FROM diff --git a/hunting/linux/queries/persistence_via_git_hook_pager.toml b/hunting/linux/queries/persistence_via_git_hook_pager.toml index 3398582a7..8d6b63d44 100644 --- a/hunting/linux/queries/persistence_via_git_hook_pager.toml +++ b/hunting/linux/queries/persistence_via_git_hook_pager.toml @@ -57,7 +57,7 @@ SELECT g.groupname AS group_owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status change time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change time, datetime(f.btime, 'unixepoch') AS file created time, f.size AS size bytes FROM diff --git a/hunting/linux/queries/persistence_via_grub_bootloader.toml b/hunting/linux/queries/persistence_via_grub_bootloader.toml new file mode 100644 index 000000000..e1aa89784 --- /dev/null +++ b/hunting/linux/queries/persistence_via_grub_bootloader.toml @@ -0,0 +1,101 @@ +[hunt] +author = "Elastic" +description = """ +This hunt identifies potential persistence mechanisms leveraging the GRUB bootloader on Linux systems. GRUB, as the primary bootloader on many Linux distributions, can be manipulated by attackers to gain persistent access or control over the boot process. By monitoring file creations, modifications, and GRUB-related process executions, this hunt helps detect unauthorized changes that could indicate malicious activity. It also provides metadata about critical GRUB configuration files, supporting forensic analysis of potential threats. +""" +integration = ["endpoint"] +uuid = "7adc1a69-3962-4f84-a46d-0b68f69e45a8" +name = "Persistence via GRUB Bootloader" +language = ["ES|QL", "SQL"] +license = "Elastic License v2" +notes = [ + "Tracks the creation of files in GRUB configuration directories such as /etc/default, /etc/grub.d, and /boot/grub* to identify unauthorized additions.", + "Monitors the execution of GRUB-related commands like grub-mkconfig and update-grub, which may indicate attempts to modify bootloader settings.", + "Queries metadata for GRUB configuration files to identify changes to ownership, access times, and file sizes, which may suggest tampering.", + "Detects recent modifications to critical GRUB configuration files, including grub.cfg and related paths, to flag potential persistence mechanisms.", + "Helps correlate events across endpoints to identify rare or anomalous activities related to GRUB, enhancing detection capabilities for bootloader persistence.", +] +mitre = ["T1542"] +query = [ +'''sql +from logs-endpoint.events.file-* +| keep @timestamp, host.os.type, event.type, event.action, file.path, process.executable, agent.id +| where @timestamp > now() - 30 day +| where host.os.type == "linux" and event.type == "creation" and ( + file.path like "/etc/default/*" or + file.path like "/etc/grub.d/*" or + file.path like "/boot/grub2/*" or + file.path like "/boot/grub/*" or + file.path like "/boot/efi/EFI/*" or + file.path like "/etc/sysconfig/*" +) +| stats cc = count(), agent_count = count_distinct(agent.id) by file.path, process.executable +| where agent_count <= 3 and cc <= 10 +| sort cc asc +| limit 100 +''', +'''sql +from logs-endpoint.events.process-* +| keep @timestamp, host.os.type, event.type, event.action, process.name, process.executable, process.parent.executable, agent.id +| where @timestamp > now() - 30 day +| where host.os.type == "linux" and event.action == "exec" and event.type == "start" and process.name in ("grub-mkconfig", "grub2-mkconfig", "update-grub") +| stats cc = count(), agent_count = count_distinct(agent.id) by process.executable, process.parent.executable +| where agent_count <= 3 and cc < 15 +| sort cc asc +| limit 100 +''', +'''sql +SELECT + f.filename, + f.path, + u.username AS file_owner, + g.groupname AS group_owner, + datetime(f.atime, 'unixepoch') AS file_last_access_time, + datetime(f.mtime, 'unixepoch') AS file_last_modified_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, + datetime(f.btime, 'unixepoch') AS file_created_time, + f.size AS size_bytes +FROM + file f +LEFT JOIN + users u ON f.uid = u.uid +LEFT JOIN + groups g ON f.gid = g.gid +WHERE + f.path = '/etc/default/grub' + OR f.path LIKE '/etc/default/grub.d/%' + OR f.path LIKE '/etc/grub.d/%' + OR f.path = '/boot/grub2/grub.cfg' + OR f.path = '/boot/grub/grub.cfg' + OR f.path = '/boot/efi/EFI/%/grub.cfg' + OR f.path = '/etc/sysconfig/grub' +''', +'''sql +SELECT + f.filename, + f.path, + u.username AS file_owner, + g.groupname AS group_owner, + datetime(f.atime, 'unixepoch') AS file_last_access_time, + datetime(f.mtime, 'unixepoch') AS file_last_modified_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, + datetime(f.btime, 'unixepoch') AS file_created_time, + f.size AS size_bytes +FROM + file f +LEFT JOIN + users u ON f.uid = u.uid +LEFT JOIN + groups g ON f.gid = g.gid +WHERE ( + f.path = '/etc/default/grub' + OR f.path LIKE '/etc/default/grub.d/%' + OR f.path LIKE '/etc/grub.d/%' + OR f.path = '/boot/grub2/grub.cfg' + OR f.path = '/boot/grub/grub.cfg' + OR f.path = '/boot/efi/EFI/%/grub.cfg' + OR f.path = '/etc/sysconfig/grub' + ) +AND (mtime > strftime('%s', 'now') - (7 * 86400)); -- Modified in the last 7 days +''' +] diff --git a/hunting/linux/queries/persistence_via_package_manager.toml b/hunting/linux/queries/persistence_via_package_manager.toml index 95e2193fc..6d69fd52b 100644 --- a/hunting/linux/queries/persistence_via_package_manager.toml +++ b/hunting/linux/queries/persistence_via_package_manager.toml @@ -61,7 +61,7 @@ SELECT g.groupname AS group owner, datetime(f.atime, 'unixepoch') AS file_last_access time, datetime(f.mtime, 'unixepoch') AS file last_modified time, - datetime(f.ctime, 'unixepoch') AS file last_status change time, + datetime(f.ctime, 'unixepoch') AS file last_status_change time, datetime(f.btime, 'unixepoch') AS file created time, f.size AS size bytes FROM diff --git a/hunting/linux/queries/persistence_via_pluggable_authentication_module.toml b/hunting/linux/queries/persistence_via_pluggable_authentication_module.toml index be3f01720..52c4f8aa2 100644 --- a/hunting/linux/queries/persistence_via_pluggable_authentication_module.toml +++ b/hunting/linux/queries/persistence_via_pluggable_authentication_module.toml @@ -43,7 +43,7 @@ SELECT g.groupname AS group_owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status_change_time + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, datetime(f.btime, 'unixepoch') AS file_created_time, f.size AS size_bytes FROM diff --git a/hunting/linux/queries/persistence_via_udev.toml b/hunting/linux/queries/persistence_via_udev.toml index dcf39d832..294df0217 100644 --- a/hunting/linux/queries/persistence_via_udev.toml +++ b/hunting/linux/queries/persistence_via_udev.toml @@ -65,7 +65,7 @@ SELECT g.groupname AS group owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status change_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, datetime(f.btime, 'unixepoch') AS file_created_time, f.size AS size bytes, h.md5 diff --git a/hunting/linux/queries/persistence_via_xdg_autostart_modifications.toml b/hunting/linux/queries/persistence_via_xdg_autostart_modifications.toml index 045a92afd..72682365f 100644 --- a/hunting/linux/queries/persistence_via_xdg_autostart_modifications.toml +++ b/hunting/linux/queries/persistence_via_xdg_autostart_modifications.toml @@ -99,7 +99,7 @@ SELECT g.groupname AS group_owner, datetime(f.atime, 'unixepoch') AS file_last_access_time, datetime(f.mtime, 'unixepoch') AS file_last_modified_time, - datetime(f.ctime, 'unixepoch') AS file_last_status change_time, + datetime(f.ctime, 'unixepoch') AS file_last_status_change_time, datetime(f.btime, 'unixepoch') AS file_created_time, f.size AS size_bytes FROM