All the meat of the new module classes

This commit is contained in:
U-BASIS\dsmyda 2018-08-08 17:43:16 -04:00
parent d74c8b6212
commit 8a3daf4110
4 changed files with 185 additions and 128 deletions

View File

@ -16,14 +16,12 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.modules.interestingdatabases; package org.sleuthkit.autopsy.modules.databaseselector;
/** /**
* * Cell types used by the CellTypeDetector class
* @author dsmyda
*/ */
public enum CellType { public enum CellType {
EMAIL, EMAIL,
NOT_INTERESTING NOT_INTERESTING
} }

View File

@ -16,18 +16,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.modules.interestingdatabases; package org.sleuthkit.autopsy.modules.databaseselector;
import org.apache.commons.validator.routines.EmailValidator;
/** /**
* * Determines the type of a database cell (EMAIL, PHONE, GPS COORD, MAC ADDRESS)
* @author dsmyda
*/ */
public class CellTypeDetector { public class CellTypeDetector {
private final EmailValidator emailValidator;
public CellTypeDetector() { public CellTypeDetector() {
emailValidator = EmailValidator.getInstance();
} }
public CellType getType(String cell) { public CellType getType(String cell) {
return CellType.EMAIL; if(emailValidator.isValid(cell)) {
return CellType.EMAIL;
} else {
return CellType.NOT_INTERESTING;
}
} }
} }

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.modules.interestingdatabases; package org.sleuthkit.autopsy.modules.databaseselector;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -39,6 +39,7 @@ import org.sleuthkit.autopsy.ingest.IngestJobContext;
import org.sleuthkit.autopsy.ingest.IngestServices; import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent; import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector.FileTypeDetectorInitException;
import org.sleuthkit.autopsy.sqlitereader.SQLiteReader; import org.sleuthkit.autopsy.sqlitereader.SQLiteReader;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
@ -46,197 +47,248 @@ import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
/** /**
* * Parses database files and marks them as having interesting records (emails,
* @author dsmyda * phone numbers, mac addresses, gps coordinates).
*/ */
public class InterestingDatabasesIngestModule extends FileIngestModuleAdapter { public class DatabaseSelectorIngestModule extends FileIngestModuleAdapter {
private static final String SUPPORTED_MIME_TYPE = "application/x-sqlite3"; private static final String SUPPORTED_MIME_TYPE = "application/x-sqlite3";
private static final String MODULE_NAME = InterestingDatabasesIngestModuleFactory.getModuleName(); private static final String MODULE_NAME = DatabaseSelectorIngestModuleFactory.getModuleName();
private final IngestServices services = IngestServices.getInstance(); private final IngestServices services = IngestServices.getInstance();
private final Logger logger = services.getLogger( private final Logger logger = services.getLogger(
InterestingDatabasesIngestModuleFactory.getModuleName()); DatabaseSelectorIngestModuleFactory.getModuleName());
private FileTypeDetector fileTypeDetector; private FileTypeDetector fileTypeDetector;
private CellTypeDetector cellTypeDetector; private CellTypeDetector cellTypeDetector;
private Blackboard blackboard; private Blackboard blackboard;
private String localDiskPath;
/**
*
* @param context
* @throws org.sleuthkit.autopsy.ingest.IngestModule.IngestModuleException
*/
@NbBundle.Messages({ @NbBundle.Messages({
"CannotRunFileTypeDetection=Unable to initialize file type detection.", "DatabaseSelectorIngestModule.CannotRunFileTypeDetection=Unable to initialize file type detection.",
}) })
@Override @Override
public void startUp(IngestJobContext context) throws IngestModuleException { public void startUp(IngestJobContext context) throws IngestModuleException {
try { try {
fileTypeDetector = new FileTypeDetector(); fileTypeDetector = new FileTypeDetector();
cellTypeDetector = new CellTypeDetector(); cellTypeDetector = new CellTypeDetector();
} catch (FileTypeDetector.FileTypeDetectorInitException ex) { } catch (FileTypeDetectorInitException ex) {
throw new IngestModuleException(Bundle.CannotRunFileTypeDetection(), ex); throw new IngestModuleException(Bundle.DatabaseSelectorIngestModule_CannotRunFileTypeDetection(), ex);
} }
} }
/**
*
* @param file
* @return
*/
@Override @Override
@NbBundle.Messages({
"InterestingDatabasesIngestModule.indexError.message=Failed to index interesting artifact hit for keyword search."
})
public ProcessResult process(AbstractFile file) { public ProcessResult process(AbstractFile file) {
if(getBlackboardInstanceFromServices().equals(ProcessResult.ERROR)) {
return ProcessResult.ERROR;
}
//Qualify the MIMEType
String dataSourceMimeType = fileTypeDetector.getMIMEType(file);
if(SUPPORTED_MIME_TYPE.equals(dataSourceMimeType)) {
if(createLocalDiskPathFromCurrentCase(file).equals(ProcessResult.ERROR)) {
return ProcessResult.ERROR;
}
try (SQLiteReader sqliteReader = new SQLiteReader(file, localDiskPath)){
try {
Set<CellType> databaseCellTypes = getCellTypesInDatabase(sqliteReader);
//No interesting hits, don't flag this database, skip artifact creation.
if(!databaseCellTypes.isEmpty()) {
String cellTypesComment = createCellTypeCommentString(databaseCellTypes);
try {
BlackboardArtifact artifact = createArtifactGivenCellTypes(
file, cellTypesComment);
indexArtifactAndFireModuleDataEvent(artifact);
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error creating blackboard artifact", ex); //NON-NLS
}
}
} catch(SQLException ex) {
logger.log(Level.WARNING, "Error attempting to read sqlite "
+ "file in DatabaseSelectorIngestModule", ex);
}
} catch (ClassNotFoundException | SQLException | IOException |
NoCurrentCaseException | TskCoreException ex) {
logger.log(Level.SEVERE, "Cannot initialize sqliteReader class "
+ "in DatabaseSelectorIngestModule", ex);
return ProcessResult.ERROR;
}
}
//Whether we successfully read the sqlite database or determined the mime
//type is not supported, the process is OK.
return ProcessResult.OK;
}
/**
* Get a pointer to the current case blackboard for indexing of artifacts
*
* @return ProcessResult indicating a success or failure in getting current case
*/
private ProcessResult getBlackboardInstanceFromServices() {
try { try {
blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard(); blackboard = Case.getCurrentCaseThrows().getServices().getBlackboard();
} catch (NoCurrentCaseException ex) { } catch (NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS logger.log(Level.SEVERE, "Exception while getting open case.", ex); //NON-NLS
return ProcessResult.ERROR; return ProcessResult.ERROR;
} }
String dataSourceMimeType = fileTypeDetector.getMIMEType(file); return ProcessResult.OK;
if(SUPPORTED_MIME_TYPE.equals(dataSourceMimeType)) { }
String localDiskPath; /**
try { * Generates a local disk path for abstract file contents to be copied.
localDiskPath = Case.getCurrentCaseThrows() * All database sources must be copied to local disk to be opened by
.getTempDirectory() + File.separator + file.getName(); * SQLiteReader
} catch (NoCurrentCaseException ex) { *
// TODO -- personal note log about current case being closed or * @param file The database abstract file
//current case not existing. * @return ProcessResult indicating a success or failure in creating a disk path
Exceptions.printStackTrace(ex); */
return ProcessResult.ERROR; private ProcessResult createLocalDiskPathFromCurrentCase(AbstractFile file) {
} try {
localDiskPath = Case.getCurrentCaseThrows()
.getTempDirectory() + File.separator + file.getName();
try (SQLiteReader sqliteReader = } catch (NoCurrentCaseException ex) {
new SQLiteReader(file, localDiskPath)){ // TODO -- personal note log about current case being closed or
//current case not existing.
Set<CellType> aggregatedCellTypes = cellTypesInDatabase(sqliteReader); Exceptions.printStackTrace(ex);
String cellTypesMessage = aggregatedCellTypes.toString() return ProcessResult.ERROR;
.replace("]", "")
.replace("[", "");
try {
BlackboardArtifact artifact = createArtifactGivenCellTypes(
file, cellTypesMessage);
try {
// index the artifact for keyword search
blackboard.indexArtifact(artifact);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + //NON-NLS
artifact.getArtifactID(), ex);
MessageNotifyUtil.Notify.error(
Bundle.InterestingDatabasesIngestModule_indexError_message(),
artifact.getDisplayName());
}
services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME,
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT,
Collections.singletonList(artifact)));
} catch (TskCoreException ex) {
logger.log(Level.SEVERE, "Error posting to the blackboard", ex); //NOI18N NON-NLS
}
} catch (SQLException ex) {
//Could be thrown in init or close.. need to fix.
Exceptions.printStackTrace(ex);
} catch (ClassNotFoundException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
} catch (NoCurrentCaseException ex) {
Exceptions.printStackTrace(ex);
} catch (TskCoreException ex) {
Exceptions.printStackTrace(ex);
}
} }
return ProcessResult.OK; return ProcessResult.OK;
} }
/** /**
* Creates and populates a set of all CellTypes in the sqlite database
* *
* @param sqliteReader * @param sqliteReader Reader currently connected to database file
* @return * @return A Set of distinct CellTypes
* @throws SQLException * @throws SQLException Caught during attempting to read sqlite database
*/ */
private Set<CellType> cellTypesInDatabase(SQLiteReader sqliteReader) throws SQLException { private Set<CellType> getCellTypesInDatabase(SQLiteReader sqliteReader) throws SQLException {
Map<String, String> tables = sqliteReader.getTableSchemas(); Map<String, String> tables = sqliteReader.getTableSchemas();
Set<CellType> aggregateCellTypes = new TreeSet<>(); Set<CellType> aggregateCellTypes = new TreeSet<>();
//Aggregate all cell types from each table //Aggregate cell types from all tables
for(String tableName : tables.keySet()) { for(String tableName : tables.keySet()) {
addCellTypesInTable(sqliteReader, tableName, aggregateCellTypes); aggregateCellTypes.addAll(getCellTypesInTable(sqliteReader, tableName));
} }
return aggregateCellTypes; return aggregateCellTypes;
} }
/** /**
* Creates and populates a set of all CellTypes in a sqlite table
* *
* @param sqliteReader * @param sqliteReader Reader currently connected to database file
* @param table * @param tableName database table to be opened and read
* @return * @return Set of all unique cell types in table
* @throws SQLException Caught during attempting to read sqlite database
*/ */
private void addCellTypesInTable(SQLiteReader sqliteReader, String tableName, private Set<CellType> getCellTypesInTable(SQLiteReader sqliteReader,
Set<CellType> aggregateCellTypes) throws SQLException { String tableName) throws SQLException {
Set<CellType> tableCellTypes = new TreeSet<>();
List<Map<String, Object>> tableValues = sqliteReader.getRowsFromTable(tableName); List<Map<String, Object>> tableValues = sqliteReader.getRowsFromTable(tableName);
//Aggregate cell types from all table rows
tableValues.forEach((row) -> { tableValues.forEach((row) -> {
addCellTypeInRow(row, aggregateCellTypes); tableCellTypes.addAll(getCellTypesInRow(row));
}); });
return tableCellTypes;
} }
/** /**
* Creates and populates a set of all CellTypes in a table row
* *
* @param row * @param row Table row is represented as a column-value map
* @return * @return
*/ */
private void addCellTypeInRow(Map<String, Object> row, private Set<CellType> getCellTypesInRow(Map<String, Object> row) {
Set<CellType> aggregateCellTypes) { Set<CellType> rowCellTypes = new TreeSet<>();
//Aggregate cell types from a row
row.values().forEach((Object cell) -> { row.values().forEach((Object cell) -> {
if(cell instanceof String) { if(cell instanceof String) {
aggregateCellTypes.add(cellTypeDetector.getType( (String) cell)); CellType type = cellTypeDetector.getType((String) cell);
if(!type.equals(CellType.NOT_INTERESTING)) {
rowCellTypes.add(type);
}
} }
}); });
return rowCellTypes;
} }
/** /**
* Creates a comma seperated string of all the cell types found in a database
* file. Used as the comment string for the blackboard artifact.
* *
* @param type * @param databaseCellTypes The set of all database cell types detected
* @return
*/
private String createCellTypeCommentString(Set<CellType> databaseCellTypes) {
return databaseCellTypes.toString().replace("]", "").replace("[", "");
}
/**
* Initializes a new interesting file hit artifact and provides name and
* comment attributes
*
* @param file The database abstract file
* @param cellTypesComment String of all the cell types found in a database
* file
* @return Interesting file hit artifact
* @throws TskCoreException Thrown if the abstract file cannot create a blackboard
* artifact
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"InterestingDatabasesIngestModule.FlagDatabases.setName=Selectors identified" "DatabaseSelectorIngestModule.FlagDatabases.setName=Selectors identified"
}) })
private BlackboardArtifact createArtifactGivenCellTypes(AbstractFile file, private BlackboardArtifact createArtifactGivenCellTypes(AbstractFile file,
String cellTypesMessage) throws TskCoreException { String cellTypesComment) throws TskCoreException {
BlackboardArtifact artifact = file.newArtifact( BlackboardArtifact artifact = file.newArtifact(
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT); BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT);
BlackboardAttribute setNameAttribute = new BlackboardAttribute( BlackboardAttribute setNameAttribute = new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_SET_NAME, MODULE_NAME,
Bundle.InterestingDatabasesIngestModule_FlagDatabases_setName()); Bundle.DatabaseSelectorIngestModule_FlagDatabases_setName());
artifact.addAttribute(setNameAttribute); artifact.addAttribute(setNameAttribute);
BlackboardAttribute commentAttribute = new BlackboardAttribute( BlackboardAttribute commentAttribute = new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME,
cellTypesMessage); cellTypesComment);
artifact.addAttribute(commentAttribute); artifact.addAttribute(commentAttribute);
return artifact; return artifact;
} }
/** /**
* Pass the artifact to blackboard for indexing and fire module data event
* in the IngestServices
* *
* @param artifact Blackboard artifact created for the interesting file hit
*/ */
@Override @NbBundle.Messages({
public void shutDown() { "DatabaseSelectorIngestModule.indexError.message="
+ "Failed to index interesting file hit artifact for keyword search."
})
private void indexArtifactAndFireModuleDataEvent(BlackboardArtifact artifact) {
try {
// index the artifact for keyword search
blackboard.indexArtifact(artifact);
} catch (Blackboard.BlackboardException ex) {
logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + //NON-NLS
artifact.getArtifactID(), ex);
MessageNotifyUtil.Notify.error(
Bundle.DatabaseSelectorIngestModule_indexError_message(),
artifact.getDisplayName());
}
services.fireModuleDataEvent(new ModuleDataEvent(MODULE_NAME,
BlackboardArtifact.ARTIFACT_TYPE.TSK_INTERESTING_FILE_HIT,
Collections.singletonList(artifact)));
} }
} }

