Add Objective See Hunter Module: OSX Antivirus Enumeration and Disablement#20813
Add Objective See Hunter Module: OSX Antivirus Enumeration and Disablement#20813gardnerapp wants to merge 27 commits intorapid7:masterfrom
Conversation
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
Co-authored-by: Julien Voisin <[email protected]>
|
Thanks for your pull request! Before this can be merged, we need the following documentation for your module: |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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']) }There was a problem hiding this comment.
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 😂!
There was a problem hiding this comment.
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
endThat 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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Where you able to test this module with shell sessions?
There was a problem hiding this comment.
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.
Co-authored-by: Christophe De La Fuente <[email protected]>
Co-authored-by: Christophe De La Fuente <[email protected]>
cdelafuente-r7
left a comment
There was a problem hiding this comment.
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
| 'LuLu', 'BlockBlock Helper', 'Do Not Disturb', | ||
| 'ReiKey', 'RansomWhere', 'OverSight' |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Co-authored-by: Christophe De La Fuente <[email protected]>
|
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.
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. |
|
I'll get to it this weekend. I already started removing the class variables, that pattern works well in Rails but not here. |
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
.appfolder for each product within the/Applicationsfolder i.e./Applications/LuLu.appetc. For each of the products discovered the module will also find the pids associated with that application. Lastly, when theKILL_PROCESSESoption 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
post/osx/manage/objective_see_huntermoduleKILL_PROCESSEStotrueThanks again.