Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions RecentActivity/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<copy todir="${basedir}/release/markmckinnon/" >
<fileset dir="${thirdparty.dir}/markmckinnon/" />
</copy>
<copy todir="${basedir}/release/thumbcache_parser/" >
<fileset dir="${thirdparty.dir}/thumbcache_parser/" />
</copy>
</target>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ ExtractSru_process_errormsg_find_software_hive=Unable to find SOFTWARE HIVE file
ExtractSru_process_errormsg_find_srudb_dat=Unable to find srudb.dat file
ExtractSru_process_errormsg_write_software_hive=Unable to write SOFTWARE HIVE file
ExtractSru_process_errormsg_write_srudb_dat=Unable to write srudb.dat file
ExtractThumbcache_error_finding_program=Could not find thumbcache_viewer_cmd.exe program
ExtractThumbcache_module_name=Thumbcache Analyzer
ExtractWebAccountType.role.admin=Administrator role
ExtractWebAccountType.role.moderator=Moderator role
ExtractWebAccountType.role.user=User role
Expand Down Expand Up @@ -260,4 +262,7 @@ ExtractWebAccountType.parentModuleName=Recent Activity
Shellbag_Artifact_Display_Name=Shell Bags
Shellbag_Key_Attribute_Display_Name=Key
Shellbag_Last_Write_Attribute_Display_Name=Last Write
Thumbcache_Files_Not_Found=Thumbcache files not found
Thumbcache_Files_TSK_Error=TSK error searching for thumbcache files
Thumbcache_process_error_executing_export_thumbcache_program=Error running thumbcache program
UsbDeviceIdMapper.parseAndLookup.text=Product: {0}
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);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} 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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 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 -10

Repository: 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 java

Repository: 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 -5

Repository: 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 -u

Repository: 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 -200

Repository: 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.java

Repository: 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.java

Repository: 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 -30

Repository: 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.java

Repository: 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 -50

Repository: 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.java

Repository: 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 -C2

Repository: 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 java

Repository: 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 -i

Repository: 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 java

Repository: 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 java

Repository: 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 -20

Repository: 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 -30

Repository: sleuthkit/autopsy

Length of output: 43


Switch to the 2-argument findFiles() overload to avoid unclear semantics with an empty parent-path filter.

This is the only place in the codebase using findFiles(dataSource, pattern, "") with an empty string as the third argument. All other extraction modules (ExtractSru, ExtractIE, ExtractRegistry, ExtractPrefetch, ExtractRecycleBin, etc.) use the 2-argument overload findFiles(dataSource, pattern) instead, which doesn't attempt a parent-path filter. For consistency and clarity, use:

thumbcacheFiles = fileManager.findFiles(dataSource, "thumbcache_%.db");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@RecentActivity/src/org/sleuthkit/autopsy/recentactivity/ExtractThumbcache.java`
around lines 149 - 150, Replace the 3-argument call to fileManager.findFiles
that passes an empty parent-path filter with the 2-argument overload to avoid
ambiguous semantics: change the call in ExtractThumbcache (the assignment to
thumbcacheFiles using fileManager.findFiles(dataSource, "thumbcache_%.db", ""))
to use fileManager.findFiles(dataSource, "thumbcache_%.db") so it matches other
extractors and does not apply a parent-path filter.

} 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);
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public void startUp(IngestJobContext context) throws IngestModuleException {
Extract webAccountType = new ExtractWebAccountType(context);
Extract messageDomainType = new DomainCategoryRunner(context);
Extract jumpList = new ExtractJumpLists(context);
Extract thumbcache = new ExtractThumbcache(context);

extractors.add(recycleBin);
extractors.add(jumpList);
Expand All @@ -98,6 +99,7 @@ public void startUp(IngestJobContext context) throws IngestModuleException {
extractors.add(zoneInfo); // this needs to run after the web browser modules
extractors.add(sru);
extractors.add(prefetch);
extractors.add(thumbcache);
extractors.add(messageDomainType);

browserExtractors.add(chrome);
Expand Down
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
Binary file not shown.
Loading