View File

@ -16,7 +16,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.modules.interestingdatabases; package org.sleuthkit.autopsy.modules.databaseselector;
import org.sleuthkit.autopsy.coreutils.Version; import org.sleuthkit.autopsy.coreutils.Version;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
@ -27,19 +27,18 @@ import org.sleuthkit.autopsy.ingest.IngestModuleFactoryAdapter;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
/** /**
* * A factory that creates a DatabaseSelectorModule to parse database files and
* @author dsmyda * mark them as having interesting records (emails, phone numbers, mac addresses, gps coordinates).
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
"InterestingDatabasesIngestModuleFactory.FlagDatabases.moduleName=Interesting Databases", "DatabaseSelectorIngestModuleFactory.FlagDatabases.moduleName=Database Selector",
"InterestingDatabasesIngestModuleFactory.FlagDatabases.moduleDesc.text=Flags databases with interesting items (emails, phone numbers, gps coordinates, ip/mac addresses)" "DatabaseSelectorIngestModuleFactory.FlagDatabases.moduleDesc.text=Flag databases with interesting items (emails, phone numbers, gps coordinates, ip/mac addresses)"
}) })
@ServiceProvider(service = IngestModuleFactory.class) @ServiceProvider(service = IngestModuleFactory.class)
public class InterestingDatabasesIngestModuleFactory extends IngestModuleFactoryAdapter { public class DatabaseSelectorIngestModuleFactory extends IngestModuleFactoryAdapter {
static String getModuleName() { static String getModuleName() {
return Bundle.InterestingDatabasesIngestModuleFactory_FlagDatabases_moduleName(); return Bundle.DatabaseSelectorIngestModuleFactory_FlagDatabases_moduleName();
} }
@Override @Override
@ -49,7 +48,7 @@ public class InterestingDatabasesIngestModuleFactory extends IngestModuleFactory
@Override @Override
public String getModuleDescription() { public String getModuleDescription() {
return Bundle.InterestingDatabasesIngestModuleFactory_FlagDatabases_moduleDesc_text(); return Bundle.DatabaseSelectorIngestModuleFactory_FlagDatabases_moduleDesc_text();
} }
@Override @Override
@ -64,6 +63,6 @@ public class InterestingDatabasesIngestModuleFactory extends IngestModuleFactory
@Override @Override
public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) { public FileIngestModule createFileIngestModule(IngestModuleIngestJobSettings ingestOptions) {
return new InterestingDatabasesIngestModule(); return new DatabaseSelectorIngestModule();
} }
} }