require 'rexml/rexml' require 'rexml/source' require 'rexml/document' require 'rexml/parsers/treeparser' require 'rex/proto/http' require 'uri' module Rex module Exploitation module OpcodeDb module OpcodeResult # :nodoc: def initialize(hash) @hash = hash end attr_reader :hash end ### # # This class provides a general interface to items that come from that opcode # database that have a symbolic entry identifier and name. # ### module DbEntry include OpcodeResult def initialize(hash) super @id = hash['id'].to_i @name = hash['name'] end # # Fields that could possibly be filtered on # def filter_hash { "id" => id, "name" => name } end attr_reader :id, :name end ### # # This class represents a particular image module including its name, # segments, imports, exports, base address, and so on. # ### class ImageModule include DbEntry ### # # This class contains information about a module-associated segment. # ### class Segment def initialize(hash) @type = hash['type'] @base_address = hash['base_address'].to_i @size = hash['segment_size'].to_i @writable = hash['writable'] == "true" ? true : false @readable = hash['readable'] == "true" ? true : false @executable = hash['executable'] == "true" ? true : false end attr_reader :type attr_reader :base_address attr_reader :size attr_reader :writable attr_reader :readable attr_reader :executable end ### # # This class contains information about a module-associated import. # ### class Import def initialize(hash) @name = hash['name'] @address = hash['address'].to_i @ordinal = hash['ordinal'].to_i end attr_reader :name attr_reader :address attr_reader :ordinal end ### # # This class contains information about a module-associated export. # ### class Export def initialize(hash) @name = hash['name'] @address = hash['address'].to_i @ordinal = hash['ordinal'].to_i end attr_reader :name attr_reader :address attr_reader :ordinal end def initialize(hash) super @locale = Locale.new(hash['locale']) @maj_maj_ver = hash['maj_maj_ver'].to_i @maj_min_ver = hash['maj_min_ver'].to_i @min_maj_ver = hash['min_maj_ver'].to_i @min_min_ver = hash['min_min_ver'].to_i @timestamp = Time.at(hash['timestamp'].to_i) @vendor = hash['vendor'] @base_address = hash['base_address'].to_i @image_size = hash['image_size'].to_i @segments = hash['segments'].map { |ent| Segment.new(ent) } if (hash['segments']) @imports = hash['imports'].map { |ent| Import.new(ent) } if (hash['imports']) @exports = hash['exports'].map { |ent| Export.new(ent) } if (hash['exports']) @platforms = hash['platforms'].map { |ent| OsVersion.new(ent) } if (hash['platforms']) @segments = [] unless(@segments) @imports = [] unless(@imports) @exports = [] unless(@exports) @platforms = [] unless(@platforms) end attr_reader :locale attr_reader :maj_maj_ver attr_reader :maj_min_ver attr_reader :min_maj_ver attr_reader :min_min_ver attr_reader :timestamp attr_reader :vendor attr_reader :base_address attr_reader :image_size attr_reader :segments attr_reader :imports attr_reader :exports attr_reader :platforms end ### # # This class contains information about a specific locale, such as English. # ### class Locale include DbEntry end ### # # This class contains information about a platform (operating system) version. # ### class OsVersion include DbEntry def initialize(hash) super @modules = (hash['modules']) ? hash['modules'].to_i : 0 @desc = hash['desc'] @arch = hash['arch'] @maj_ver = hash['maj_ver'].to_i @min_ver = hash['min_ver'].to_i @maj_patch_level = hash['maj_patch_level'].to_i @min_patch_level = hash['min_patch_level'].to_i end attr_reader :modules attr_reader :desc attr_reader :arch attr_reader :maj_ver attr_reader :min_ver attr_reader :maj_patch_level attr_reader :min_patch_level end ### # # An opcode group (esp => eip). # ### class Group include DbEntry end ### # # An opcode type (jmp esp). # ### class Type include DbEntry def initialize(hash) super @opcodes = (hash['opcodes']) ? hash['opcodes'].to_i : 0 @meta_type = MetaType.new(hash['meta_type']) if (hash['meta_type']) @group = Group.new(hash['group']) if (hash['group']) @arch = hash['arch'] end attr_reader :opcodes attr_reader :meta_type attr_reader :group attr_reader :arch end ### # # An opcode meta type (jmp reg). # ### class MetaType include DbEntry end ### # # An opcode that has a specific address and is associated with one or more # modules. # ### class Opcode include DbEntry def initialize(hash) super @address = hash['address'].to_i @type = Type.new(hash['type']) @group = @type.group @modules = hash['modules'].map { |ent| ImageModule.new(ent) } if (hash['modules']) @modules = [] unless(@modules) end attr_reader :address attr_reader :type attr_reader :group attr_reader :modules end ### # # Current statistics of the opcode database. # ### class Statistics def initialize(hash) @modules = hash['modules'].to_i @opcodes = hash['opcodes'].to_i @opcode_types = hash['opcode_types'].to_i @platforms = hash['platforms'].to_i @architectures = hash['architectures'].to_i @module_segments = hash['module_segments'].to_i @module_imports = hash['module_imports'].to_i @module_exports = hash['module_exports'].to_i @last_update = Time.at(hash['last_update'].to_i) end attr_reader :modules attr_reader :opcodes attr_reader :opcode_types attr_reader :platforms attr_reader :architectures attr_reader :module_segments attr_reader :module_imports attr_reader :module_exports attr_reader :last_update end ### # # This class implements a client interface to the Metasploit Opcode Database. # It is intended to be used as a method of locating reliable return addresses # given a set of executable files and a set of usable opcodes. # ### class Client DefaultServerHost = "www.metasploit.com" DefaultServerPort = 80 DefaultServerUri = "/users/opcode/msfopcode_server.cgi" # # Returns an instance of an initialized client that will use the supplied # server values. # def initialize(host = DefaultServerHost, port = DefaultServerPort, uri = DefaultServerUri) self.server_host = host self.server_port = port self.server_uri = uri end # # Returns an array of MetaType instances. # def meta_types request('meta_types').map { |ent| MetaType.new(ent) } end # # Returns an array of Group instances. # def groups request('groups').map { |ent| Group.new(ent) } end # # Returns an array of Type instances. Opcode types are specific opcodes, # such as a jmp esp. Optionally, a filter hash can be passed to include # extra information in the results. # # Statistics (Bool) # # If this hash element is set to true, the number of opcodes currently in # the database of this type will be returned. # def types(filter = {}) request('types', filter).map { |ent| Type.new(ent) } end # # Returns an array of OsVersion instances. OS versions are associated with # a particular operating system release (including service packs). # Optionally, a filter hash can be passed to limit the number of results # returned. If no filter hash is supplied, all results are returned. # # Names (Array) # # If this hash element is specified, only the operating systems that # contain one or more of the names specified will be returned. # # Statistics (Bool) # # If this hash element is set to true, the number of modules associated # with this matched operating system versions will be returned. # def platforms(filter = {}) request('platforms', filter).map { |ent| OsVersion.new(ent) } end # # Returns an array of ImageModule instances. Image modules are # version-specific, locale-specific, and operating system version specific # image files. Modules have opcodes, segments, imports and exports # associated with them. Optionally, a filter hash can be specified to # limit the number of results returned from the database. If no filter # hash is supplied, all modules will be returned. # # Locales (Array) # # This hash element limits results to one or more specific locale. The # elements in the array should be instances of a Locale class. # # LocaleNames (Array) # # This hash element limits results to one or more specific locale by name. # # Platforms (Array) # # This hash element limits results to one or more specific operating # system version. The elements in this array should be instances of the # OsVersion class. # # PlatformNames (Array) # # This hash element limits results to one or more specific platform by # name. # # Modules (Array) # # This hash element limits results to one or more specific module. The # elements in this array must be instances of the ImageModule class. # # ModuleNames (Array) # # This hash element limits results to one or more specific module by name. # # Segments (Bool) # # If this hash element is set to true, the segments associated with each # resulting module will be returned by the server. # # Imports (Bool) # # If this hash element is set to true, the imports associated with each # resulting module will be returned by the server. # # Exports (Bool) # # If this hash element is set to true, the exports associated with each # resulting module will be returned by the server. # def modules(filter = {}) request('modules', filter).map { |ent| ImageModule.new(ent) } end # # Returns an array of Locale instances that are supported by the server. # def locales request('locales').map { |ent| Locale.new(ent) } end # # Returns an array of Opcode instances that match the filter limitations # specified in the supplied filter hash. If no filter hash is specified, # all opcodes will be returned (but are most likely going to be limited by # the server). The filter hash limiters that can be specified are: # # Modules (Array) # # This hash element limits results to one or more specific module. The # elements in the array must be instances of the ImageModule class. # # ModuleNames (Array) # # This hash element limits results to one or more specific modules by # name. # # Groups (Array) # # This hash element limits results to one or more specific opcode group. # The elements in the array must be instances of the Group class. # # GroupNames (Array) # # This hash element limits results to one or more specific opcode group by # name. # # Types (Array) # # This hash element limits results to one or more specific opcode type. # The elements in the array must be instances of the Type class. # # TypeNames (Array) # # This hash element limits results to one or more specific opcode type by # name. # # MetaTypes (Array) # # This hash element limits results to one or more specific opcode meta # type. The elements in the array must be instances of the MetaType # class. # # MetaTypeNames (Array) # # This hash element limits results to one or more specific opcode meta # type by name. # # Locales (Array) # # Limits results to one or more specific locale. The elements in the # array must be instances of the Locale class. # # LocaleNames (Array) # # Limits results to one or more specific locale by name. # # Platforms (Array) # # Limits results to one or more specific operating system versions. The # elements in the array must be instances of the OsVersion class. # # PlatformNames (Array) # # Limits reslts to one or more specific operating system version by name. # # Addresses (Array) # # Limits results to a specific set of addresses. # # Portable (Bool) # # If this hash element is true, opcode results will be limited to ones # that span more than one operating system version. # def search(filter = {}) request('search', filter).map { |ent| Opcode.new(ent) } end # # Returns an instance of the Statistics class that holds information about # the server's database stats. # def statistics Statistics.new(request('statistics')) end # # These attributes convey information about the remote server and can be # changed in order to point it to a locate copy as necessary. # attr_accessor :server_host, :server_port, :server_uri protected # # Transmits a request to the Opcode database server and translates the # response into a native general ruby datatype. # def request(method, opts = {}) client = Rex::Proto::Http::Client.new(server_host, server_port) begin # Initialize the request with the POST body. request = client.gen_post(server_uri) request.body += "method=#{method}" # Enumerate each option filter specified and convert it into a CGI # parameter. opts.each_pair { |k, v| request.body += "&#{k}=#{xlate_param(v)}" } # Send the request and grab the response. response = client.send_request(request) # Non-200 return code? if (response.code != 200) raise RuntimeError, "Invalid response recieved from server." end # Convert the return value to the native type. parse_response(response.body) ensure client.close end end # # Translates a parameter into a flat CGI parameter string. # def xlate_param(v) if (v.kind_of?(Array)) v.map { |ent| xlate_param(ent) }.join(',') elsif (v.kind_of?(Hash)) v.map { |k,v| "#{URI.escape(k)}:#{xlate_param(v)}" if (v) }.join(',') else URI.escape(v.to_s) end end # # Translate the data type from a flat string to a ruby native type. # def parse_response(xml) source = REXML::Source.new(xml) doc = REXML::Document.new REXML::Parsers::TreeParser.new(source, doc).parse translate_element(doc.root) end # # Translate elements conveyed as data types. # def translate_element(element) case element.name when "Array" return element.elements.map { |child| translate_element(child) } when "Hash" hsh = {} element.each_element { |child| if (e = child.elements[1]) v = translate_element(e) else v = child.text end hsh[child.attributes['name']] = v } return hsh else return element.text end end end end end end