-
Notifications
You must be signed in to change notification settings - Fork 659
Parsing of Thumbcache artifacts #7973
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
eb69244
57e2efc
472bba1
18e717e
503d2fc
790a0eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,262 @@ | ||
| /* | ||
| * | ||
| * Autopsy Forensic Browser | ||
| * | ||
| * Copyright 2020-2025 Sleuth Kit Labs. | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| * you may not use this file except in compliance with the License. | ||
| * You may obtain a copy of the License at | ||
| * | ||
| * http://www.apache.org/licenses/LICENSE-2.0 | ||
| * | ||
| * Unless required by applicable law or agreed to in writing, software | ||
| * distributed under the License is distributed on an "AS IS" BASIS, | ||
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| * See the License for the specific language governing permissions and | ||
| * limitations under the License. | ||
| */ | ||
| package org.sleuthkit.autopsy.recentactivity; | ||
|
|
||
| import java.io.File; | ||
| import java.io.FileNotFoundException; | ||
| import java.io.IOException; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
| import java.nio.file.attribute.BasicFileAttributes; | ||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.logging.Level; | ||
| import org.apache.commons.io.FileUtils; | ||
| import org.openide.modules.InstalledFileLocator; | ||
| import org.openide.util.NbBundle.Messages; | ||
| import org.sleuthkit.autopsy.casemodule.Case; | ||
| import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; | ||
| import org.sleuthkit.autopsy.casemodule.services.FileManager; | ||
| import org.sleuthkit.autopsy.coreutils.ExecUtil; | ||
| import org.sleuthkit.autopsy.coreutils.Logger; | ||
| import org.sleuthkit.autopsy.coreutils.PlatformUtil; | ||
| import org.sleuthkit.autopsy.datamodel.ContentUtils; | ||
| import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProcessTerminator; | ||
| import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress; | ||
| import org.sleuthkit.autopsy.ingest.IngestJobContext; | ||
| import org.sleuthkit.autopsy.ingest.IngestServices; | ||
| import org.sleuthkit.autopsy.ingest.ModuleContentEvent; | ||
| import org.sleuthkit.datamodel.AbstractFile; | ||
| import org.sleuthkit.datamodel.Content; | ||
| import org.sleuthkit.datamodel.DerivedFile; | ||
| import org.sleuthkit.datamodel.TskCoreException; | ||
| import org.sleuthkit.datamodel.TskData; | ||
|
|
||
| /** | ||
| * Extract the thumbcache files to a temp directory so it can be | ||
| * parsed and added as a derived file(s) | ||
| */ | ||
| final class ExtractThumbcache extends Extract { | ||
|
|
||
| private static final Logger logger = Logger.getLogger(ExtractThumbcache.class.getName()); | ||
|
|
||
| private static final String THUMBCACHE_TOOL_FOLDER = "thumbcache_parser"; //NON-NLS | ||
| private static final String THUMBCACHE_TOOL_NAME_WINDOWS = "thumbcache_viewer_cmd.exe"; //NON-NLS | ||
| private static final String THUMBCACHE_OUTPUT_FILE_NAME = "Output.txt"; //NON-NLS | ||
| private static final String THUMBCACHE_ERROR_FILE_NAME = "Error.txt"; //NON-NLS | ||
|
|
||
| private final IngestJobContext context; | ||
|
|
||
| @Messages({"ExtractThumbcache_module_name=Thumbcache Analyzer"}) | ||
|
|
||
| ExtractThumbcache(IngestJobContext context) { | ||
| super(Bundle.ExtractThumbcache_module_name(), context); | ||
| this.context = context; | ||
| } | ||
|
|
||
| @Messages({ | ||
| "Thumbcache_Files_Not_Found=Thumbcache files not found", | ||
| "ExtractThumbcache_error_finding_program=Could not find thumbcache_viewer_cmd.exe program", | ||
| "Thumbcache_process_error_executing_export_thumbcache_program=Error running thumbcache program", | ||
| "Thumbcache_Files_TSK_Error=TSK error searching for thumbcache files" | ||
| }) | ||
|
|
||
| @Override | ||
| void process(Content dataSource, DataSourceIngestModuleProgress progressBar) { | ||
|
|
||
| if (!PlatformUtil.isWindowsOS()) { | ||
| logger.log(Level.WARNING,"Thumbcache only Supported on Windows Platform."); //NON-NLS | ||
| return; // No need to continue | ||
| } | ||
|
|
||
| String modOutPath = RAImageIngestModule.getRAOutputPath(Case.getCurrentCase(), "thumbcache", context.getJobId()); | ||
| File dir = new File(modOutPath); | ||
| if (dir.exists() == false) { | ||
| dir.mkdirs(); | ||
| } | ||
|
|
||
| String tempDirPath = RAImageIngestModule.getRATempPath(Case.getCurrentCase(), "thumbcache", context.getJobId()); //NON-NLS | ||
| List<AbstractFile> thumbcacheFiles = getThumbcacheFiles(dataSource, tempDirPath); | ||
| if (thumbcacheFiles == null) { | ||
| this.addErrorMessage(Bundle.Thumbcache_Files_TSK_Error()); | ||
| return; //If we cannot find the thumbcache files we cannot proceed | ||
| } | ||
|
|
||
| if (thumbcacheFiles.isEmpty()) { | ||
| logger.log(Level.WARNING, "Error finding thumbcache files"); //NON-NLS | ||
| return; //If we cannot find the thumbcache files we cannot proceed | ||
| } | ||
|
|
||
| final String thumbcacheDumper = getPathForThumbcacheDumper(); | ||
| if (thumbcacheDumper == null) { | ||
| this.addErrorMessage(Bundle.ExtractThumbcache_error_finding_program()); | ||
| logger.log(Level.WARNING, "Error finding thumbcache parsing program"); //NON-NLS | ||
| return; //If we cannot find the thumbcache parser program we cannot proceed | ||
| } | ||
|
|
||
| if (context.dataSourceIngestIsCancelled()) { | ||
| return; | ||
| } | ||
|
|
||
| String thumbcacheFileLocation = null; | ||
| for (AbstractFile thumbcacheFile: thumbcacheFiles) { | ||
| if (context.dataSourceIngestIsCancelled()) { | ||
| return; | ||
| } | ||
| try { | ||
| File thumbcacheFileName = new File(tempDirPath + File.separator + thumbcacheFile.getId() + "_" + thumbcacheFile.getName()); | ||
| if (thumbcacheFileName.exists()) { | ||
| String modOutFile = modOutPath + File.separator + thumbcacheFile.getId() + "_" +thumbcacheFile.getName(); | ||
| thumbcacheFileLocation = tempDirPath + File.separator + thumbcacheFile.getId() + "_" + thumbcacheFile.getName(); | ||
|
|
||
| dir = new File(modOutFile); | ||
| if (dir.exists() == false) { | ||
| dir.mkdirs(); | ||
| } | ||
|
|
||
| extractThumbcacheFiles(thumbcacheDumper, modOutFile, thumbcacheFileLocation); | ||
| addThumbcacheDerivedFiles(modOutFile, thumbcacheFile); | ||
| } | ||
| } catch (IOException ex) { | ||
| logger.log(Level.SEVERE, String.format("Error processing thumbcache file %s", thumbcacheFile.getId() + "_" + thumbcacheFile.getName()), ex); //NON-NLS= | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Extract the thumbcache file to the temp directory | ||
| * | ||
| * @param dataSource datasource where software hive is | ||
| * @param tempDirPath temp directory to write file to | ||
| * * | ||
| * @return hive file location | ||
| */ | ||
| List<AbstractFile> getThumbcacheFiles(Content dataSource, String tempDirPath) { | ||
| FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); | ||
|
|
||
| List<AbstractFile> thumbcacheFiles; | ||
|
|
||
| try { | ||
| thumbcacheFiles = fileManager.findFiles(dataSource, "thumbcache_%.db", ""); //NON-NLS | ||
|
Comment on lines
+158
to
+159
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Verify how FileManager.findFiles(...) handles an empty dirName.
# Expected result: the 3-arg overload explicitly special-cases "" as "any directory".
fd -i 'FileManager.java' -x rg -n -A20 -B4 'findFiles\s*\(' {}
rg -n -C2 --type java 'findFiles\(\s*dataSource,\s*"thumbcache_%\.db"\s*,\s*""\s*\)'Repository: sleuthkit/autopsy Length of output: 6557 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the CaseDb or similar database class that implements findFiles
fd -i 'casedb' -o -i '*database*.java' | head -20
# Also search for the actual implementation
rg -l 'def findFiles|void findFiles|List.*findFiles' --type java | head -10Repository: sleuthkit/autopsy Length of output: 177 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for the actual findFiles implementation in SleuthKit database classes
rg -n -B3 -A15 'public.*List.*findFiles.*String.*fileName.*String.*parentSubString' --type javaRepository: sleuthkit/autopsy Length of output: 4573 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find where caseDb type is defined and used
rg -n 'caseDb\.' Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java | head -5
# Search for SleuthKit Case class that likely implements findFiles
fd -i 'case.java' --type f | grep -i sleuthkit | head -5Repository: sleuthkit/autopsy Length of output: 662 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for the actual findFiles implementation in TSK/SleuthKit packages
rg -l 'public.*List.*AbstractFile.*findFiles' --type java | xargs -I {} basename {} | sort -uRepository: sleuthkit/autopsy Length of output: 77 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for Case.java file and check the findFiles implementation
fd -i 'case.java' -x cat {} | head -200Repository: sleuthkit/autopsy Length of output: 6494 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Look for the actual SleuthKit or database implementation
# Search for findFiles with parentSubString in Case.java
rg -n -A10 'public.*List.*AbstractFile.*findFiles.*parentSubString' Core/src/org/sleuthkit/autopsy/casemodule/Case.javaRepository: sleuthkit/autopsy Length of output: 43 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find the actual Case.java in the autopsy codebase (not central repository)
fd 'Case.java' --path '*/casemodule/*' -x wc -l {}
# Check what caseDb is - likely from org.sleuthkit.datamodel
rg -n 'private.*caseDb|SleuthkitCase.*caseDb' Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.javaRepository: sleuthkit/autopsy Length of output: 276 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for the SQL query that builds LIKE clauses with parentSubString
# Look for how parentSubString is being used
rg -n 'parentSubString|parent.*LIKE|%.*LIKE' --type java | head -30Repository: sleuthkit/autopsy Length of output: 3431 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Find where caseDb is defined in FileManager.java
rg -n -B5 'caseDb.*=' Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java | head -20
# Look for the type definition
rg -n 'SleuthkitCase|private.*caseDb' Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.javaRepository: sleuthkit/autopsy Length of output: 971 🏁 Script executed: #!/bin/bash
set -euoripefail
# Look for actual SQL construction in sleuthkit packages
# The key is to find where the SQL LIKE clause is built with parentSubString
rg -n -B2 -A8 'parent_path.*LIKE.*%' --type java | grep -A8 -B2 'findFiles' | head -50Repository: sleuthkit/autopsy Length of output: 735 🏁 Script executed: #!/bin/bash
set -euo pipefail
# SleuthkitCase is from org.sleuthkit.datamodel
# Let me check if there's a jar or source code in this repo
find . -name 'SleuthkitCase.java' -o -name 'SleuthkitCase*.jar'Repository: sleuthkit/autopsy Length of output: 43 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for how the parentSubString parameter is being used in caseDb calls
# Look for the actual implementation pattern
rg -n -B3 -A10 'caseDb\.findFiles.*fileName.*parentSubString' Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.javaRepository: sleuthkit/autopsy Length of output: 772 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Look for similar patterns - how is findFiles called elsewhere with parent filters
rg -n 'findFiles.*""' --type java RecentActivity/Repository: sleuthkit/autopsy Length of output: 253 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Check if SleuthkitCase is available in external jars
find . -name '*.jar' | xargs -I {} sh -c 'jar tf {} 2>/dev/null | grep -q "SleuthkitCase.class" && echo {}'Repository: sleuthkit/autopsy Length of output: 45 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Look at the 2-arg overload to see if using that would be simpler
rg -n -A5 'public List<AbstractFile> findFiles\(Content dataSource, String fileName\)' Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java
# Also check how the code could be refactored to use the 2-arg version
cat -n RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractThumbcache.java | sed -n '145,155p'Repository: sleuthkit/autopsy Length of output: 869 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for other usages of findFiles with empty string to see if this is a pattern
rg -n 'findFiles.*""' --type java -C2Repository: sleuthkit/autopsy Length of output: 728 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Check if there's documentation or tests showing the expected behavior
rg -n -B3 -A3 'parentSubString.*""' --type javaRepository: sleuthkit/autopsy Length of output: 43 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Let's check the sleuthkit external library sources if available
# Look for how the SQL is constructed
rg -n 'surrounded.*%.*LIKE|LIKE.*surrounded' --type java -iRepository: sleuthkit/autopsy Length of output: 326 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search the web or documentation for SleuthKit's LIKE behavior with percent-surrounded patterns
# Also check if there are any test cases showing this pattern in use
rg -n -B2 -A10 'test.*findFiles.*""' --type javaRepository: sleuthkit/autopsy Length of output: 43 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Look for any other similar patterns in the codebase where empty string is used as parentSubString
rg -n 'findFiles.*,\s*"[^"]*"\s*,\s*""' --type javaRepository: sleuthkit/autopsy Length of output: 253 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Check if there are any other uses of the 2-arg findFiles overload without parent filter
rg -n 'findFiles\(dataSource, "[^"]*"\)' --type java | head -20Repository: sleuthkit/autopsy Length of output: 1734 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Look at the ExtractThumbcache context more broadly
# to see if there's a reason the parent path filter was needed
cat -n RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractThumbcache.java | sed -n '140,160p'Repository: sleuthkit/autopsy Length of output: 1036 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Search for SleuthKit documentation or other references to understand LIKE behavior
# Look for documentation comments about empty parentSubString
rg -n -B5 'findFiles.*Content.*String.*String' --type java | grep -A5 -B5 'documentation\|javadoc' | head -30Repository: sleuthkit/autopsy Length of output: 43 Switch to the 2-argument This is the only place in the codebase using thumbcacheFiles = fileManager.findFiles(dataSource, "thumbcache_%.db");🤖 Prompt for AI Agents |
||
| } catch (TskCoreException ex) { | ||
| logger.log(Level.SEVERE,"TskCoreException Looking for thumbcache file.", ex); //NON-NLS | ||
| return null; // No need to continue | ||
| } | ||
|
|
||
| if (thumbcacheFiles.isEmpty()) { | ||
| return new ArrayList<>(); // No thumbcache files found | ||
| } | ||
|
|
||
| for (AbstractFile thumbcacheFile : thumbcacheFiles) { | ||
| String thumbcacheFileName = tempDirPath + File.separator + thumbcacheFile.getId() + "_" + thumbcacheFile.getName(); | ||
|
|
||
| try { | ||
| ContentUtils.writeToFile(thumbcacheFile, new File(thumbcacheFileName)); | ||
| } catch (IOException ex) { | ||
| logger.log(Level.WARNING, String.format("Unable to write %s to temp directory. File name: %s", thumbcacheFile.getName(), thumbcacheFile), ex); //NON-NLS | ||
| } | ||
| } | ||
|
|
||
| return thumbcacheFiles; | ||
| } | ||
|
|
||
| /** | ||
| * Run the thumbcache parser cmd program | ||
| * | ||
| * @param thumbcacheExePath - Eecutable for the thumbcache program | ||
| * @param thumbcacheFilePath - output directory for program | ||
| * @param tempOutPath - file to parse | ||
| * | ||
| * @throws FileNotFoundException | ||
| * @throws IOException | ||
| */ | ||
| void extractThumbcacheFiles(String thumbcacheExePath, String thumbcacheFilePath, String thumbcacheFile) throws IOException { | ||
| final Path outputFilePath = Paths.get(thumbcacheFilePath, THUMBCACHE_OUTPUT_FILE_NAME); | ||
| final Path errFilePath = Paths.get(thumbcacheFilePath, THUMBCACHE_ERROR_FILE_NAME); | ||
|
|
||
|
|
||
| List<String> commandLine = new ArrayList<>(); | ||
| commandLine.add(thumbcacheExePath); | ||
| commandLine.add("-O"); //NON-NLS | ||
| commandLine.add(thumbcacheFilePath); | ||
| commandLine.add(thumbcacheFile); | ||
|
|
||
|
|
||
| ProcessBuilder processBuilder = new ProcessBuilder(commandLine); | ||
| processBuilder.redirectOutput(outputFilePath.toFile()); | ||
| processBuilder.redirectError(errFilePath.toFile()); | ||
|
|
||
| ExecUtil.execute(processBuilder, new DataSourceIngestModuleProcessTerminator(context, true)); | ||
| } | ||
|
|
||
| private String getPathForThumbcacheDumper() { | ||
| Path path = Paths.get(THUMBCACHE_TOOL_FOLDER, THUMBCACHE_TOOL_NAME_WINDOWS); | ||
| File thumbcacheToolFile = InstalledFileLocator.getDefault().locate(path.toString(), | ||
| ExtractThumbcache.class.getPackage().getName(), false); | ||
| if (thumbcacheToolFile != null) { | ||
| return thumbcacheToolFile.getAbsolutePath(); | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private void addThumbcacheDerivedFiles(String outputFolder, AbstractFile thumbcacheFile) { | ||
| Path outputFolderPath = Paths.get(outputFolder); | ||
| java.util.Collection<File> files = (List<File>) FileUtils.listFiles(outputFolderPath.toFile(), null, true); | ||
| for (File file : files) { | ||
| if (context.dataSourceIngestIsCancelled()) { | ||
| return; | ||
| } | ||
|
|
||
| Path candidate = file.toPath(); | ||
|
|
||
| if (candidate.getFileName().toString().equals(THUMBCACHE_ERROR_FILE_NAME) || candidate.getFileName().toString().equals(THUMBCACHE_OUTPUT_FILE_NAME)) { | ||
| continue; | ||
| } | ||
| try { | ||
| final Case currentCase = Case.getCurrentCaseThrows(); | ||
| final Path caseDirectory = Paths.get(currentCase.getCaseDirectory()); | ||
| final BasicFileAttributes attrs = Files.readAttributes(candidate, BasicFileAttributes.class); | ||
| final Path localCasePath = caseDirectory.relativize(candidate); | ||
|
|
||
| final DerivedFile tcacheFile = currentCase.getSleuthkitCase() | ||
| .addDerivedFile(candidate.getFileName().toString(), | ||
| localCasePath.toString(), attrs.size(), 0L, | ||
| attrs.creationTime().to(TimeUnit.SECONDS), | ||
| attrs.lastAccessTime().to(TimeUnit.SECONDS), | ||
| attrs.lastModifiedTime().to(TimeUnit.SECONDS), | ||
| attrs.isRegularFile(), thumbcacheFile, "", | ||
| "", "", "", TskData.EncodingType.NONE); | ||
|
|
||
| context.addFilesToJob(Arrays.asList(tcacheFile)); | ||
| IngestServices.getInstance().fireModuleContentEvent(new ModuleContentEvent(tcacheFile)); | ||
| dataFound = true; | ||
| } catch (IOException ex) { | ||
| logger.log(Level.WARNING, "I/O error encountered during thumbcache processing.", ex); | ||
| } catch (TskCoreException ex) { | ||
| logger.log(Level.SEVERE, "Unable to add thumbcache as derived files.", ex); | ||
| } catch (NoCurrentCaseException ex) { | ||
| logger.log(Level.WARNING, "No open case!", ex); | ||
| } | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| ExtractThumbcache_module_name=Thumbcache Analyzer | ||
| Thumbcache_Files_Not_Found=Thumbcache files not found | ||
| Thumbcache_process_error_executing_export_thumbcache_program=Error running thumbcache program |
Uh oh!
There was an error while loading. Please reload this page.