shadowbrokers-exploits/windows/Resources/Ops/PyScripts/lnk_parse.py
2017-04-14 11:45:07 +02:00

251 lines
No EOL
12 KiB
Python

import optparse
import os
import struct
import time
import dsz
import ops.cmd
import getutils
__version__ = '1.0'
def main():
parser = get_option_parser()
(options, args) = parser.parse_args()
if ((len(args) < 1) and (not options.all) and (not options.recent_documents) and (not options.nethood) and (not options.desktop)):
parser.print_help()
parser.exit(1)
if options.all:
lnk_files = _dir_listing(mask='*.lnk')
for filename in lnk_files:
remote_lnk_info(filename)
return
if options.recent_documents:
parse_folders_of_type('Recent', options.force_get)
if options.nethood:
parse_folders_of_type('NetHood', options.force_get)
if options.desktop:
parse_folders_of_type('Desktop', options.force_get)
for item in args:
lnk_files = _dir_listing(path=item, mask='*.lnk')
if (not lnk_files):
lnk_files = [item]
else:
print ''
prompt_text = ('Found %s lnk files in %s, would you like to get & parse them?' % (len(lnk_files), item))
if ((not options.force_get) and (not dsz.ui.Prompt(prompt_text, dsz.GOOD))):
continue
for filename in lnk_files:
if filename.lower().endswith('.lnk'):
remote_lnk_info(filename)
def get_option_parser():
usage = 'usage: %prog [options] [filename] [directory of files]'
description = 'Parse the contents of a .lnk file (or folder of .lnks)'
parser = optparse.OptionParser(usage=usage, version=__version__, description=description)
parser.disable_interspersed_args()
parser.add_option('-r', '--recent-documents', action='store_true', default=False, help='Find all Recent document folders and parse the lnk files in those folders')
parser.add_option('-n', '--nethood', action='store_true', default=False, help='Find all NetHood folders and parse the lnk files in those folders')
parser.add_option('-d', '--desktop', action='store_true', default=False, help='Find all Desktop folders and parse the lnk files in those folders')
parser.add_option('-a', '--all', action='store_true', default=False, help='Search for and parse all lnk files on the target')
parser.add_option('-f', '--force-get', action='store_true', default=False, help="Automatically get lnk files, don't prompt per each folder")
return parser
def remote_lnk_info(filename, verbose=True):
result = getutils.wrapget(filename)
if (not result.successful):
return None
lnk_file = LnkParser(result.FullLocalName)
if verbose:
_print_lnk_info(lnk_file)
return lnk_file
def parse_folders_of_type(folder_mask, force=False):
print ''
dsz.ui.Echo(("Searching for all '%s' folders..." % folder_mask), dsz.GOOD)
mask_dirs = _dir_listing(mask=folder_mask, dirsonly=True)
for directory in mask_dirs:
lnk_files = _dir_listing(path=directory, mask='*.lnk')
if (not lnk_files):
continue
print ''
prompt_text = ('Found %s lnk files in %s, would you like to get & parse them?' % (len(lnk_files), directory))
if ((not force) and (not dsz.ui.Prompt(prompt_text, dsz.GOOD))):
continue
for filename in lnk_files:
remote_lnk_info(filename)
def _dir_listing(path='*', mask='*', recursive=True, dirsonly=False):
if ((' ' in path) and ('"' not in path)):
path = (('"' + path) + '"')
cmd = ops.cmd.getDszCommand('dir', path=path, mask=mask, recursive=recursive, dirsonly=dirsonly)
obj = cmd.execute()
if (not cmd.success):
return []
files = []
for dir_item in obj.diritem:
for file_item in dir_item.fileitem:
files.append(os.path.join(dir_item.path, file_item.name))
return files
def _print_lnk_info(lnk_file):
print ('Local or Network: %s' % ('Local Volume' if lnk_file.is_local else 'Network Share'))
print ('Link Target: %s' % lnk_file.target_filename)
print ('Volume Type: %s' % lnk_file.volume_type)
print ('Volume Label: %s' % lnk_file.volume_label)
print ('Volume Serial Number: %s' % lnk_file.volume_serial_number)
print ('Relative Path: %s' % lnk_file.relative_path)
print ('Command Line: %s' % lnk_file.command_line)
print ('Working Directory: %s' % lnk_file.working_directory)
print ('Target Creation Time: %s' % lnk_file.ctime)
print ('Target Mod Time: %s' % lnk_file.mtime)
print ('Target Access Time: %s' % lnk_file.atime)
print ('Description: %s' % lnk_file.description)
print ''
HEADER_LEN = 76
LNK_HEADER = {'unknown1': [0, 4, '<I'], 'GUID': [4, 20, ''], 'flags': [20, 24, '<I'], 'file_attributes': [24, 28, '<I'], 'ctimeL': [28, 32, '<I'], 'ctimeH': [32, 36, '<I'], 'atimeL': [36, 40, '<I'], 'atimeH': [40, 44, '<I'], 'mtimeL': [44, 48, '<I'], 'mtimeH': [48, 52, '<I'], 'file_length': [52, 56, '<I'], 'icon_number': [56, 60, '<I'], 'show_wnd': [60, 64, '<I'], 'hot_key': [64, 68, '<I'], 'unknown2': [68, 76, '']}
NET_VOL_TBL = {'size': [0, 4, '<I', 'Size: '], 'uknown1': [4, 8, '<I', ''], 'net_sharename_offset': [8, 12, '<I', ''], 'unknown2': [12, 16, '<I', ''], 'unknown3': [16, 20, '<I', ''], 'net_sharename': [20, '', '', 'Network Sharename: ']}
LOCAL_VOL_TBL = {'size': [0, 4, '<I'], 'vol_type': [4, 8, '<I'], 'vol_serial_num': [8, 12, '<I'], 'volume_name_offset': [12, 16, '<I'], 'volume_label': [16, '', '']}
FILE_LOC_HEADER_SIZE = 28
FILE_LOC_HEADER = {'size': [0, 4, '<I'], 'first_entry': [4, 8, '<I'], 'flags': [8, 12, '<I'], 'local_vol_info_offset': [12, 16, '<I'], 'local_base_path_offset': [16, 20, '<I'], 'net_vol_info_offset': [20, 24, '<I'], 'remain_pathname_offset': [24, 28, '<I']}
FILE_ATTRIBUTES = ['Read Only', 'Hidden', 'System File', 'Volume Label', 'Directory', 'Modified Since Last Backup', 'Encrypted (NTFS Partitions)', 'Normal', 'Temporary', 'Sparse File', 'Reparse Point Data', 'Compressed', 'Offline']
SHOW_WINDOW_STATES = ['Normal', 'Hidden', 'Minimized', 'Maximized']
VOLUME_TYPE = ['No root directory', 'Removable (Floppy, Zip, etc..)', 'Fixed (Hard Disk)', 'Remote (Network Drive)', 'CD-ROM', 'Ram drive']
def parse_structured_data(raw_data, data_format):
parsed_data = {}
BEGIN = 0
END = 1
CONV = 2
for key in data_format.keys():
key_format = data_format[key]
if (key_format[END] == ''):
key_format[END] = (key_format[BEGIN] + len(raw_data))
txt = raw_data[key_format[BEGIN]:key_format[END]]
if (len(key_format[CONV]) > 0):
parsed_data[key] = struct.unpack(key_format[CONV], txt)[0]
else:
parsed_data[key] = txt
return parsed_data
def process_volume_table(raw_data, info_offset, parse_dict):
txt = raw_data[info_offset:(info_offset + 4)]
size = struct.unpack('<I', txt)[0]
vol_tbl_raw = raw_data[info_offset:(info_offset + size)]
return parse_structured_data(vol_tbl_raw, parse_dict)
def conv_time(low, high):
epoch = 116444736000000000L
if ((low + high) != 0):
new_time = ((((long(high) << 32) + long(low)) - epoch) / 10000000)
else:
new_time = 0
return time.strftime('%Y/%m/%d %H:%M:%S %a', time.localtime(new_time))
class LnkParser:
def __init__(self, file_name=None):
self.is_local = False
self.target_filename = None
self.volume_type = None
self.volume_label = None
self.volume_serial_number = None
self.relative_path = None
self.command_line = None
self.working_directory = None
self.ctime = None
self.mtime = None
self.atime = None
self.description = None
self.icon_filename = None
self.file_attributes = None
self.header = None
self.file_length = None
self.icon_number = None
self.shell_item_id_list = None
self.file_loc = None
self.hot_key = None
self.target_type = None
self.show_window = None
if file_name:
self.parse(file_name)
def parse(self, file_name):
file_handle = open(file_name, 'rb')
header_raw = file_handle.read(HEADER_LEN)
self.header = parse_structured_data(header_raw, LNK_HEADER)
if ((self.header['flags'] & 2) > 0):
self.target_type = 'File or Directory'
else:
self.target_type = 'Unknown'
self.file_attributes = []
for index in range(len(FILE_ATTRIBUTES)):
if ((self.header['file_attributes'] & (2 ** index)) > 0):
self.file_attributes.append(FILE_ATTRIBUTES[index])
self.ctime = conv_time(int(self.header['ctimeL']), int(self.header['ctimeH']))
self.mtime = conv_time(int(self.header['mtimeL']), int(self.header['mtimeH']))
self.atime = conv_time(int(self.header['atimeL']), int(self.header['atimeH']))
self.file_length = self.header['file_length']
self.icon_number = self.header['icon_number']
self.show_window = []
for index in range(len(SHOW_WINDOW_STATES)):
if ((self.header['show_wnd'] & (2 ** index)) > 0):
self.show_window.append(SHOW_WINDOW_STATES[index])
self.hot_key = str(hex(self.header['hot_key']))
self.shell_item_id_list = self._get_val(file_handle, 1, 1)
if ((int(self.header['flags']) & 2) > 0):
txt = file_handle.read(4)
self.file_loc = {}
self.file_loc['size'] = struct.unpack('<I', txt)[0]
file_loc_raw = (txt + file_handle.read((self.file_loc['size'] - 4)))
self.file_loc = parse_structured_data(file_loc_raw, FILE_LOC_HEADER)
offset = self.file_loc['remain_pathname_offset']
remaining_pathname = ' '.join(file_loc_raw[offset:].split('\x00'))
self.file_loc['remaining_pathname'] = remaining_pathname.strip()
if ((self.file_loc['flags'] & 1) > 0):
local_vol_table = process_volume_table(file_loc_raw, self.file_loc['local_vol_info_offset'], LOCAL_VOL_TBL)
self.file_loc['local_vol_table'] = local_vol_table
offset = self.file_loc['local_base_path_offset']
self.file_loc['base_pathname'] = ' '.join(file_loc_raw[offset:].split('\x00'))
self.is_local = True
self.target_filename = self.file_loc['base_pathname']
if self.file_loc['remaining_pathname']:
self.target_filename += '\\'
self.target_filename += self.file_loc['remaining_pathname']
self.volume_type = VOLUME_TYPE[(local_vol_table['vol_type'] - 1)]
self.volume_label = local_vol_table['volume_label']
self.volume_serial_number = str(local_vol_table['vol_serial_num'])
else:
self.file_loc['local_vol_table'] = None
if ((self.file_loc['flags'] & 2) > 0):
net_vol_table = process_volume_table(file_loc_raw, self.file_loc['net_vol_info_offset'], NET_VOL_TBL)
share_names = net_vol_table['net_sharename'].split('\x00')
net_vol_table['net_sharename'] = ('(%s) %s' % (share_names[0], share_names[1]))
self.file_loc['net_vol_table'] = net_vol_table
self.is_local = False
self.volume_type = 'N/A'
self.volume_label = 'N/A'
self.volume_serial_number = 'N/A'
self.target_filename = net_vol_table['net_sharename']
if self.file_loc['remaining_pathname']:
self.target_filename += '\\'
self.target_filename += self.file_loc['remaining_pathname']
else:
self.file_loc['net_vol_table'] = None
self.description = self._get_val(file_handle, 4, 2)
self.relative_path = self._get_val(file_handle, 8, 2)
self.working_directory = self._get_val(file_handle, 16, 2)
self.command_line = self._get_val(file_handle, 32, 2)
self.icon_filename = self._get_val(file_handle, 64, 2)
def _get_val(self, file_handle, mask, size):
if ((int(self.header['flags']) & mask) <= 0):
return
txt = file_handle.read(2)
length = struct.unpack('<H', txt)[0]
length = (length * size)
data = file_handle.read(length)
if (size == 2):
data = data.decode('UTF-16-LE', 'replace')
return data
if (__name__ == '__main__'):
main()