diff --git a/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb new file mode 100644 index 0000000000..d7a7db2409 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb @@ -0,0 +1,71 @@ +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Extapi +module Adsi + +### +# +# This meterpreter extension contains extended API functions for +# querying and managing desktop windows. +# +### +class Adsi + + def initialize(client) + @client = client + end + + # + # Perform a generic domain query against ADSI. + # + # @param domain_name [String] The FQDN of the target domain. + # @param filter [String] The filter to apply to the query in + # LDAP format. + # @param max_results [Integer] The maximum number of results + # to return. + # @param page_size [Integer] The size of the page of results + # to return. + # @param fields [Array] Array of string fields to return for + # each result found + # + # @returns [Hash] Array of field names with associated results. + # + def domain_query(domain_name, filter, max_results, page_size, fields) + request = Packet.create_request('extapi_adsi_domain_query') + + request.add_tlv(TLV_TYPE_EXT_ADSI_DOMAIN, domain_name) + request.add_tlv(TLV_TYPE_EXT_ADSI_FILTER, filter) + request.add_tlv(TLV_TYPE_EXT_ADSI_MAXRESULTS, max_results) + request.add_tlv(TLV_TYPE_EXT_ADSI_PAGESIZE, page_size) + + fields.each do |f| + request.add_tlv(TLV_TYPE_EXT_ADSI_FIELD, f) + end + + response = client.send_request(request) + + results = [] + response.each(TLV_TYPE_EXT_ADSI_RESULT) { |r| + result = [] + r.each(TLV_TYPE_EXT_ADSI_VALUE) { |v| + result << v.value + } + results << result + } + + return { + :fields => fields, + :results => results + } + end + + attr_accessor :client + +end + +end; end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb index af97be3e42..08408e3489 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb @@ -4,6 +4,7 @@ require 'rex/post/meterpreter/extensions/extapi/tlv' require 'rex/post/meterpreter/extensions/extapi/window/window' require 'rex/post/meterpreter/extensions/extapi/service/service' require 'rex/post/meterpreter/extensions/extapi/clipboard/clipboard' +require 'rex/post/meterpreter/extensions/extapi/adsi/adsi' module Rex module Post @@ -30,7 +31,8 @@ class Extapi < Extension { 'window' => Rex::Post::Meterpreter::Extensions::Extapi::Window::Window.new(client), 'service' => Rex::Post::Meterpreter::Extensions::Extapi::Service::Service.new(client), - 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client) + 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client), + 'adsi' => Rex::Post::Meterpreter::Extensions::Extapi::Adsi::Adsi.new(client) }) }, ]) diff --git a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb index 5c47c4905e..132230e3ca 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb @@ -40,6 +40,14 @@ TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX = TLV_META_TYPE_UINT | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 47) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 48) +TLV_TYPE_EXT_ADSI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 55) +TLV_TYPE_EXT_ADSI_FILTER = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 56) +TLV_TYPE_EXT_ADSI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 57) +TLV_TYPE_EXT_ADSI_VALUE = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 58) +TLV_TYPE_EXT_ADSI_RESULT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 59) +TLV_TYPE_EXT_ADSI_MAXRESULTS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 60) +TLV_TYPE_EXT_ADSI_PAGESIZE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 61) + end end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb index ffd231c7e0..40df85f5f8 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb @@ -16,6 +16,7 @@ class Console::CommandDispatcher::Extapi require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/window' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/service' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard' + require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi' Klass = Console::CommandDispatcher::Extapi @@ -23,7 +24,8 @@ class Console::CommandDispatcher::Extapi [ Klass::Window, Klass::Service, - Klass::Clipboard + Klass::Clipboard, + Klass::Adsi ] include Console::CommandDispatcher diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb new file mode 100644 index 0000000000..a8659650e3 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb @@ -0,0 +1,198 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Extended API ADSI management user interface. +# +### +class Console::CommandDispatcher::Extapi::Adsi + + Klass = Console::CommandDispatcher::Extapi::Adsi + + include Console::CommandDispatcher + + # Zero indicates "no limit" + DEFAULT_MAX_RESULTS = 0 + DEFAULT_PAGE_SIZE = 0 + + # + # List of supported commands. + # + def commands + { + "adsi_user_enum" => "Enumerate all users on the specified domain.", + "adsi_computer_enum" => "Enumerate all computers on the specified domain.", + "adsi_domain_query" => "Enumerate all objects on the specified domain that match a filter." + } + end + + # + # Name for this dispatcher + # + def name + "Extapi: ADSI Management" + end + + # + # Options for the adsi_user_enum command. + # + @@adsi_user_enum_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_user_enum_usage + print( + "\nUsage: adsi_user_enum [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the users on the target domain.\n\n" + + "Enumeration returns information such as the user name, SAM account name, locked\n" + + "status, desc, and comment.\n" + + @@adsi_user_enum_opts.usage) + end + + # + # Enumerate domain users. + # + def cmd_adsi_user_enum(*args) + args.unshift("-h") if args.length == 0 + if args.include?("-h") + adsi_user_enum_usage + return true + end + + domain = args.shift + filter = "(objectClass=user)" + fields = [ + "samaccountname", + "name", + "distinguishedname", + "description", + "comment" + ] + args = [domain, filter] + fields + args + return cmd_adsi_domain_query(*args) + end + + # + # Options for the adsi_computer_enum command. + # + @@adsi_computer_enum_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_computer_enum_usage + print( + "\nUsage: adsi_computer_enum [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the computers on the target domain.\n\n" + + "Enumeration returns information such as the computer name, desc, and comment.\n" + + @@adsi_computer_enum_opts.usage) + end + + # + # Enumerate domain computers. + # + def cmd_adsi_computer_enum(*args) + args.unshift("-h") if args.length == 0 + if args.include?("-h") + adsi_computer_enum_usage + return true + end + + domain = args.shift + filter = "(objectClass=computer)" + fields = [ + "name", + "distinguishedname", + "description", + "comment" + ] + args = [domain, filter] + fields + args + return cmd_adsi_domain_query(*args) + end + + # + # Options for the adsi_domain_query command. + # + @@adsi_domain_query_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_domain_query_usage + print( + "\nUsage: adsi_domain_query [field 2 [field ..]] [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the objects on the target domain.\n\n" + + "Enumeration returns the set of fields that are specified.\n" + + @@adsi_domain_query_opts.usage) + end + + # + # Enumerate domain objects. + # + def cmd_adsi_domain_query(*args) + page_size = DEFAULT_PAGE_SIZE + max_results = DEFAULT_MAX_RESULTS + + args.unshift("-h") if args.length < 3 + + @@adsi_domain_query_opts.parse(args) { |opt, idx, val| + case opt + when "-p" + page_size = val.to_i + when "-m" + max_results = val.to_i + when "-h" + adsi_domain_query_usage + return true + end + } + + # Assume that the flags are passed in at the end. Safe? + switch_index = args.index { |a| a.start_with?("-") } + if switch_index + args = args.first(switch_index) + end + + domain = args.shift + filter = args.shift + + objects = client.extapi.adsi.domain_query(domain, filter, max_results, page_size, args) + + table = Rex::Ui::Text::Table.new( + 'Header' => "#{domain} Objects", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => objects[:fields] + ) + + objects[:results].each do |c| + table << c + end + + print_line + print_line(table.to_s) + + print_line("Total objects: #{objects[:results].length}") + + print_line + + return true + end + +end + +end +end +end +end +