Autopsy  4.19.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
ExtractRegistry.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2012-2021 Basis Technology Corp.
6  *
7  * Copyright 2012 42six Solutions.
8  * Contact: aebadirad <at> 42six <dot> com
9  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 package org.sleuthkit.autopsy.recentactivity;
24 
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileNotFoundException;
29 import java.io.FileReader;
30 import java.io.FileWriter;
31 import java.io.IOException;
32 import java.io.InputStreamReader;
33 import java.io.StringReader;
34 import java.nio.charset.StandardCharsets;
35 import java.text.ParseException;
36 import java.text.SimpleDateFormat;
37 import java.util.logging.Level;
38 import javax.xml.parsers.DocumentBuilder;
39 import javax.xml.parsers.DocumentBuilderFactory;
40 import javax.xml.parsers.ParserConfigurationException;
41 import org.apache.commons.io.FilenameUtils;
42 import org.openide.modules.InstalledFileLocator;
43 import org.openide.util.NbBundle;
51 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
52 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
53 import org.w3c.dom.Document;
54 import org.w3c.dom.Element;
55 import org.w3c.dom.Node;
56 import org.w3c.dom.NodeList;
57 import org.xml.sax.InputSource;
58 import org.xml.sax.SAXException;
59 import java.nio.file.Path;
60 import java.util.AbstractMap;
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.Collection;
64 import java.util.Date;
65 import java.util.HashMap;
66 import java.util.Map;
67 import java.util.Scanner;
68 import java.util.Set;
69 import java.util.HashSet;
70 import static java.util.Locale.US;
71 import java.util.Optional;
72 import static java.util.TimeZone.getTimeZone;
73 import java.util.stream.Collectors;
74 import org.openide.util.Lookup;
79 import org.sleuthkit.datamodel.AbstractFile;
80 import org.sleuthkit.datamodel.Account;
81 import org.sleuthkit.datamodel.Blackboard.BlackboardException;
82 import org.sleuthkit.datamodel.BlackboardArtifact;
83 import org.sleuthkit.datamodel.BlackboardAttribute;
84 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
85 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME;
86 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED;
87 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED;
88 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED;
89 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID;
90 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
91 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
92 import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_HOME_DIR;
93 import org.sleuthkit.datamodel.Content;
94 import org.sleuthkit.datamodel.DataArtifact;
95 import org.sleuthkit.datamodel.DataSource;
96 import org.sleuthkit.datamodel.Host;
97 import org.sleuthkit.datamodel.HostManager;
98 import org.sleuthkit.datamodel.OsAccount;
99 import org.sleuthkit.datamodel.OsAccount.OsAccountAttribute;
100 import org.sleuthkit.datamodel.OsAccountInstance;
101 import org.sleuthkit.datamodel.OsAccountManager;
102 import org.sleuthkit.datamodel.OsAccountManager.NotUserSIDException;
103 import org.sleuthkit.datamodel.OsAccountManager.OsAccountUpdateResult;
104 import org.sleuthkit.datamodel.OsAccountRealm;
105 import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
106 import org.sleuthkit.datamodel.Report;
107 import org.sleuthkit.datamodel.TskCoreException;
108 import org.sleuthkit.datamodel.TskDataException;
109 
116 @NbBundle.Messages({
117  "RegRipperNotFound=Autopsy RegRipper executable not found.",
118  "RegRipperFullNotFound=Full version RegRipper executable not found.",
119  "Progress_Message_Analyze_Registry=Analyzing Registry Files",
120  "Shellbag_Artifact_Display_Name=Shell Bags",
121  "Shellbag_Key_Attribute_Display_Name=Key",
122  "Shellbag_Last_Write_Attribute_Display_Name=Last Write",
123  "Recently_Used_Artifacts_Office_Trustrecords=Stored in TrustRecords because Office security exception was granted",
124  "Recently_Used_Artifacts_ArcHistory=Recently opened by 7Zip",
125  "Recently_Used_Artifacts_Applets=Recently opened according to Applets registry key",
126  "Recently_Used_Artifacts_Mmc=Recently opened according to Windows Management Console MRU",
127  "Recently_Used_Artifacts_Winrar=Recently opened according to WinRAR MRU",
128  "Recently_Used_Artifacts_Officedocs=Recently opened according to Office MRU",
129  "Recently_Used_Artifacts_Adobe=Recently opened according to Adobe MRU",
130  "Recently_Used_Artifacts_Mediaplayer=Recently opened according to Media Player MRU",
131  "Registry_System_Bam=Recently Executed according to Background Activity Moderator (BAM)"
132 })
133 class ExtractRegistry extends Extract {
134 
135  private static final String USERNAME_KEY = "Username"; //NON-NLS
136  private static final String SID_KEY = "SID"; //NON-NLS
137  private static final String RID_KEY = "RID"; //NON-NLS
138  private static final String ACCOUNT_CREATED_KEY = "Account Created"; //NON-NLS
139  private static final String LAST_LOGIN_KEY = "Last Login Date"; //NON-NLS
140  private static final String LOGIN_COUNT_KEY = "Login Count"; //NON-NLS
141  private static final String FULL_NAME_KEY = "Full Name"; //NON-NLS
142  private static final String USER_COMMENT_KEY = "User Comment"; //NON-NLS
143  private static final String ACCOUNT_TYPE_KEY = "Account Type"; //NON-NLS
144  private static final String NAME_KEY = "Name"; //NON-NLS
145  private static final String PWD_RESET_KEY = "Pwd Rest Date"; //NON-NLS
146  private static final String PWD_FAILE_KEY = "Pwd Fail Date"; //NON-NLS
147  private static final String INTERNET_NAME_KEY = "InternetName"; //NON-NLS
148  private static final String PWD_DOES_NOT_EXPIRE_KEY = "Password does not expire"; //NON-NLS
149  private static final String ACCOUNT_DISABLED_KEY = "Account Disabled"; //NON-NLS
150  private static final String PWD_NOT_REQUIRED_KEY = "Password not required"; //NON-NLS
151  private static final String NORMAL_ACCOUNT_KEY = "Normal user account"; //NON-NLS
152  private static final String HOME_DIRECTORY_REQUIRED_KEY = "Home directory required";
153  private static final String TEMPORARY_DUPLICATE_ACCOUNT = "Temporary duplicate account";
154  private static final String MNS_LOGON_ACCOUNT_KEY = "MNS logon user account";
155  private static final String INTERDOMAIN_TRUST_ACCOUNT_KEY = "Interdomain trust account";
156  private static final String WORKSTATION_TRUST_ACCOUNT = "Workstation trust account";
157  private static final String SERVER_TRUST_ACCOUNT = "Server trust account";
158  private static final String ACCOUNT_AUTO_LOCKED = "Account auto locked";
159  private static final String PASSWORD_HINT = "Password Hint";
160 
161  private static final String[] PASSWORD_SETTINGS_FLAGS = {PWD_DOES_NOT_EXPIRE_KEY, PWD_NOT_REQUIRED_KEY};
162  private static final String[] ACCOUNT_SETTINGS_FLAGS = {ACCOUNT_AUTO_LOCKED, HOME_DIRECTORY_REQUIRED_KEY, ACCOUNT_DISABLED_KEY};
163  private static final String[] ACCOUNT_TYPE_FLAGS = {NORMAL_ACCOUNT_KEY, SERVER_TRUST_ACCOUNT, WORKSTATION_TRUST_ACCOUNT, INTERDOMAIN_TRUST_ACCOUNT_KEY, MNS_LOGON_ACCOUNT_KEY, TEMPORARY_DUPLICATE_ACCOUNT};
164 
165  final private static UsbDeviceIdMapper USB_MAPPER = new UsbDeviceIdMapper();
166  final private static String RIP_EXE = "rip.exe";
167  final private static String RIP_PL = "rip.pl";
168  final private static String RIP_PL_INCLUDE_FLAG = "-I";
169  final private static int MS_IN_SEC = 1000;
170  final private static String NEVER_DATE = "Never";
171  final private static String SECTION_DIVIDER = "-------------------------";
172  final private static Logger logger = Logger.getLogger(ExtractRegistry.class.getName());
173  private final List<String> rrCmd = new ArrayList<>();
174  private final List<String> rrFullCmd = new ArrayList<>();
175  private final Path rrHome; // Path to the Autopsy version of RegRipper
176  private final Path rrFullHome; // Path to the full version of RegRipper
177  private Content dataSource;
178  private IngestJobContext context;
179  private Map<String, String> userNameMap;
180 
181  private String compName = "";
182  private String domainName = "";
183 
184  private static final String SHELLBAG_ARTIFACT_NAME = "RA_SHELL_BAG"; //NON-NLS
185  private static final String SHELLBAG_ATTRIBUTE_LAST_WRITE = "RA_SHELL_BAG_LAST_WRITE"; //NON-NLS
186  private static final String SHELLBAG_ATTRIBUTE_KEY = "RA_SHELL_BAG_KEY"; //NON-NLS
187 
188  private static final SimpleDateFormat REG_RIPPER_TIME_FORMAT = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy 'Z'", US);
189 
190  private BlackboardArtifact.Type shellBagArtifactType = null;
191  private BlackboardAttribute.Type shellBagKeyAttributeType = null;
192  private BlackboardAttribute.Type shellBagLastWriteAttributeType = null;
193 
194  static {
195  REG_RIPPER_TIME_FORMAT.setTimeZone(getTimeZone("GMT"));
196  }
197 
198  ExtractRegistry() throws IngestModuleException {
199  super(NbBundle.getMessage(ExtractIE.class, "ExtractRegistry.moduleName.text"));
200 
201  final File rrRoot = InstalledFileLocator.getDefault().locate("rr", ExtractRegistry.class.getPackage().getName(), false); //NON-NLS
202  if (rrRoot == null) {
203  throw new IngestModuleException(Bundle.RegRipperNotFound());
204  }
205 
206  final File rrFullRoot = InstalledFileLocator.getDefault().locate("rr-full", ExtractRegistry.class.getPackage().getName(), false); //NON-NLS
207  if (rrFullRoot == null) {
208  throw new IngestModuleException(Bundle.RegRipperFullNotFound());
209  }
210 
211  String executableToRun = RIP_EXE;
212  if (!PlatformUtil.isWindowsOS()) {
213  executableToRun = RIP_PL;
214  }
215  rrHome = rrRoot.toPath();
216  String rrPath = rrHome.resolve(executableToRun).toString();
217  rrFullHome = rrFullRoot.toPath();
218 
219  if (!(new File(rrPath).exists())) {
220  throw new IngestModuleException(Bundle.RegRipperNotFound());
221  }
222  String rrFullPath = rrFullHome.resolve(executableToRun).toString();
223  if (!(new File(rrFullPath).exists())) {
224  throw new IngestModuleException(Bundle.RegRipperFullNotFound());
225  }
226  if (PlatformUtil.isWindowsOS()) {
227  rrCmd.add(rrPath);
228  rrFullCmd.add(rrFullPath);
229  } else {
230  String perl;
231  File usrBin = new File("/usr/bin/perl");
232  File usrLocalBin = new File("/usr/local/bin/perl");
233  if (usrBin.canExecute() && usrBin.exists() && !usrBin.isDirectory()) {
234  perl = "/usr/bin/perl";
235  } else if (usrLocalBin.canExecute() && usrLocalBin.exists() && !usrLocalBin.isDirectory()) {
236  perl = "/usr/local/bin/perl";
237  } else {
238  throw new IngestModuleException("perl not found in your system");
239  }
240  rrCmd.add(perl);
241  rrCmd.add(RIP_PL_INCLUDE_FLAG);
242  rrCmd.add(rrHome.toString());
243  rrCmd.add(rrPath);
244  rrFullCmd.add(perl);
245  rrFullCmd.add(RIP_PL_INCLUDE_FLAG);
246  rrFullCmd.add(rrFullHome.toString());
247  rrFullCmd.add(rrFullPath);
248  }
249  }
250 
254  private List<AbstractFile> findRegistryFiles() {
255  List<AbstractFile> allRegistryFiles = new ArrayList<>();
256  org.sleuthkit.autopsy.casemodule.services.FileManager fileManager = currentCase.getServices().getFileManager();
257 
258  // find the sam hives', process this first so we can map the user id's and sids for later use
259  try {
260  allRegistryFiles.addAll(fileManager.findFiles(dataSource, "sam", "/system32/config")); //NON-NLS
261  } catch (TskCoreException ex) {
262  String msg = NbBundle.getMessage(this.getClass(),
263  "ExtractRegistry.findRegFiles.errMsg.errReadingFile", "sam");
264  logger.log(Level.WARNING, msg, ex);
265  this.addErrorMessage(this.getName() + ": " + msg);
266  }
267 
268  // find the user-specific ntuser-dat files
269  try {
270  allRegistryFiles.addAll(fileManager.findFiles(dataSource, "ntuser.dat")); //NON-NLS
271  } catch (TskCoreException ex) {
272  logger.log(Level.WARNING, "Error fetching 'ntuser.dat' file."); //NON-NLS
273  }
274 
275  // find the user-specific ntuser-dat files
276  try {
277  allRegistryFiles.addAll(fileManager.findFiles(dataSource, "usrclass.dat")); //NON-NLS
278  } catch (TskCoreException ex) {
279  logger.log(Level.WARNING, String.format("Error finding 'usrclass.dat' files."), ex); //NON-NLS
280  }
281 
282  // find the system hives'
283  String[] regFileNames = new String[]{"system", "software", "security"}; //NON-NLS
284  for (String regFileName : regFileNames) {
285  try {
286  allRegistryFiles.addAll(fileManager.findFiles(dataSource, regFileName, "/system32/config")); //NON-NLS
287  } catch (TskCoreException ex) {
288  String msg = NbBundle.getMessage(this.getClass(),
289  "ExtractRegistry.findRegFiles.errMsg.errReadingFile", regFileName);
290  logger.log(Level.WARNING, msg, ex);
291  this.addErrorMessage(this.getName() + ": " + msg);
292  }
293  }
294  return allRegistryFiles;
295  }
296 
302  private void analyzeRegistryFiles(long ingestJobId) {
303  List<AbstractFile> allRegistryFiles = findRegistryFiles();
304 
305  // open the log file
306  FileWriter logFile = null;
307  try {
308  logFile = new FileWriter(RAImageIngestModule.getRAOutputPath(currentCase, "reg", ingestJobId) + File.separator + "regripper-info.txt"); //NON-NLS
309  } catch (IOException ex) {
310  logger.log(Level.SEVERE, null, ex);
311  }
312 
313  for (AbstractFile regFile : allRegistryFiles) {
314  if (context.dataSourceIngestIsCancelled()) {
315  return;
316  }
317 
318  String regFileName = regFile.getName();
319  long regFileId = regFile.getId();
320  String regFileNameLocal = RAImageIngestModule.getRATempPath(currentCase, "reg", ingestJobId) + File.separator + regFileName;
321  String outputPathBase = RAImageIngestModule.getRAOutputPath(currentCase, "reg", ingestJobId) + File.separator + regFileName + "-regripper-" + Long.toString(regFileId); //NON-NLS
322  File regFileNameLocalFile = new File(regFileNameLocal);
323  try {
324  ContentUtils.writeToFile(regFile, regFileNameLocalFile, context::dataSourceIngestIsCancelled);
325  } catch (ReadContentInputStreamException ex) {
326  logger.log(Level.WARNING, String.format("Error reading registry file '%s' (id=%d).",
327  regFile.getName(), regFileId), ex); //NON-NLS
328  this.addErrorMessage(
329  NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp",
330  this.getName(), regFileName));
331  continue;
332  } catch (IOException ex) {
333  logger.log(Level.SEVERE, String.format("Error writing temp registry file '%s' for registry file '%s' (id=%d).",
334  regFileNameLocal, regFile.getName(), regFileId), ex); //NON-NLS
335  this.addErrorMessage(
336  NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.errMsg.errWritingTemp",
337  this.getName(), regFileName));
338  continue;
339  }
340 
341  if (context.dataSourceIngestIsCancelled()) {
342  break;
343  }
344 
345  try {
346  if (logFile != null) {
347  logFile.write(Long.toString(regFileId) + "\t" + regFile.getUniquePath() + "\n");
348  }
349  } catch (TskCoreException | IOException ex) {
350  logger.log(Level.SEVERE, null, ex);
351  }
352 
353  logger.log(Level.INFO, "{0}- Now getting registry information from {1}", new Object[]{getName(), regFileNameLocal}); //NON-NLS
354  RegOutputFiles regOutputFiles = ripRegistryFile(regFileNameLocal, outputPathBase);
355  if (context.dataSourceIngestIsCancelled()) {
356  break;
357  }
358 
359  // parse the autopsy-specific output
360  if (regOutputFiles.autopsyPlugins.isEmpty() == false && parseAutopsyPluginOutput(regOutputFiles.autopsyPlugins, regFile) == false) {
361  this.addErrorMessage(
362  NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults",
363  this.getName(), regFileName));
364  }
365 
366  if (context.dataSourceIngestIsCancelled()) {
367  return;
368  }
369 
370  // create a report for the full output
371  if (!regOutputFiles.fullPlugins.isEmpty()) {
372  //parse the full regripper output from SAM hive files
373  if (regFileNameLocal.toLowerCase().contains("sam") && parseSamPluginOutput(regOutputFiles.fullPlugins, regFile, ingestJobId) == false) {
374  this.addErrorMessage(
375  NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults",
376  this.getName(), regFileName));
377  } else if (regFileNameLocal.toLowerCase().contains("ntuser") || regFileNameLocal.toLowerCase().contains("usrclass")) {
378  try {
379  List<ShellBag> shellbags = ShellBagParser.parseShellbagOutput(regOutputFiles.fullPlugins);
380  createShellBagArtifacts(regFile, shellbags);
381  createRecentlyUsedArtifacts(regOutputFiles.fullPlugins, regFile);
382  } catch (IOException | TskCoreException ex) {
383  logger.log(Level.WARNING, String.format("Unable to get shell bags from file %s", regOutputFiles.fullPlugins), ex);
384  }
385  } else if (regFileNameLocal.toLowerCase().contains("system") && parseSystemPluginOutput(regOutputFiles.fullPlugins, regFile) == false) {
386  this.addErrorMessage(
387  NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults",
388  this.getName(), regFileName));
389  }
390 
391  if (context.dataSourceIngestIsCancelled()) {
392  return;
393  }
394 
395  try {
396  Report report = currentCase.addReport(regOutputFiles.fullPlugins,
397  NbBundle.getMessage(this.getClass(), "ExtractRegistry.parentModuleName.noSpace"),
398  "RegRipper " + regFile.getUniquePath(), regFile); //NON-NLS
399 
400  // Index the report content so that it will be available for keyword search.
401  KeywordSearchService searchService = Lookup.getDefault().lookup(KeywordSearchService.class);
402  if (null == searchService) {
403  logger.log(Level.WARNING, "Keyword search service not found. Report will not be indexed");
404  } else {
405  searchService.index(report);
406  report.close();
407  }
408  } catch (TskCoreException e) {
409  this.addErrorMessage("Error adding regripper output as Autopsy report: " + e.getLocalizedMessage()); //NON-NLS
410  }
411  }
412  // delete the hive
413  regFileNameLocalFile.delete();
414  }
415 
416  try {
417  if (logFile != null) {
418  logFile.close();
419  }
420  } catch (IOException ex) {
421  logger.log(Level.SEVERE, null, ex);
422  }
423  }
424 
432  private RegOutputFiles ripRegistryFile(String regFilePath, String outFilePathBase) {
433  String autopsyType = ""; // Type argument for rr for autopsy-specific modules
434  String fullType; // Type argument for rr for full set of modules
435 
436  RegOutputFiles regOutputFiles = new RegOutputFiles();
437 
438  if (regFilePath.toLowerCase().contains("system")) { //NON-NLS
439  autopsyType = "autopsysystem"; //NON-NLS
440  fullType = "system"; //NON-NLS
441  } else if (regFilePath.toLowerCase().contains("software")) { //NON-NLS
442  autopsyType = "autopsysoftware"; //NON-NLS
443  fullType = "software"; //NON-NLS
444  } else if (regFilePath.toLowerCase().contains("ntuser")) { //NON-NLS
445  autopsyType = "autopsyntuser"; //NON-NLS
446  fullType = "ntuser"; //NON-NLS
447  } else if (regFilePath.toLowerCase().contains("sam")) { //NON-NLS
448  //fullType sam output files are parsed for user information
449  fullType = "sam"; //NON-NLS
450  } else if (regFilePath.toLowerCase().contains("security")) { //NON-NLS
451  fullType = "security"; //NON-NLS
452  } else if (regFilePath.toLowerCase().contains("usrclass")) { //NON-NLS
453  fullType = "usrclass"; //NON-NLS
454  } else {
455  return regOutputFiles;
456  }
457 
458  // run the autopsy-specific set of modules
459  if (!autopsyType.isEmpty()) {
460  regOutputFiles.autopsyPlugins = outFilePathBase + "-autopsy.txt"; //NON-NLS
461  String errFilePath = outFilePathBase + "-autopsy.err.txt"; //NON-NLS
462  logger.log(Level.INFO, "Writing RegRipper results to: {0}", regOutputFiles.autopsyPlugins); //NON-NLS
463  executeRegRipper(rrCmd, rrHome, regFilePath, autopsyType, regOutputFiles.autopsyPlugins, errFilePath);
464  }
465  if (context.dataSourceIngestIsCancelled()) {
466  return regOutputFiles;
467  }
468 
469  // run the full set of rr modules
470  if (!fullType.isEmpty()) {
471  regOutputFiles.fullPlugins = outFilePathBase + "-full.txt"; //NON-NLS
472  String errFilePath = outFilePathBase + "-full.err.txt"; //NON-NLS
473  logger.log(Level.INFO, "Writing Full RegRipper results to: {0}", regOutputFiles.fullPlugins); //NON-NLS
474  executeRegRipper(rrFullCmd, rrFullHome, regFilePath, fullType, regOutputFiles.fullPlugins, errFilePath);
475  try {
476  scanErrorLogs(errFilePath);
477  } catch (IOException ex) {
478  logger.log(Level.SEVERE, String.format("Unable to run RegRipper on %s", regFilePath), ex); //NON-NLS
479  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile", this.getName(), regFilePath));
480  }
481  }
482  return regOutputFiles;
483  }
484 
485  private void scanErrorLogs(String errFilePath) throws IOException {
486  File regfile = new File(errFilePath);
487  try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
488  String line = reader.readLine();
489  while (line != null) {
490  line = line.trim();
491  if (line.toLowerCase().contains("error") || line.toLowerCase().contains("@inc")) {
492  logger.log(Level.WARNING, "Regripper file {0} contains errors from run", errFilePath); //NON-NLS
493 
494  }
495  line = reader.readLine();
496  }
497  }
498  }
499 
500  private void executeRegRipper(List<String> regRipperPath, Path regRipperHomeDir, String hiveFilePath, String hiveFileType, String outputFile, String errFile) {
501  try {
502  List<String> commandLine = new ArrayList<>();
503  for (String cmd : regRipperPath) {
504  commandLine.add(cmd);
505  }
506  commandLine.add("-r"); //NON-NLS
507  commandLine.add(hiveFilePath);
508  commandLine.add("-f"); //NON-NLS
509  commandLine.add(hiveFileType);
510 
511  ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
512  processBuilder.directory(regRipperHomeDir.toFile()); // RegRipper 2.8 has to be run from its own directory
513  processBuilder.redirectOutput(new File(outputFile));
514  processBuilder.redirectError(new File(errFile));
515  ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context, true));
516  } catch (IOException ex) {
517  logger.log(Level.SEVERE, String.format("Error running RegRipper on %s", hiveFilePath), ex); //NON-NLS
518  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "ExtractRegistry.execRegRip.errMsg.failedAnalyzeRegFile", this.getName(), hiveFilePath));
519  }
520  }
521 
522  // @@@ VERIFY that we are doing the right thing when we parse multiple NTUSER.DAT
531  private boolean parseAutopsyPluginOutput(String regFilePath, AbstractFile regFile) {
532  FileInputStream fstream = null;
533  List<BlackboardArtifact> newArtifacts = new ArrayList<>();
534  try {
535  // Read the file in and create a Document and elements
536  File regfile = new File(regFilePath);
537  fstream = new FileInputStream(regfile);
538  String regString = new Scanner(fstream, "UTF-8").useDelimiter("\\Z").next(); //NON-NLS
539  String startdoc = "<?xml version=\"1.0\"?><document>"; //NON-NLS
540  String result = regString.replaceAll("----------------------------------------", "");
541  result = result.replaceAll("\\n", ""); //NON-NLS
542  result = result.replaceAll("\\r", ""); //NON-NLS
543  result = result.replaceAll("'", "&apos;"); //NON-NLS
544  result = result.replaceAll("&", "&amp;"); //NON-NLS
545  result = result.replace('\0', ' '); // NON-NLS
546  String enddoc = "</document>"; //NON-NLS
547  String stringdoc = startdoc + result + enddoc;
548  DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
549  Document doc = builder.parse(new InputSource(new StringReader(stringdoc)));
550 
551  // cycle through the elements in the doc
552  Element oroot = doc.getDocumentElement();
553  NodeList children = oroot.getChildNodes();
554  int len = children.getLength();
555  for (int i = 0; i < len; i++) {
556 
557  if (context.dataSourceIngestIsCancelled()) {
558  return false;
559  }
560 
561  Element tempnode = (Element) children.item(i);
562 
563  String dataType = tempnode.getNodeName();
564  NodeList timenodes = tempnode.getElementsByTagName("mtime"); //NON-NLS
565  Long mtime = null;
566  if (timenodes.getLength() > 0) {
567  Element timenode = (Element) timenodes.item(0);
568  String etime = timenode.getTextContent();
569  //sometimes etime will be an empty string and therefore can not be parsed into a date
570  if (etime != null && !etime.isEmpty()) {
571  try {
572  mtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US).parse(etime).getTime();
573  String Tempdate = mtime.toString();
574  mtime = Long.valueOf(Tempdate) / MS_IN_SEC;
575  } catch (ParseException ex) {
576  logger.log(Level.WARNING, "Failed to parse epoch time when parsing the registry.", ex); //NON-NLS
577  }
578  }
579  }
580 
581  NodeList artroots = tempnode.getElementsByTagName("artifacts"); //NON-NLS
582  if (artroots.getLength() == 0) {
583  // If there isn't an artifact node, skip this entry
584  continue;
585  }
586 
587  Element artroot = (Element) artroots.item(0);
588  NodeList myartlist = artroot.getChildNodes();
589  String parentModuleName = RecentActivityExtracterModuleFactory.getModuleName();
590 
591  // If all artifact nodes should really go under one Blackboard artifact, need to process it differently
592  switch (dataType) {
593  case "WinVersion": //NON-NLS
594  String version = "";
595  String systemRoot = "";
596  String productId = "";
597  String regOwner = "";
598  String regOrg = "";
599  Long installtime = null;
600  for (int j = 0; j < myartlist.getLength(); j++) {
601  Node artchild = myartlist.item(j);
602  // If it has attributes, then it is an Element (based off API)
603  if (artchild.hasAttributes()) {
604  Element artnode = (Element) artchild;
605 
606  String value = artnode.getTextContent();
607  if (value != null) {
608  value = value.trim();
609  }
610  String name = artnode.getAttribute("name"); //NON-NLS
611  if (name == null) {
612  continue;
613  }
614  switch (name) {
615  case "ProductName": // NON-NLS
616  version = value;
617  break;
618  case "CSDVersion": // NON-NLS
619  // This is dependant on the fact that ProductName shows up first in the module output
620  version = version + " " + value;
621  break;
622  case "SystemRoot": //NON-NLS
623  systemRoot = value;
624  break;
625  case "ProductId": //NON-NLS
626  productId = value;
627  break;
628  case "RegisteredOwner": //NON-NLS
629  regOwner = value;
630  break;
631  case "RegisteredOrganization": //NON-NLS
632  regOrg = value;
633  break;
634  case "InstallDate": //NON-NLS
635  if (value != null && !value.isEmpty()) {
636  try {
637  installtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyyZ", US).parse(value+"+0000").getTime();
638  String Tempdate = installtime.toString();
639  installtime = Long.valueOf(Tempdate) / MS_IN_SEC;
640  } catch (ParseException e) {
641  logger.log(Level.WARNING, "RegRipper::Conversion on DateTime -> ", e); //NON-NLS
642  }
643  }
644  break;
645  default:
646  break;
647  }
648  }
649  }
650  try {
651  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
652  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, parentModuleName, version));
653  if (installtime != null) {
654  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, parentModuleName, installtime));
655  }
656  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH, parentModuleName, systemRoot));
657  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PRODUCT_ID, parentModuleName, productId));
658  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_OWNER, parentModuleName, regOwner));
659  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_ORGANIZATION, parentModuleName, regOrg));
660 
661  // Check if there is already an OS_INFO artifact for this file, and add to that if possible.
662  ArrayList<BlackboardArtifact> results = tskCase.getBlackboardArtifacts(ARTIFACT_TYPE.TSK_OS_INFO, regFile.getId());
663  if (results.isEmpty()) {
664  newArtifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_OS_INFO, regFile, bbattributes));
665  } else {
666  results.get(0).addAttributes(bbattributes);
667  }
668 
669  } catch (TskCoreException ex) {
670  logger.log(Level.SEVERE, String.format("Error adding installed program artifact to blackboard for file %d.", regFile.getId()), ex); //NON-NLS
671  }
672  break;
673  case "Profiler": // NON-NLS
674  String os = "";
675  String procArch = "";
676  String tempDir = "";
677  for (int j = 0; j < myartlist.getLength(); j++) {
678  Node artchild = myartlist.item(j);
679  // If it has attributes, then it is an Element (based off API)
680  if (artchild.hasAttributes()) {
681  Element artnode = (Element) artchild;
682 
683  String value = artnode.getTextContent().trim();
684  String name = artnode.getAttribute("name"); //NON-NLS
685  switch (name) {
686  case "OS": // NON-NLS
687  os = value;
688  break;
689  case "PROCESSOR_ARCHITECTURE": // NON-NLS
690  procArch = value;
691  break;
692  case "PROCESSOR_IDENTIFIER": //NON-NLS
693  break;
694  case "TEMP": //NON-NLS
695  tempDir = value;
696  break;
697  default:
698  break;
699  }
700  }
701  }
702  try {
703  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
704  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VERSION, parentModuleName, os));
705  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE, parentModuleName, procArch));
706  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TEMP_DIR, parentModuleName, tempDir));
707 
708  // Check if there is already an OS_INFO artifact for this file and add to that if possible
709  ArrayList<BlackboardArtifact> results = tskCase.getBlackboardArtifacts(ARTIFACT_TYPE.TSK_OS_INFO, regFile.getId());
710  if (results.isEmpty()) {
711  newArtifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_OS_INFO, regFile, bbattributes));
712  } else {
713  results.get(0).addAttributes(bbattributes);
714  }
715  } catch (TskCoreException ex) {
716  logger.log(Level.SEVERE, String.format("Error adding installed os_info to blackboard for file %d.", regFile.getId()), ex); //NON-NLS
717  }
718  break;
719  case "CompName": // NON-NLS
720  for (int j = 0; j < myartlist.getLength(); j++) {
721  Node artchild = myartlist.item(j);
722  // If it has attributes, then it is an Element (based off API)
723  if (artchild.hasAttributes()) {
724  Element artnode = (Element) artchild;
725 
726  String value = artnode.getTextContent().trim();
727  String name = artnode.getAttribute("name"); //NON-NLS
728 
729  if (name.equals("ComputerName")) { // NON-NLS
730  compName = value;
731  } else if (name.equals("Domain")) { // NON-NLS
732  domainName = value;
733  }
734  }
735  }
736  try {
737  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
738  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, parentModuleName, compName));
739  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, parentModuleName, domainName));
740 
741  // Check if there is already an OS_INFO artifact for this file and add to that if possible
742  ArrayList<BlackboardArtifact> results = tskCase.getBlackboardArtifacts(ARTIFACT_TYPE.TSK_OS_INFO, regFile.getId());
743  if (results.isEmpty()) {
744  newArtifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_OS_INFO, regFile, bbattributes));
745  } else {
746  results.get(0).addAttributes(bbattributes);
747  }
748  for (Map.Entry<String, String> userMap : getUserNameMap().entrySet()) {
749  String sid = "";
750  try{
751  sid = userMap.getKey();
752  String userName = userMap.getValue();
753  createOrUpdateOsAccount(regFile, sid, userName, null);
754  } catch(TskCoreException | TskDataException | NotUserSIDException ex) {
755  logger.log(Level.WARNING, String.format("Failed to update Domain for existing OsAccount: %s, sid: %s", regFile.getId(), sid), ex);
756  }
757  }
758  } catch (TskCoreException ex) {
759  logger.log(Level.SEVERE, String.format("Error adding os_info artifact to blackboard for file %d.", regFile.getId()), ex); //NON-NLS
760  }
761  break;
762  default:
763  for (int j = 0; j < myartlist.getLength(); j++) {
764  Node artchild = myartlist.item(j);
765  // If it has attributes, then it is an Element (based off API)
766  if (artchild.hasAttributes()) {
767  Element artnode = (Element) artchild;
768 
769  String value = artnode.getTextContent().trim();
770  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
771 
772  switch (dataType) {
773  case "recentdocs": //NON-NLS
774  // BlackboardArtifact bbart = tskCase.getContentById(orgId).newArtifact(ARTIFACT_TYPE.TSK_RECENT_OBJECT);
775  // bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "RecentActivity", dataType, mtime));
776  // bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME.getTypeID(), "RecentActivity", dataType, mtimeItem));
777  // bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE.getTypeID(), "RecentActivity", dataType, value));
778  // bbart.addAttributes(bbattributes);
779  // @@@ BC: Why are we ignoring this...
780  break;
781  case "usb": //NON-NLS
782  try {
783  Long usbMtime = Long.parseLong(artnode.getAttribute("mtime")); //NON-NLS
784  usbMtime = Long.valueOf(usbMtime.toString());
785  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, parentModuleName, usbMtime));
786  String dev = artnode.getAttribute("dev"); //NON-NLS
787  String make = "";
788  String model = dev;
789  if (dev.toLowerCase().contains("vid")) { //NON-NLS
790  USBInfo info = USB_MAPPER.parseAndLookup(dev);
791  if (info.getVendor() != null) {
792  make = info.getVendor();
793  }
794  if (info.getProduct() != null) {
795  model = info.getProduct();
796  }
797  }
798  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE, parentModuleName, make));
799  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL, parentModuleName, model));
800  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_ID, parentModuleName, value));
801  newArtifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_DEVICE_ATTACHED, regFile, bbattributes));
802  } catch (TskCoreException ex) {
803  logger.log(Level.SEVERE, String.format("Error adding device_attached artifact to blackboard for file %d.", regFile.getId()), ex); //NON-NLS
804  }
805  break;
806  case "uninstall": //NON-NLS
807  Long itemMtime = null;
808  try {
809  String mTimeAttr = artnode.getAttribute("mtime");
810  if (mTimeAttr != null && !mTimeAttr.isEmpty()) {
811  itemMtime = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US).parse(mTimeAttr).getTime(); //NON-NLS
812  itemMtime /= MS_IN_SEC;
813  }
814  } catch (ParseException ex) {
815  logger.log(Level.SEVERE, "Failed to parse epoch time for installed program artifact.", ex); //NON-NLS
816  }
817 
818  try {
819  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, parentModuleName, value));
820  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, parentModuleName, itemMtime));
821  BlackboardArtifact bbart = regFile.newDataArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_INSTALLED_PROG), bbattributes);
822  newArtifacts.add(bbart);
823  } catch (TskCoreException ex) {
824  logger.log(Level.SEVERE, "Error adding installed program artifact to blackboard.", ex); //NON-NLS
825  }
826  break;
827  case "office": //NON-NLS
828  String officeName = artnode.getAttribute("name"); //NON-NLS
829 
830  try {
831  // @@@ BC: Consider removing this after some more testing. It looks like an Mtime associated with the root key and not the individual item
832  if (mtime != null) {
833  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, parentModuleName, mtime));
834  }
835  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME, parentModuleName, officeName));
836  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE, parentModuleName, value));
837  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, parentModuleName, artnode.getNodeName()));
838  BlackboardArtifact bbart = regFile.newDataArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_RECENT_OBJECT), bbattributes);
839 
840  newArtifacts.add(bbart);
841  } catch (TskCoreException ex) {
842  logger.log(Level.SEVERE, "Error adding recent object artifact to blackboard.", ex); //NON-NLS
843  }
844  break;
845 
846  case "ProcessorArchitecture": //NON-NLS
847  // Architecture is now included under Profiler
848  //try {
849  // String processorArchitecture = value;
850  // if (processorArchitecture.equals("AMD64"))
851  // processorArchitecture = "x86-64";
852 
853  // BlackboardArtifact bbart = regFile.newArtifact(ARTIFACT_TYPE.TSK_OS_INFO);
854  // bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROCESSOR_ARCHITECTURE.getTypeID(), parentModuleName, processorArchitecture));
855  // bbart.addAttributes(bbattributes);
856  //} catch (TskCoreException ex) {
857  // logger.log(Level.SEVERE, "Error adding os info artifact to blackboard."); //NON-NLS
858  //}
859  break;
860 
861  case "ProfileList": //NON-NLS
862  String homeDir = value;
863  String sid = artnode.getAttribute("sid"); //NON-NLS
864  String username = artnode.getAttribute("username"); //NON-NLS
865 
866  try{
867  createOrUpdateOsAccount(regFile, sid, username, homeDir);
868  } catch(TskCoreException | TskDataException | NotUserSIDException ex) {
869  logger.log(Level.SEVERE, String.format("Failed to create OsAccount for file: %s, sid: %s", regFile.getId(), sid), ex);
870  }
871  break;
872 
873  case "NtuserNetwork": // NON-NLS
874  try {
875  String localPath = artnode.getAttribute("localPath"); //NON-NLS
876  String remoteName = value;
877 
878  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCAL_PATH,
879  parentModuleName, localPath));
880  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REMOTE_PATH,
881  parentModuleName, remoteName));
882  BlackboardArtifact bbart = regFile.newDataArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_REMOTE_DRIVE), bbattributes);
883  newArtifacts.add(bbart);
884  } catch (TskCoreException ex) {
885  logger.log(Level.SEVERE, "Error adding network artifact to blackboard.", ex); //NON-NLS
886  }
887  break;
888  case "SSID": // NON-NLS
889  String adapter = artnode.getAttribute("adapter"); //NON-NLS
890  try {
891  Long lastWriteTime = Long.parseLong(artnode.getAttribute("writeTime")); //NON-NLS
892  lastWriteTime = Long.valueOf(lastWriteTime.toString());
893  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_SSID, parentModuleName, value));
894  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, parentModuleName, lastWriteTime));
895  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_ID, parentModuleName, adapter));
896  BlackboardArtifact bbart = regFile.newDataArtifact(new BlackboardArtifact.Type(ARTIFACT_TYPE.TSK_WIFI_NETWORK), bbattributes);
897  newArtifacts.add(bbart);
898  } catch (TskCoreException ex) {
899  logger.log(Level.SEVERE, "Error adding SSID artifact to blackboard.", ex); //NON-NLS
900  }
901  break;
902  case "shellfolders": // NON-NLS
903  // The User Shell Folders subkey stores the paths to Windows Explorer folders for the current user of the computer
904  // (https://technet.microsoft.com/en-us/library/Cc962613.aspx).
905  // No useful information. Skip.
906  break;
907 
908  default:
909  logger.log(Level.SEVERE, "Unrecognized node name: {0}", dataType); //NON-NLS
910  break;
911  }
912  }
913  }
914  break;
915  }
916  } // for
917  return true;
918  } catch (FileNotFoundException ex) {
919  logger.log(Level.WARNING, String.format("Error finding the registry file: %s", regFilePath), ex); //NON-NLS
920  } catch (SAXException ex) {
921  logger.log(Level.WARNING, String.format("Error parsing the registry XML: %s", regFilePath), ex); //NON-NLS
922  } catch (IOException ex) {
923  logger.log(Level.WARNING, String.format("Error building the document parser: %s", regFilePath), ex); //NON-NLS
924  } catch (ParserConfigurationException ex) {
925  logger.log(Level.WARNING, String.format("Error configuring the registry parser: %s", regFilePath), ex); //NON-NLS
926  } finally {
927  try {
928  if (fstream != null) {
929  fstream.close();
930  }
931  } catch (IOException ex) {
932  }
933 
934  if (!context.dataSourceIngestIsCancelled()) {
935  postArtifacts(newArtifacts);
936  }
937  }
938  return false;
939  }
940 
941  private boolean parseSystemPluginOutput(String regfilePath, AbstractFile regAbstractFile) {
942  File regfile = new File(regfilePath);
943  try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
944  String line = reader.readLine();
945  while (line != null) {
946  line = line.trim();
947 
948  if (line.toLowerCase().matches("^bam v.*")) {
949  parseBamKey(regAbstractFile, reader, Bundle.Registry_System_Bam());
950  } else if (line.toLowerCase().matches("^bthport v..*")) {
951  parseBlueToothDevices(regAbstractFile, reader);
952  }
953  line = reader.readLine();
954  }
955  return true;
956  } catch (FileNotFoundException ex) {
957  logger.log(Level.WARNING, "Error finding the registry file.", ex); //NON-NLS
958  } catch (IOException ex) {
959  logger.log(Level.WARNING, "Error reading the system hive: {0}", ex); //NON-NLS
960  }
961 
962  return false;
963 
964  }
965 
978  private void parseBlueToothDevices(AbstractFile regFile, BufferedReader reader) throws FileNotFoundException, IOException {
979  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
980  String line = reader.readLine();
981  while ((line != null) && (!line.contains(SECTION_DIVIDER))) {
982  line = reader.readLine();
983 
984  if (line != null) {
985  line = line.trim();
986  }
987 
988  if ((line != null) && (line.toLowerCase().contains("device unique id"))) {
989  // Columns are seperated by colons :
990  // Data : Values
991  // Record is 4 lines in length (Device Unique Id, Name, Last Seen, LastConnected
992  while (line != null && !line.contains(SECTION_DIVIDER) && !line.isEmpty() && !line.toLowerCase().contains("radio support not found")) {
993  Collection<BlackboardAttribute> attributes = new ArrayList<>();
994  addBlueToothAttribute(line, attributes, TSK_DEVICE_ID);
995  line = reader.readLine();
996  // Name may not exist, check for it to make sure.
997  if ((line != null) && (line.toLowerCase().contains("name"))) {
998  addBlueToothAttribute(line, attributes, TSK_NAME);
999  line = reader.readLine();
1000  }
1001  addBlueToothAttribute(line, attributes, TSK_DATETIME);
1002  line = reader.readLine();
1003  addBlueToothAttribute(line, attributes, TSK_DATETIME_ACCESSED);
1004 
1005  try {
1006  bbartifacts.add(createArtifactWithAttributes(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING, regFile, attributes));
1007  } catch (TskCoreException ex) {
1008  logger.log(Level.SEVERE, String.format("Failed to create bluetooth_pairing artifact for file %d", regFile.getId()), ex);
1009  }
1010  // Read blank line between records then next read line is start of next block
1011  reader.readLine();
1012  line = reader.readLine();
1013  }
1014 
1015  if (line != null) {
1016  line = line.trim();
1017  }
1018  }
1019  }
1020 
1021  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1022  postArtifacts(bbartifacts);
1023  }
1024  }
1025 
1026  private void addBlueToothAttribute(String line, Collection<BlackboardAttribute> attributes, ATTRIBUTE_TYPE attributeType) {
1027  if (line == null) {
1028  return;
1029  }
1030 
1031  String tokens[] = line.split(": ");
1032  if (tokens.length > 1 && !tokens[1].isEmpty()) {
1033  String tokenString = tokens[1];
1034  if (attributeType.getDisplayName().toLowerCase().contains("date")) {
1035  String dateString = tokenString.toLowerCase().replace(" z", "");
1036  // date format for plugin Tue Jun 23 10:27:54 2020 Z
1037  SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US);
1038  Long dateLong = Long.valueOf(0);
1039  try {
1040  Date newDate = dateFormat.parse(dateString);
1041  dateLong = newDate.getTime() / 1000;
1042  } catch (ParseException ex) {
1043  // catching error and displaying date that could not be parsed
1044  // we set the timestamp to 0 and continue on processing
1045  logger.log(Level.WARNING, String.format("Failed to parse date/time %s for Bluetooth Last Seen attribute.", dateString), ex); //NON-NLS
1046  }
1047  attributes.add(new BlackboardAttribute(attributeType, getName(), dateLong));
1048  } else {
1049  attributes.add(new BlackboardAttribute(attributeType, getName(), tokenString));
1050  }
1051  }
1052  }
1053 
1064  private boolean parseSamPluginOutput(String regFilePath, AbstractFile regAbstractFile, long ingestJobId) {
1065 
1066  File regfile = new File(regFilePath);
1067  List<BlackboardArtifact> newArtifacts = new ArrayList<>();
1068  try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(regfile), StandardCharsets.UTF_8))) {
1069  // Read the file in and create a Document and elements
1070  String userInfoSection = "User Information";
1071  String previousLine = null;
1072  String line = bufferedReader.readLine();
1073  Set<Map<String, String>> userSet = new HashSet<>();
1074  Map<String, List<String>> groupMap = null;
1075  while (line != null) {
1076  if (line.contains(SECTION_DIVIDER) && previousLine != null && previousLine.contains(userInfoSection)) {
1077  readUsers(bufferedReader, userSet);
1078  }
1079 
1080  if (line.contains(SECTION_DIVIDER) && previousLine != null && previousLine.contains("Group Membership Information")) {
1081  groupMap = readGroups(bufferedReader);
1082  }
1083 
1084  previousLine = line;
1085  line = bufferedReader.readLine();
1086  }
1087  Map<String, Map<String, String>> userInfoMap = new HashMap<>();
1088  //load all the user info which was read into a map
1089  for (Map<String, String> userInfo : userSet) {
1090  userInfoMap.put(userInfo.get(SID_KEY), userInfo);
1091  }
1092 
1093  // New OsAccount Code
1094  OsAccountManager accountMgr = tskCase.getOsAccountManager();
1095  HostManager hostMrg = tskCase.getHostManager();
1096  Host host = hostMrg.getHostByDataSource((DataSource)dataSource);
1097 
1098  List<OsAccount> existingAccounts = accountMgr.getOsAccounts(host);
1099  for(OsAccount osAccount: existingAccounts) {
1100  Optional<String> optional = osAccount.getAddr();
1101  if(!optional.isPresent()) {
1102  continue;
1103  }
1104 
1105  String sid = optional.get();
1106  Map<String, String> userInfo = userInfoMap.remove(sid);
1107  if(userInfo != null) {
1108  updateOsAccount(osAccount, userInfo, groupMap.get(sid), regAbstractFile);
1109  }
1110  }
1111 
1112  //add remaining userinfos as accounts;
1113  for (Map<String, String> userInfo : userInfoMap.values()) {
1114  OsAccount osAccount = accountMgr.newWindowsOsAccount(userInfo.get(SID_KEY), null, domainName, host, domainName != null && !domainName.isEmpty() ? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN);
1115  accountMgr.newOsAccountInstance(osAccount, (DataSource)dataSource, OsAccountInstance.OsAccountInstanceType.LAUNCHED);
1116  updateOsAccount(osAccount, userInfo, groupMap.get(userInfo.get(SID_KEY)), regAbstractFile);
1117  }
1118  return true;
1119  } catch (FileNotFoundException ex) {
1120  logger.log(Level.WARNING, "Error finding the registry file.", ex); //NON-NLS
1121  } catch (IOException ex) {
1122  logger.log(Level.WARNING, "Error building the document parser: {0}", ex); //NON-NLS
1123  } catch (TskDataException | TskCoreException ex) {
1124  logger.log(Level.WARNING, "Error updating TSK_OS_ACCOUNT artifacts to include newly parsed data.", ex); //NON-NLS
1125  } catch (OsAccountManager.NotUserSIDException ex) {
1126  logger.log(Level.WARNING, "Error creating OS Account, input SID is not a user SID.", ex); //NON-NLS
1127  }
1128  finally {
1129  if (!context.dataSourceIngestIsCancelled()) {
1130  postArtifacts(newArtifacts);
1131  }
1132  }
1133  return false;
1134  }
1135 
1147  private void readUsers(BufferedReader bufferedReader, Set<Map<String, String>> users) throws IOException {
1148  String line = bufferedReader.readLine();
1149  //read until end of file or next section divider
1150  String userName = "";
1151  String user_rid = "";
1152  while (line != null && !line.contains(SECTION_DIVIDER)) {
1153  //when a user name field exists read the name and id number
1154  if (line.contains(USERNAME_KEY)) {
1155  String regx = USERNAME_KEY + "\\s*?:";
1156  String userNameAndIdString = line.replaceAll(regx, "");
1157  userName = userNameAndIdString.substring(0, userNameAndIdString.lastIndexOf('[')).trim();
1158  user_rid = userNameAndIdString.substring(userNameAndIdString.lastIndexOf('['), userNameAndIdString.lastIndexOf(']'));
1159  } else if (line.contains(SID_KEY) && !userName.isEmpty()) {
1160  Map.Entry<String, String> entry = getSAMKeyValue(line);
1161 
1162  HashMap<String, String> userInfo = new HashMap<>();
1163  userInfo.put(USERNAME_KEY, userName);
1164  userInfo.put(RID_KEY, user_rid);
1165  userInfo.put(entry.getKey(), entry.getValue());
1166 
1167  //continue reading this users information until end of file or a blank line between users
1168  line = bufferedReader.readLine();
1169  while (line != null && !line.isEmpty()) {
1170  entry = getSAMKeyValue(line);
1171  if (entry != null) {
1172  userInfo.put(entry.getKey(), entry.getValue());
1173  }
1174  line = bufferedReader.readLine();
1175  }
1176  users.add(userInfo);
1177 
1178  userName = "";
1179  }
1180  line = bufferedReader.readLine();
1181  }
1182  }
1183 
1193  private void createRecentlyUsedArtifacts(String regFileName, AbstractFile regFile) throws FileNotFoundException, IOException {
1194  File regfile = new File(regFileName);
1195  try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
1196  String line = reader.readLine();
1197  while (line != null) {
1198  line = line.trim();
1199 
1200  if (line.matches("^adoberdr v.*")) {
1201  parseAdobeMRUList(regFile, reader, Bundle.Recently_Used_Artifacts_Adobe());
1202  } else if (line.matches("^mpmru v.*")) {
1203  parseMediaPlayerMRUList(regFile, reader, Bundle.Recently_Used_Artifacts_Mediaplayer());
1204  } else if (line.matches("^trustrecords v.*")) {
1205  parseOfficeTrustRecords(regFile, reader, Bundle.Recently_Used_Artifacts_Office_Trustrecords());
1206  } else if (line.matches("^ArcHistory:")) {
1207  parse7ZipMRU(regFile, reader, Bundle.Recently_Used_Artifacts_ArcHistory());
1208  } else if (line.matches("^applets v.*")) {
1209  parseGenericMRUList(regFile, reader, Bundle.Recently_Used_Artifacts_Applets());
1210  } else if (line.matches("^mmc v.*")) {
1211  parseGenericMRUList(regFile, reader, Bundle.Recently_Used_Artifacts_Mmc());
1212  } else if (line.matches("^winrar v.*")) {
1213  parseWinRARMRUList(regFile, reader, Bundle.Recently_Used_Artifacts_Winrar());
1214  } else if (line.matches("^officedocs2010 v.*")) {
1215  parseOfficeDocs2010MRUList(regFile, reader, Bundle.Recently_Used_Artifacts_Officedocs());
1216  }
1217  line = reader.readLine();
1218  }
1219  }
1220  }
1221 
1233  private void parseBamKey(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1234  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1235  String line = reader.readLine();
1236  // Read thru first bam output to get to second bam output which is the same but delimited
1237  while (!line.contains(SECTION_DIVIDER)) {
1238  line = reader.readLine();
1239  line = line.trim();
1240  }
1241  line = reader.readLine();
1242  line = line.trim();
1243  while (!line.contains(SECTION_DIVIDER)) {
1244  // Split the line into it parts based on delimiter of "|"
1245  // 1570493613|BAM|||\Device\HarddiskVolume3\Program Files\TechSmith\Snagit 2018\Snagit32.exe (S-1-5-21-3042408413-2583535980-1301764466-1001)
1246  String tokens[] = line.split("\\|");
1247  Long progRunDateTime = Long.valueOf(tokens[0]);
1248  // Split on " (S-" as this signifies a User SID, if S- not used then may have issues becuase of (x86) in path is valid.
1249  // We can add the S- back to the string that we split on since S- is a valid beginning of a User SID
1250  String fileNameSid[] = tokens[4].split("\\s+\\(S-");
1251  String userSid = "S-" + fileNameSid[1].substring(0, fileNameSid[1].length() - 1);
1252  String userName = getUserNameMap().get(userSid);
1253  if (userName == null) {
1254  userName = userSid;
1255  }
1256  String fileName = fileNameSid[0];
1257  if (fileName.startsWith("\\Device\\HarddiskVolume")) {
1258  // Start at point past the 2nd slash
1259  int fileNameStart = fileName.indexOf('\\', 16);
1260  fileName = fileName.substring(fileNameStart, fileName.length());
1261 
1262  }
1263  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1264  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, getName(), fileName));
1265  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME, getName(), userName));
1266  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME, getName(), progRunDateTime));
1267  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, getName(), comment));
1268 
1269  try {
1270  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_PROG_RUN, regFile, attributes);
1271  bbartifacts.add(bba);
1272  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1273  if (bba != null) {
1274  bbartifacts.add(bba);
1275  }
1276  } catch (TskCoreException ex) {
1277  logger.log(Level.SEVERE, String.format("Failed to create TSK_PROG_RUN artifact for file %d", regFile.getId()), ex);
1278  }
1279  line = reader.readLine();
1280  }
1281  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1282  postArtifacts(bbartifacts);
1283  }
1284  }
1285 
1297  private void parseAdobeMRUList(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1298  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1299  String line = reader.readLine();
1300  SimpleDateFormat adobePluginDateFormat = new SimpleDateFormat("yyyyMMddHHmmssZ", US);
1301  Long adobeUsedTime = Long.valueOf(0);
1302  while (!line.contains(SECTION_DIVIDER)) {
1303  line = reader.readLine();
1304  line = line.trim();
1305  if (line.matches("^Key name,file name,sDate,uFileSize,uPageCount")) {
1306  line = reader.readLine();
1307  // Columns are
1308  // Key name, file name, sDate, uFileSize, uPageCount
1309  while (!line.contains(SECTION_DIVIDER)) {
1310  // Split csv line, handles double quotes around individual file names
1311  // since file names can contain commas
1312  String tokens[] = line.split(",(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
1313  String fileName = tokens[1].substring(0, tokens[1].length() - 1);
1314  fileName = fileName.replace("\"", "");
1315  if (fileName.charAt(0) == '/') {
1316  fileName = fileName.substring(1, fileName.length() - 1);
1317  fileName = fileName.replaceFirst("/", ":/");
1318  }
1319  // Check to see if more then 2 tokens, Date may not be populated, will default to 0
1320  if (tokens.length > 2) {
1321  // Time in the format of 20200131104456-05'00'
1322  try {
1323  String fileUsedTime = tokens[2].replaceAll("'", "");
1324  Date usedDate = adobePluginDateFormat.parse(fileUsedTime);
1325  adobeUsedTime = usedDate.getTime() / 1000;
1326  } catch (ParseException ex) {
1327  // catching error and displaying date that could not be parsed
1328  // we set the timestamp to 0 and continue on processing
1329  logger.log(Level.WARNING, String.format("Failed to parse date/time %s for adobe file artifact.", tokens[2]), ex); //NON-NLS
1330  }
1331  }
1332  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1333  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1334  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, getName(), adobeUsedTime));
1335  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1336  try{
1337  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1338  if (bba != null) {
1339  bbartifacts.add(bba);
1340  fileName = fileName.replace("\0", "");
1341  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1342  if (bba != null) {
1343  bbartifacts.add(bba);
1344  }
1345  }
1346  } catch(TskCoreException ex) {
1347  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1348  }
1349  line = reader.readLine();
1350  }
1351  line = line.trim();
1352  }
1353  }
1354  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1355  postArtifacts(bbartifacts);
1356  }
1357  }
1358 
1371  private void parseMediaPlayerMRUList(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1372  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1373  String line = reader.readLine();
1374  while (!line.contains(SECTION_DIVIDER)) {
1375  line = reader.readLine();
1376  line = line.trim();
1377  if (line.contains("LastWrite")) {
1378  line = reader.readLine();
1379  // Columns are
1380  // FileX -> <Media file>
1381  while (!line.contains(SECTION_DIVIDER) && !line.contains("RecentFileList has no values.")) {
1382  // Split line on "> " which is the record delimiter between position and file
1383  String tokens[] = line.split("> ");
1384  String fileName = tokens[1];
1385  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1386  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1387  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1388  try{
1389  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1390  if (bba != null) {
1391  bbartifacts.add(bba);
1392  bba = createAssociatedArtifact(fileName, bba);
1393  if (bba != null) {
1394  bbartifacts.add(bba);
1395  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1396  if (bba != null) {
1397  bbartifacts.add(bba);
1398  }
1399  }
1400  }
1401  } catch(TskCoreException ex) {
1402  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1403  }
1404  line = reader.readLine();
1405  }
1406  line = line.trim();
1407  }
1408  }
1409  if (!bbartifacts.isEmpty()&& !context.dataSourceIngestIsCancelled()) {
1410  postArtifacts(bbartifacts);
1411  }
1412  }
1413 
1426  private void parseGenericMRUList(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1427  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1428  String line = reader.readLine();
1429  while (!line.contains(SECTION_DIVIDER)) {
1430  line = reader.readLine();
1431  line = line.trim();
1432  if (line.contains("LastWrite")) {
1433  line = reader.readLine();
1434  // Columns are
1435  // FileX -> <file>
1436  while (!line.contains(SECTION_DIVIDER) && !line.isEmpty() && !line.contains("Applets")
1437  && !line.contains(("Recent File List"))) {
1438  // Split line on "> " which is the record delimiter between position and file
1439  String tokens[] = line.split("> ");
1440  if (tokens.length > 1) {
1441  String fileName = tokens[1];
1442  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1443  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1444  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1445  try{
1446  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1447  if (bba != null) {
1448  bbartifacts.add(bba);
1449  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1450  if (bba != null) {
1451  bbartifacts.add(bba);
1452  }
1453  }
1454  } catch(TskCoreException ex) {
1455  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1456  }
1457  }
1458  line = reader.readLine();
1459  }
1460  line = line.trim();
1461  }
1462  }
1463  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1464  postArtifacts(bbartifacts);
1465  }
1466  }
1467 
1480  private void parseWinRARMRUList(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1481  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1482  String line = reader.readLine();
1483  while (!line.contains(SECTION_DIVIDER)) {
1484  line = reader.readLine();
1485  line = line.trim();
1486  if (line.contains("LastWrite")) {
1487  line = reader.readLine();
1488  // Columns are
1489  // FileX -> <Media file>
1490  if (!line.isEmpty()) {
1491  while (!line.contains(SECTION_DIVIDER)) {
1492  // Split line on "> " which is the record delimiter between position and file
1493  String tokens[] = line.split("> ");
1494  String fileName = tokens[1];
1495  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1496  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1497  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1498  try{
1499  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1500  bbartifacts.add(bba);
1501  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1502  if (bba != null) {
1503  bbartifacts.add(bba);
1504  }
1505  } catch(TskCoreException ex) {
1506  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1507  }
1508  line = reader.readLine();
1509  }
1510  }
1511  line = line.trim();
1512  }
1513  }
1514  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1515  postArtifacts(bbartifacts);
1516  }
1517  }
1518 
1531  private void parse7ZipMRU(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1532  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1533  String line = reader.readLine();
1534  line = line.trim();
1535  if (!line.contains("PathHistory:")) {
1536  while (!line.contains("PathHistory:") && !line.isEmpty()) {
1537  // Columns are
1538  // <fileName>
1539  String fileName = line;
1540  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1541  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1542  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1543  try{
1544  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1545  bbartifacts.add(bba);
1546  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1547  if (bba != null) {
1548  bbartifacts.add(bba);
1549  }
1550 
1551  } catch(TskCoreException ex) {
1552  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1553  }
1554  line = reader.readLine();
1555  line = line.trim();
1556  }
1557  }
1558  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1559  postArtifacts(bbartifacts);
1560  }
1561  }
1562 
1575  private void parseOfficeDocs2010MRUList(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1576  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1577  String line = reader.readLine();
1578  line = line.trim();
1579  // Reading to the SECTION DIVIDER to get next section of records to process. Dates appear to have
1580  // multiple spaces in them that makes it harder to parse so next section will be easier to parse
1581  while (!line.contains(SECTION_DIVIDER)) {
1582  line = reader.readLine();
1583  }
1584  line = reader.readLine();
1585  while (!line.contains(SECTION_DIVIDER)) {
1586  // record has the following format
1587  // 1294283922|REG|||OfficeDocs2010 - F:\Windows_time_Rules_xp.doc
1588  String tokens[] = line.split("\\|");
1589  Long docDate = Long.valueOf(tokens[0]);
1590  String fileNameTokens[] = tokens[4].split(" - ");
1591  String fileName = fileNameTokens[1];
1592  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1593  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1594  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, getName(), docDate));
1595  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1596  try{
1597  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1598  bbartifacts.add(bba);
1599  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1600  if (bba != null) {
1601  bbartifacts.add(bba);
1602  }
1603  } catch(TskCoreException ex) {
1604  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1605  }
1606  line = reader.readLine();
1607  line = line.trim();
1608  }
1609  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1610  postArtifacts(bbartifacts);
1611  }
1612  }
1613 
1626  private void parseOfficeTrustRecords(AbstractFile regFile, BufferedReader reader, String comment) throws FileNotFoundException, IOException {
1627  String userProfile = regFile.getParentPath();
1628  userProfile = userProfile.substring(0, userProfile.length() - 1);
1629  List<BlackboardArtifact> bbartifacts = new ArrayList<>();
1630  SimpleDateFormat pluginDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy", US);
1631  Long usedTime = Long.valueOf(0);
1632  String line = reader.readLine();
1633  while (!line.contains(SECTION_DIVIDER)) {
1634  line = reader.readLine();
1635  line = line.trim();
1636  usedTime = Long.valueOf(0);
1637  if (!line.contains("**") && !line.contains("----------") && !line.contains("LastWrite")
1638  && !line.contains(SECTION_DIVIDER) && !line.isEmpty() && !line.contains("TrustRecords")
1639  && !line.contains("VBAWarnings =")) {
1640  // Columns are
1641  // Date : <File Name>/<Website>
1642  // Split line on " : " which is the record delimiter between position and file
1643  String fileName = null;
1644  String tokens[] = line.split(" : ");
1645  fileName = tokens[1];
1646  fileName = fileName.replace("%USERPROFILE%", userProfile);
1647  // Time in the format of Wed May 31 14:33:03 2017 Z
1648  try {
1649  String fileUsedTime = tokens[0].replaceAll(" Z", "");
1650  Date usedDate = pluginDateFormat.parse(fileUsedTime);
1651  usedTime = usedDate.getTime() / 1000;
1652  } catch (ParseException ex) {
1653  // catching error and displaying date that could not be parsed
1654  // we set the timestamp to 0 and continue on processing
1655  logger.log(Level.WARNING, String.format("Failed to parse date/time %s for TrustRecords artifact.", tokens[0]), ex); //NON-NLS
1656  }
1657  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1658  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), fileName));
1659  attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED, getName(), usedTime));
1660  attributes.add(new BlackboardAttribute(TSK_COMMENT, getName(), comment));
1661  try{
1662  BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_RECENT_OBJECT, regFile, attributes);
1663  bbartifacts.add(bba);
1664  bba = createAssociatedArtifact(FilenameUtils.normalize(fileName, true), bba);
1665  if (bba != null) {
1666  bbartifacts.add(bba);
1667  }
1668  } catch(TskCoreException ex) {
1669  logger.log(Level.SEVERE, String.format("Failed to create TSK_RECENT_OBJECT artifact for file %d", regFile.getId()), ex);
1670  }
1671  line = line.trim();
1672  }
1673  }
1674  if (!bbartifacts.isEmpty() && !context.dataSourceIngestIsCancelled()) {
1675  postArtifacts(bbartifacts);
1676  }
1677  }
1678 
1689  private BlackboardArtifact createAssociatedArtifact(String filePathName, BlackboardArtifact bba) {
1690  String fileName = FilenameUtils.getName(filePathName);
1691  String filePath = FilenameUtils.getPath(filePathName);
1692  List<AbstractFile> sourceFiles;
1693  try {
1694  sourceFiles = currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource, fileName, filePath);
1695  if (!sourceFiles.isEmpty()) {
1696  return createAssociatedArtifact(sourceFiles.get(0), bba);
1697  }
1698  } catch (TskCoreException ex) {
1699  // only catching the error and displaying the message as the file may not exist on the
1700  // system anymore
1701  logger.log(Level.WARNING, String.format("Error finding actual file %s. file may not exist", filePathName)); //NON-NLS
1702  }
1703 
1704  return null;
1705  }
1706 
1716  private Map<String, String> makeUserNameMap(Content dataSource) throws TskCoreException {
1717  Map<String, String> map = new HashMap<>();
1718 
1719  for(OsAccount account: tskCase.getOsAccountManager().getOsAccounts(((DataSource)dataSource).getHost())) {
1720  Optional<String> userName = account.getLoginName();
1721  map.put(account.getName(), userName.isPresent() ? userName.get() : "");
1722  }
1723 
1724  return map;
1725  }
1726 
1732  private Map<String, String> getUserNameMap() {
1733  if(userNameMap == null) {
1734  // Get a mapping of user sids to user names and save globally so it can be used for other areas
1735  // of the registry, ie: BAM key
1736  try {
1737  userNameMap = makeUserNameMap(dataSource);
1738  } catch (TskCoreException ex) {
1739  logger.log(Level.WARNING, "Unable to create OS Account user name map", ex);
1740  // This is not the end of the world we will just continue without
1741  // user names
1742  userNameMap = new HashMap<>();
1743  }
1744  }
1745 
1746  return userNameMap;
1747  }
1748 
1759  private BlackboardAttribute getAttributeForArtifact(BlackboardArtifact artifact, BlackboardAttribute.ATTRIBUTE_TYPE type) throws TskCoreException {
1760  return artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(type.getTypeID())));
1761  }
1762 
1771  void createShellBagArtifacts(AbstractFile regFile, List<ShellBag> shellbags) throws TskCoreException {
1772  List<BlackboardArtifact> artifacts = new ArrayList<>();
1773  List<DataArtifact> dataArtifacts = new ArrayList<>();
1774  try {
1775  for (ShellBag bag : shellbags) {
1776  Collection<BlackboardAttribute> attributes = new ArrayList<>();
1777  attributes.add(new BlackboardAttribute(TSK_PATH, getName(), bag.getResource()));
1778  attributes.add(new BlackboardAttribute(getKeyAttribute(), getName(), bag.getKey()));
1779 
1780  long time;
1781  time = bag.getLastWrite();
1782  if (time != 0) {
1783  attributes.add(new BlackboardAttribute(getLastWriteAttribute(), getName(), time));
1784  }
1785 
1786  time = bag.getModified();
1787  if (time != 0) {
1788  attributes.add(new BlackboardAttribute(TSK_DATETIME_MODIFIED, getName(), time));
1789  }
1790 
1791  time = bag.getCreated();
1792  if (time != 0) {
1793  attributes.add(new BlackboardAttribute(TSK_DATETIME_CREATED, getName(), time));
1794  }
1795 
1796  time = bag.getAccessed();
1797  if (time != 0) {
1798  attributes.add(new BlackboardAttribute(TSK_DATETIME_ACCESSED, getName(), time));
1799  }
1800 
1801  BlackboardArtifact artifact = createArtifactWithAttributes(getShellBagArtifact(), regFile, attributes);
1802  artifacts.add(artifact);
1803  dataArtifacts.add((DataArtifact)artifact);
1804  }
1805  } finally {
1806  if(!context.dataSourceIngestIsCancelled()) {
1807  postArtifacts(artifacts);
1808  context.addDataArtifactsToJob(dataArtifacts);
1809  }
1810  }
1811  }
1812 
1821  private BlackboardArtifact.Type getShellBagArtifact() throws TskCoreException {
1822  if (shellBagArtifactType == null) {
1823  try {
1824  shellBagArtifactType = tskCase.getBlackboard().getOrAddArtifactType(SHELLBAG_ARTIFACT_NAME, Bundle.Shellbag_Artifact_Display_Name());
1825  } catch (BlackboardException ex) {
1826  throw new TskCoreException(String.format("Failed to get shell bag artifact type", SHELLBAG_ARTIFACT_NAME), ex);
1827  }
1828  }
1829 
1830  return shellBagArtifactType;
1831  }
1832 
1841  private BlackboardAttribute.Type getLastWriteAttribute() throws TskCoreException {
1842  if (shellBagLastWriteAttributeType == null) {
1843  try {
1844  shellBagLastWriteAttributeType = tskCase.getBlackboard().getOrAddAttributeType(SHELLBAG_ATTRIBUTE_LAST_WRITE,
1845  BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.DATETIME,
1846  Bundle.Shellbag_Last_Write_Attribute_Display_Name());
1847  } catch (BlackboardException ex) {
1848  // Attribute already exists get it from the case
1849  throw new TskCoreException(String.format("Failed to get custom attribute %s", SHELLBAG_ATTRIBUTE_LAST_WRITE), ex);
1850  }
1851  }
1852  return shellBagLastWriteAttributeType;
1853  }
1854 
1863  private BlackboardAttribute.Type getKeyAttribute() throws TskCoreException {
1864  if (shellBagKeyAttributeType == null) {
1865  try {
1866  shellBagKeyAttributeType = tskCase.getBlackboard().getOrAddAttributeType(SHELLBAG_ATTRIBUTE_KEY,
1867  BlackboardAttribute.TSK_BLACKBOARD_ATTRIBUTE_VALUE_TYPE.STRING,
1868  Bundle.Shellbag_Key_Attribute_Display_Name());
1869  } catch (BlackboardException ex) {
1870  throw new TskCoreException(String.format("Failed to get key attribute %s", SHELLBAG_ATTRIBUTE_KEY), ex);
1871  }
1872  }
1873  return shellBagKeyAttributeType;
1874  }
1875 
1885  Map<String, List<String>> readGroups(BufferedReader bufferedReader) throws IOException {
1886  Map<String, List<String>> groupMap = new HashMap<>();
1887 
1888  String line = bufferedReader.readLine();
1889 
1890  int userCount = 0;
1891  String groupName = null;
1892 
1893  while (line != null && !line.contains(SECTION_DIVIDER)) {
1894 
1895  if (line.contains("Group Name")) {
1896  String value = line.replaceAll("Group Name\\s*?:", "").trim();
1897  groupName = (value.replaceAll("\\[\\d*?\\]", "")).trim();
1898  int startIndex = value.indexOf(" [") + 1;
1899  int endIndex = value.indexOf(']');
1900 
1901  if (startIndex != -1 && endIndex != -1) {
1902  String countStr = value.substring(startIndex + 1, endIndex);
1903  userCount = Integer.parseInt(countStr);
1904  }
1905  } else if (line.matches("Users\\s*?:")) {
1906  for (int i = 0; i < userCount; i++) {
1907  line = bufferedReader.readLine();
1908  if (line != null) {
1909  String sid = line.trim();
1910  List<String> groupList = groupMap.get(sid);
1911  if (groupList == null) {
1912  groupList = new ArrayList<>();
1913  groupMap.put(sid, groupList);
1914  }
1915  groupList.add(groupName);
1916  }
1917  }
1918  groupName = null;
1919  }
1920  line = bufferedReader.readLine();
1921  }
1922  return groupMap;
1923  }
1924 
1933  private Map.Entry<String, String> getSAMKeyValue(String line) {
1934  int index = line.indexOf(':');
1935  Map.Entry<String, String> returnValue = null;
1936  String key = null;
1937  String value = null;
1938 
1939  if (index != -1) {
1940  key = line.substring(0, index).trim();
1941  if (index + 1 < line.length()) {
1942  value = line.substring(index + 1).trim();
1943  } else {
1944  value = "";
1945  }
1946 
1947  } else if (line.contains("-->")) {
1948  key = line.replace("-->", "").trim();
1949  value = "true";
1950  }
1951 
1952  if (key != null) {
1953  returnValue = new AbstractMap.SimpleEntry<>(key, value);
1954  }
1955 
1956  return returnValue;
1957  }
1958 
1959  @Override
1960  public void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
1961  this.dataSource = dataSource;
1962  this.context = context;
1963 
1964  progressBar.progress(Bundle.Progress_Message_Analyze_Registry());
1965  analyzeRegistryFiles(context.getJobId());
1966 
1967  }
1968 
1972  private class RegOutputFiles {
1973 
1974  public String autopsyPlugins = "";
1975  public String fullPlugins = "";
1976  }
1977 
1990  private void createOrUpdateOsAccount(AbstractFile file, String sid, String userName, String homeDir) throws TskCoreException, TskDataException, NotUserSIDException {
1991  OsAccountManager accountMgr = tskCase.getOsAccountManager();
1992  HostManager hostMrg = tskCase.getHostManager();
1993  Host host = hostMrg.getHostByDataSource((DataSource)dataSource);
1994 
1995  Optional<OsAccount> optional = accountMgr.getWindowsOsAccount(sid, null, null, host);
1996  OsAccount osAccount;
1997  if (!optional.isPresent()) {
1998  osAccount = accountMgr.newWindowsOsAccount(sid, userName != null && userName.isEmpty() ? null : userName, domainName, host, domainName != null && !domainName.isEmpty()? OsAccountRealm.RealmScope.DOMAIN : OsAccountRealm.RealmScope.UNKNOWN);
1999  accountMgr.newOsAccountInstance(osAccount, (DataSource)dataSource, OsAccountInstance.OsAccountInstanceType.LAUNCHED);
2000  } else {
2001  osAccount = optional.get();
2002  if (userName != null && !userName.isEmpty()) {
2003  OsAccountUpdateResult updateResult= accountMgr.updateCoreWindowsOsAccountAttributes(osAccount, null, userName, domainName.isEmpty() ? null : domainName, host);
2004  osAccount = updateResult.getUpdatedAccount().orElse(osAccount);
2005  }
2006  }
2007 
2008  if (homeDir != null && !homeDir.isEmpty()) {
2009  List<OsAccountAttribute> attributes = new ArrayList<>();
2010  String dir = homeDir.replaceFirst("^(%\\w*%)", "");
2011  dir = dir.replace("\\", "/");
2012  attributes.add(createOsAccountAttribute(TSK_HOME_DIR, dir, osAccount, host, file));
2013  accountMgr.addExtendedOsAccountAttributes(osAccount, attributes);
2014  }
2015 
2016  }
2017 
2024  private void addEmailAccount(AbstractFile regFile, String emailAddress) {
2025  try {
2026  currentCase.getSleuthkitCase()
2027  .getCommunicationsManager()
2028  .createAccountFileInstance(Account.Type.EMAIL,
2029  emailAddress, getRAModuleName(), regFile);
2030  } catch (TskCoreException ex) {
2031  logger.log(Level.SEVERE,
2032  String.format("Error adding email account with value "
2033  + "%s, to the case database for file %s [objId=%d]",
2034  emailAddress, regFile.getName(), regFile.getId()), ex);
2035  }
2036  }
2037 
2046  private Long parseRegRipTime(String value) {
2047  try {
2048  return REG_RIPPER_TIME_FORMAT.parse(value).getTime() / MS_IN_SEC;
2049  } catch (ParseException ex) {
2050  logger.log(Level.SEVERE, String.format("Failed to parse reg rip time: %s", value));
2051  }
2052  return null;
2053  }
2054 
2066  private void updateOsAccount(OsAccount osAccount, Map<String, String> userInfo, List<String> groupList, AbstractFile regFile) throws TskDataException, TskCoreException, NotUserSIDException {
2067  Host host = ((DataSource)dataSource).getHost();
2068 
2069  SimpleDateFormat regRipperTimeFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss yyyy 'Z'", US);
2070  regRipperTimeFormat.setTimeZone(getTimeZone("GMT"));
2071 
2072  List<OsAccountAttribute> attributes = new ArrayList<>();
2073 
2074  Long creationTime = null;
2075 
2076  String value = userInfo.get(ACCOUNT_CREATED_KEY);
2077  if (value != null && !value.isEmpty() && !value.equals(NEVER_DATE)) {
2078  creationTime = parseRegRipTime(value);
2079  }
2080 
2081  value = userInfo.get(LAST_LOGIN_KEY);
2082  if (value != null && !value.isEmpty() && !value.equals(NEVER_DATE)) {
2083  Long time = parseRegRipTime(value);
2084  if (time != null) {
2085  attributes.add(createOsAccountAttribute(TSK_DATETIME_ACCESSED,
2086  parseRegRipTime(value),
2087  osAccount, host, regFile));
2088  }
2089  }
2090 
2091  String loginName = null;
2092  value = userInfo.get(USERNAME_KEY);
2093  if (value != null && !value.isEmpty()) {
2094  loginName = value;
2095  }
2096 
2097  value = userInfo.get(LOGIN_COUNT_KEY);
2098  if (value != null && !value.isEmpty()) {
2099  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
2100  Integer.parseInt(value),
2101  osAccount, host, regFile));
2102  }
2103 
2104  // From regripper the possible values for this key are
2105  // "Default Admin User", "Custom Limited Acct"
2106  // and "Default Guest Acct"
2107  value = userInfo.get(ACCOUNT_TYPE_KEY);
2108  if (value != null && !value.isEmpty() && value.toLowerCase().contains("admin")) {
2109  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_IS_ADMIN,
2110  1, osAccount, host, regFile));
2111  }
2112 
2113  value = userInfo.get(USER_COMMENT_KEY);
2114  if (value != null && !value.isEmpty()) {
2115  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_DESCRIPTION,
2116  value, osAccount, host, regFile));
2117  }
2118 
2119  value = userInfo.get(INTERNET_NAME_KEY);
2120  if (value != null && !value.isEmpty()) {
2121  addEmailAccount(regFile, value);
2122 
2123  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_EMAIL,
2124  value, osAccount, host, regFile));
2125  }
2126 
2127  // FULL_NAME_KEY and NAME_KEY appear to be the same value.
2128  String fullName = null;
2129  value = userInfo.get(FULL_NAME_KEY);
2130  if (value != null && !value.isEmpty()) {
2131  fullName = value;
2132  } else {
2133  value = userInfo.get(NAME_KEY);
2134  if (value != null && !value.isEmpty()) {
2135  fullName = value;
2136  }
2137  }
2138 
2139  value = userInfo.get(PWD_RESET_KEY);
2140  if (value != null && !value.isEmpty() && !value.equals(NEVER_DATE)) {
2141  Long time = parseRegRipTime(value);
2142  if (time != null) {
2143  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_PASSWORD_RESET,
2144  time, osAccount, host, regFile));
2145  }
2146  }
2147 
2148  value = userInfo.get(PASSWORD_HINT);
2149  if (value != null && !value.isEmpty()) {
2150  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_PASSWORD_HINT,
2151  value, osAccount, host, regFile));
2152  }
2153 
2154  value = userInfo.get(PWD_FAILE_KEY);
2155  if (value != null && !value.isEmpty() && !value.equals(NEVER_DATE)) {
2156  Long time = parseRegRipTime(value);
2157  if (time != null) {
2158  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_PASSWORD_FAIL,
2159  time, osAccount, host, regFile));
2160  }
2161  }
2162 
2163  String settingString = getSettingsFromMap(PASSWORD_SETTINGS_FLAGS, userInfo);
2164  if (!settingString.isEmpty()) {
2165  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_PASSWORD_SETTINGS,
2166  settingString, osAccount, host, regFile));
2167  }
2168 
2169  settingString = getSettingsFromMap(ACCOUNT_SETTINGS_FLAGS, userInfo);
2170  if (!settingString.isEmpty()) {
2171  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_ACCOUNT_SETTINGS,
2172  settingString, osAccount, host, regFile));
2173  }
2174 
2175  settingString = getSettingsFromMap(ACCOUNT_TYPE_FLAGS, userInfo);
2176  if (!settingString.isEmpty()) {
2177  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_FLAG,
2178  settingString, osAccount, host, regFile));
2179  }
2180 
2181  if (groupList != null && groupList.isEmpty()) {
2182  String groups = groupList.stream()
2183  .map(String::valueOf)
2184  .collect(Collectors.joining(", "));
2185 
2186  attributes.add(createOsAccountAttribute(ATTRIBUTE_TYPE.TSK_GROUPS,
2187  groups, osAccount, host, regFile));
2188  }
2189 
2190  // add the attributes to account.
2191  OsAccountManager accountMgr = tskCase.getOsAccountManager();
2192  accountMgr.addExtendedOsAccountAttributes(osAccount, attributes);
2193 
2194  // update the loginname
2195  accountMgr.updateCoreWindowsOsAccountAttributes(osAccount, null, loginName, domainName.isEmpty() ? null : domainName, host);
2196 
2197  // update other standard attributes - fullname, creationdate
2198  accountMgr.updateStandardOsAccountAttributes(osAccount, fullName, null, null, creationTime);
2199 
2200 
2201  }
2202 
2211  private String getSettingsFromMap(String[] keys, Map<String, String> map) {
2212  List<String> settingsList = new ArrayList<>();
2213  for (String setting : keys) {
2214  if (map.containsKey(setting)) {
2215  settingsList.add(setting);
2216  }
2217  }
2218 
2219  if (!settingsList.isEmpty()) {
2220  return settingsList.stream()
2221  .map(String::valueOf)
2222  .collect(Collectors.joining(", "));
2223  }
2224 
2225  return "";
2226  }
2227 
2239  private OsAccountAttribute createOsAccountAttribute(BlackboardAttribute.ATTRIBUTE_TYPE type, String value, OsAccount osAccount, Host host, AbstractFile file) {
2240  return osAccount.new OsAccountAttribute(new BlackboardAttribute.Type(type), value, osAccount, host, file);
2241  }
2242 
2254  private OsAccountAttribute createOsAccountAttribute(BlackboardAttribute.ATTRIBUTE_TYPE type, Long value, OsAccount osAccount, Host host, AbstractFile file) {
2255  return osAccount.new OsAccountAttribute(new BlackboardAttribute.Type(type), value, osAccount, host, file);
2256  }
2257 
2269  private OsAccountAttribute createOsAccountAttribute(BlackboardAttribute.ATTRIBUTE_TYPE type, Integer value, OsAccount osAccount, Host host, AbstractFile file) {
2270  return osAccount.new OsAccountAttribute(new BlackboardAttribute.Type(type), value, osAccount, host, file);
2271  }
2272 }

Copyright © 2012-2021 Basis Technology. Generated on: Fri Aug 6 2021
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.