Skip to content

Conversation

@bcoles
Copy link
Contributor

@bcoles bcoles commented Oct 23, 2025

This module replaces exploit/linux/local/diamorphine_rootkit_signal_priv_esc.

register_options([
OptInt.new('MIN_SIGNAL', [true, 'Start at signal', 0]),
OptInt.new('MAX_SIGNAL', [true, 'Stop at signal', 64]),
OptString.new('PID', [true, 'Process ID to send signals to', '$$'])
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not work on fish shell as it does not support $$. Regardless, using the current process is by far the safest default.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to be extra-fancy and support non-POSIX shells, cut -d ' ' -f 4 /proc/self/stat gets the current PID.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to be extra-fancy and support non-POSIX shells, cut -d ' ' -f 4 /proc/self/stat gets the current PID.

This still runs into the same issue with Fish which does not support $() or backticks for command substitution.

$$ is widely supported.

I'm not concerned about supporting Fish. The module that this module replaces also did not support Fish.

# Iterate from MIN to MAX sending each signal to PID.
# SIGCONT if the process hangs.
res = cmd_exec(
%{i=#{datastore['MIN_SIGNAL']}; while [ "$i" -le #{datastore['MAX_SIGNAL']} ]; do sh -c "kill -$i #{pid}; id" 2>/dev/null & pid=$!; sleep 0.1; kill -CONT "$pid" 2>/dev/null; wait "$pid"; i=$((i + 1)); done 2>/dev/null},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This abomination is designed to be portable, fast, and safe.

  • Fast: Faster than calling cmd_exec(kill ...) for each signal (but not as fast as using a PID which the user doesn't have permission to kill, such as PID 1, or PID for a non-existent process).
  • Portable: This should work on all POSIX compliant shells, but will fail on fish due to lack of support for $! (and $$).
  • Safe: A new shell is spawned for each attempt, and signals are sent to this PID by default.

Using a different PID (such as PID 1) by default would have made the code much faster and cleaner, but it is not a safe default. The module includes a root check to prevent running as root, but indiscriminately spamming signals at PID 1 is never safe. For example, CAP_KILL allows non-root users to kill arbitrary processes.

An alternative is to use PID 666 by default. This is the default PID used by KoviD rookit. But again, this runs the risk of terminating any legitimate process which happens to use this PID.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe putting it on several lines with a join would make it a bit less abominable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Still an abomination.

If we didn't care about leaving a few hung shell processes, the code would be much cleaner (a single command).

'Notes' => {
'Reliability' => [ REPEATABLE_SESSION ],
'Stability' => [ CRASH_OS_DOWN ],
'SideEffects' => [ ARTIFACTS_ON_DISK, SCREEN_EFFECTS ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why SCREEN_EFFECTS?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the session is associated with a user with a GUI session, the user may see crash handler popups.

register_options([
OptInt.new('MIN_SIGNAL', [true, 'Start at signal', 0]),
OptInt.new('MAX_SIGNAL', [true, 'Stop at signal', 64]),
OptString.new('PID', [true, 'Process ID to send signals to', '$$'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
OptString.new('PID', [true, 'Process ID to send signals to', '$$'])
OptString.new('PID', [true, 'Process ID to send signals to ($$ for the current process)', '$$'])

Not everyone is fluent in sh-fu, and some people might be unaware of the $$ trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the process ID of a newly spawned process, rather than the current process. I've changed the description.

register_options([
OptInt.new('MIN_SIGNAL', [true, 'Start at signal', 0]),
OptInt.new('MAX_SIGNAL', [true, 'Stop at signal', 64]),
OptString.new('PID', [true, 'Process ID to send signals to', '$$'])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to be extra-fancy and support non-POSIX shells, cut -d ' ' -f 4 /proc/self/stat gets the current PID.

# Iterate from MIN to MAX sending each signal to PID.
# SIGCONT if the process hangs.
res = cmd_exec(
%{i=#{datastore['MIN_SIGNAL']}; while [ "$i" -le #{datastore['MAX_SIGNAL']} ]; do sh -c "kill -$i #{pid}; id" 2>/dev/null & pid=$!; sleep 0.1; kill -CONT "$pid" 2>/dev/null; wait "$pid"; i=$((i + 1)); done 2>/dev/null},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe putting it on several lines with a join would make it a bit less abominable?

@bcoles bcoles force-pushed the rootkit_privesc_signal_hunter branch from 5fa9347 to 0fbf02b Compare October 24, 2025 08:48
@msutovsky-r7 msutovsky-r7 self-assigned this Oct 24, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants