diff --git a/Core/nbproject/project.properties b/Core/nbproject/project.properties index a55cb7e02c..9cdbb08f9e 100644 --- a/Core/nbproject/project.properties +++ b/Core/nbproject/project.properties @@ -2,7 +2,8 @@ file.reference.jdom-2.0.5-contrib.jar=release/modules/ext/jdom-2.0.5-contrib.jar file.reference.jdom-2.0.5.jar=release/modules/ext/jdom-2.0.5.jar file.reference.jython-standalone-2.7.0.jar=release/modules/ext/jython-standalone-2.7.0.jar file.reference.jython.jar-1=release/modules/ext/jython.jar -file.reference.metadata-extractor-2.6.2.jar=release/modules/ext/metadata-extractor-2.6.2.jar +file.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1.jar +file.reference.opencv-248.jar=release/modules/ext/opencv-248.jar file.reference.Rejistry-1.0-SNAPSHOT.jar=release/modules/ext/Rejistry-1.0-SNAPSHOT.jar file.reference.sevenzipjbinding-AllPlatforms.jar=release/modules/ext/sevenzipjbinding-AllPlatforms.jar file.reference.sevenzipjbinding.jar=release/modules/ext/sevenzipjbinding.jar @@ -10,12 +11,13 @@ file.reference.sqlite-jdbc-3.8.11.jar=release/modules/ext/sqlite-jdbc-3.8.11.jar file.reference.StixLib.jar=release/modules/ext/StixLib.jar file.reference.tika-core-1.2.jar=release/modules/ext/tika-core-1.2.jar file.reference.Tsk_DataModel.jar=release/modules/ext/Tsk_DataModel.jar -file.reference.xmpcore.jar=release/modules/ext/xmpcore.jar +file.reference.xmpcore-5.1.2.jar=release/modules/ext/xmpcore-5.1.2.jar javac.source=1.8 javac.compilerargs=-Xlint -Xlint:-serial license.file=../LICENSE-2.0.txt nbm.homepage=http://www.sleuthkit.org/ nbm.module.author=Brian Carrier nbm.needs.restart=true +source.reference.metadata-extractor-2.8.1.jar=release/modules/ext/metadata-extractor-2.8.1-src.zip!/Source/ spec.version.base=10.3 diff --git a/Core/nbproject/project.xml b/Core/nbproject/project.xml index 440fdaa0bc..c8089bda6c 100644 --- a/Core/nbproject/project.xml +++ b/Core/nbproject/project.xml @@ -203,10 +203,26 @@ org.sleuthkit.autopsy.report org.sleuthkit.datamodel + + ext/xmpcore-5.1.2.jar + release/modules/ext/xmpcore-5.1.2.jar + ext/jdom-2.0.5.jar release/modules/ext/jdom-2.0.5.jar + + ext/StixLib.jar + release/modules/ext/StixLib.jar + + + ext/sqlite-jdbc-3.8.11.jar + release/modules/ext/sqlite-jdbc-3.8.11.jar + + + ext/opencv-248.jar + release/modules/ext/opencv-248.jar + ext/Rejistry-1.0-SNAPSHOT.jar release/modules/ext/Rejistry-1.0-SNAPSHOT.jar @@ -219,34 +235,18 @@ ext/jython-standalone-2.7.0.jar release/modules/ext/jython-standalone-2.7.0.jar - - ext/StixLib.jar - release/modules/ext/StixLib.jar - - - ext/opencv-248.jar - release/modules/ext/opencv-248.jar - - - ext/sqlite-jdbc-3.8.11.jar - release/modules/ext/sqlite-jdbc-3.8.11.jar - ext/sevenzipjbinding-AllPlatforms.jar release/modules/ext/sevenzipjbinding-AllPlatforms.jar - - ext/metadata-extractor-2.6.2.jar - release/modules/ext/metadata-extractor-2.6.2.jar - - - ext/xmpcore.jar - release/modules/ext/xmpcore.jar - ext/tika-core-1.2.jar release/modules/ext/tika-core-1.2.jar + + ext/metadata-extractor-2.8.1.jar + release/modules/ext/metadata-extractor-2.8.1.jar + ext/jdom-2.0.5-contrib.jar release/modules/ext/jdom-2.0.5-contrib.jar diff --git a/Core/release/modules/ext/metadata-extractor-2.6.2.jar b/Core/release/modules/ext/metadata-extractor-2.6.2.jar deleted file mode 100755 index 68426ac059..0000000000 Binary files a/Core/release/modules/ext/metadata-extractor-2.6.2.jar and /dev/null differ diff --git a/Core/release/modules/ext/metadata-extractor-2.8.1.jar b/Core/release/modules/ext/metadata-extractor-2.8.1.jar new file mode 100755 index 0000000000..a5fe48640b Binary files /dev/null and b/Core/release/modules/ext/metadata-extractor-2.8.1.jar differ diff --git a/Core/release/modules/ext/xmpcore-5.1.2.jar b/Core/release/modules/ext/xmpcore-5.1.2.jar new file mode 100755 index 0000000000..ecd5db142e Binary files /dev/null and b/Core/release/modules/ext/xmpcore-5.1.2.jar differ diff --git a/Core/release/modules/ext/xmpcore.jar b/Core/release/modules/ext/xmpcore.jar deleted file mode 100755 index 884c2dd57f..0000000000 Binary files a/Core/release/modules/ext/xmpcore.jar and /dev/null differ diff --git a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java index 7c186e93b4..ee9e3fa366 100644 --- a/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java +++ b/Core/src/org/sleuthkit/autopsy/modules/exif/ExifParserFileIngestModule.java @@ -22,10 +22,21 @@ import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.ImageProcessingException; import com.drew.lang.GeoLocation; import com.drew.lang.Rational; +import com.drew.metadata.Directory; import com.drew.metadata.Metadata; +import com.drew.metadata.MetadataException; +import com.drew.metadata.exif.makernotes.CanonMakernoteDirectory; import com.drew.metadata.exif.ExifIFD0Directory; import com.drew.metadata.exif.ExifSubIFDDirectory; import com.drew.metadata.exif.GpsDirectory; +import com.drew.metadata.exif.makernotes.CasioType1MakernoteDirectory; +import com.drew.metadata.exif.makernotes.FujifilmMakernoteDirectory; +import com.drew.metadata.exif.makernotes.KodakMakernoteDirectory; +import com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory; +import com.drew.metadata.exif.makernotes.PanasonicMakernoteDirectory; +import com.drew.metadata.exif.makernotes.PentaxMakernoteDirectory; +import com.drew.metadata.exif.makernotes.SanyoMakernoteDirectory; +import com.drew.metadata.exif.makernotes.SonyType1MakernoteDirectory; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; @@ -33,6 +44,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.HashSet; +import java.util.List; +import java.util.TimeZone; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import org.openide.util.NbBundle; @@ -47,6 +60,8 @@ import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE; +import org.sleuthkit.datamodel.Content; +import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.ReadContentInputStream; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskData; @@ -63,10 +78,13 @@ public final class ExifParserFileIngestModule implements FileIngestModule { private final IngestServices services = IngestServices.getInstance(); private final AtomicInteger filesProcessed = new AtomicInteger(0); private volatile boolean filesToFire = false; + private volatile boolean facesDetected = false; + private final List listOfFacesDetectedArtifacts = new ArrayList<>(); private long jobId; private static final IngestModuleReferenceCounter refCounter = new IngestModuleReferenceCounter(); private FileTypeDetector fileTypeDetector; private final HashSet supportedMimeTypes = new HashSet<>(); + private TimeZone timeZone = null; ExifParserFileIngestModule() { supportedMimeTypes.add("audio/x-wav"); @@ -103,9 +121,16 @@ public final class ExifParserFileIngestModule implements FileIngestModule { // update the tree every 1000 files if we have EXIF data that is not being being displayed final int filesProcessedValue = filesProcessed.incrementAndGet(); - if ((filesToFire) && (filesProcessedValue % 1000 == 0)) { - services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); - filesToFire = false; + if ((filesProcessedValue % 1000 == 0)) { + if (filesToFire) { + services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); + filesToFire = false; + } + if (facesDetected) { + services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED, listOfFacesDetectedArtifacts)); + listOfFacesDetectedArtifacts.clear(); + facesDetected = false; + } } //skip unsupported @@ -125,11 +150,24 @@ public final class ExifParserFileIngestModule implements FileIngestModule { bin = new BufferedInputStream(in); Collection attributes = new ArrayList<>(); - Metadata metadata = ImageMetadataReader.readMetadata(bin, true); + Metadata metadata = ImageMetadataReader.readMetadata(bin); // Date - ExifSubIFDDirectory exifDir = metadata.getDirectory(ExifSubIFDDirectory.class); + ExifSubIFDDirectory exifDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); if (exifDir != null) { + + // set the timeZone for the current datasource. + if (timeZone == null) { + try { + Content dataSource = f.getDataSource(); + if ((dataSource != null) && (dataSource instanceof Image)) { + Image image = (Image) dataSource; + timeZone = TimeZone.getTimeZone(image.getTimeZone()); + } + } catch (TskCoreException ex) { + logger.log(Level.INFO, "Error getting time zones", ex); //NON-NLS + } + } Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL); if (date != null) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED.getTypeID(), ExifParserModuleFactory.getModuleName(), date.getTime() / 1000)); @@ -137,7 +175,7 @@ public final class ExifParserFileIngestModule implements FileIngestModule { } // GPS Stuff - GpsDirectory gpsDir = metadata.getDirectory(GpsDirectory.class); + GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class); if (gpsDir != null) { GeoLocation loc = gpsDir.getGeoLocation(); if (loc != null) { @@ -147,14 +185,14 @@ public final class ExifParserFileIngestModule implements FileIngestModule { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), longitude)); } - Rational altitude = gpsDir.getRational(GpsDirectory.TAG_GPS_ALTITUDE); + Rational altitude = gpsDir.getRational(GpsDirectory.TAG_ALTITUDE); if (altitude != null) { attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID(), ExifParserModuleFactory.getModuleName(), altitude.doubleValue())); } } // Device info - ExifIFD0Directory devDir = metadata.getDirectory(ExifIFD0Directory.class); + ExifIFD0Directory devDir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); if (devDir != null) { String model = devDir.getString(ExifIFD0Directory.TAG_MODEL); if (model != null && !model.isEmpty()) { @@ -167,6 +205,11 @@ public final class ExifParserFileIngestModule implements FileIngestModule { } } + if (containsFace(metadata)) { + listOfFacesDetectedArtifacts.add(f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED)); + facesDetected = true; + } + // Add the attributes, if there are any, to a new artifact if (!attributes.isEmpty()) { BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF); @@ -199,6 +242,121 @@ public final class ExifParserFileIngestModule implements FileIngestModule { } } + /** + * Checks if this metadata contains any tags related to facial information. + * NOTE: Cases with this metadata containing tags like enabled red-eye + * reduction settings, portrait settings, etc are also assumed to contain + * facial information. The method returns true. The return value of this + * method does NOT guarantee actual presence of face. + * + * @param metadata the metadata which needs to be parsed for possible facial + * information. + * + * @return returns true if the metadata contains any tags related to facial + * information. + */ + private boolean containsFace(Metadata metadata) { + Directory d = metadata.getFirstDirectoryOfType(CanonMakernoteDirectory.class); + if (d != null) { + if (d.containsTag(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_1) + && d.getString(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_1) != null) { + return true; + } + if (d.containsTag(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_2) + && d.getString(CanonMakernoteDirectory.TAG_FACE_DETECT_ARRAY_2) != null) { + return true; + } + } + + d = metadata.getFirstDirectoryOfType(CasioType1MakernoteDirectory.class); + if (d != null) { + try { + if (d.containsTag(CasioType1MakernoteDirectory.TAG_FLASH_MODE) + && d.getInt(CasioType1MakernoteDirectory.TAG_FLASH_MODE) == 0x04) { //0x04 = "Red eye reduction" + return true; + } + } catch (MetadataException ex) { + // move on and check next directory + } + } + + d = metadata.getFirstDirectoryOfType(FujifilmMakernoteDirectory.class); + if (d != null) { + if (d.containsTag(FujifilmMakernoteDirectory.TAG_FACES_DETECTED) + && d.getString(FujifilmMakernoteDirectory.TAG_FACES_DETECTED) != null) { + return true; + } + } + + d = metadata.getFirstDirectoryOfType(KodakMakernoteDirectory.class); + if (d != null) { + try { + if (d.containsTag(KodakMakernoteDirectory.TAG_FLASH_MODE) + && d.getInt(KodakMakernoteDirectory.TAG_FLASH_MODE) == 0x03) { //0x03 = "Red Eye" + return true; + } + } catch (MetadataException ex) { + /// move on and check next directory + } + } + + d = metadata.getFirstDirectoryOfType(NikonType2MakernoteDirectory.class); + if (d != null) { + if (d.containsTag(NikonType2MakernoteDirectory.TAG_SCENE_MODE) + && d.getString(NikonType2MakernoteDirectory.TAG_SCENE_MODE) != null + && (d.getString(NikonType2MakernoteDirectory.TAG_SCENE_MODE).equals("BEST FACE") // NON-NLS + || (d.getString(NikonType2MakernoteDirectory.TAG_SCENE_MODE).equals("SMILE")))) { // NON-NLS + return true; + } + } + + d = metadata.getFirstDirectoryOfType(PanasonicMakernoteDirectory.class); + if (d != null) { + if (d.containsTag(PanasonicMakernoteDirectory.TAG_FACES_DETECTED) + && d.getString(PanasonicMakernoteDirectory.TAG_FACES_DETECTED) != null) { + return true; + } + } + + d = metadata.getFirstDirectoryOfType(PentaxMakernoteDirectory.class); + if (d != null) { + try { + if (d.containsTag(PentaxMakernoteDirectory.TAG_FLASH_MODE) + && d.getInt(PentaxMakernoteDirectory.TAG_FLASH_MODE) == 6) { // 6 = Red-eye Reduction + return true; + } + } catch (MetadataException ex) { + // move on and check next directory + } + } + + d = metadata.getFirstDirectoryOfType(SanyoMakernoteDirectory.class); + if (d != null) { + if (d.containsTag(SanyoMakernoteDirectory.TAG_MANUAL_FOCUS_DISTANCE_OR_FACE_INFO) + && d.getString(SanyoMakernoteDirectory.TAG_MANUAL_FOCUS_DISTANCE_OR_FACE_INFO) != null) { + return true; + } + } + + d = metadata.getFirstDirectoryOfType(SonyType1MakernoteDirectory.class); + if (d != null) { + try { + if (d.containsTag(SonyType1MakernoteDirectory.TAG_AF_MODE) + && d.getInt(SonyType1MakernoteDirectory.TAG_AF_MODE) == 15) { //15 = "Face Detected" + return true; + } + if (d.containsTag(SonyType1MakernoteDirectory.TAG_EXPOSURE_MODE) + && d.getInt(SonyType1MakernoteDirectory.TAG_EXPOSURE_MODE) == 14) { //14 = "Smile shutter" + return true; + } + } catch (MetadataException ex) { + // move on and check next directory + } + } + + return false; + } + /** * Checks if should try to attempt to extract exif. Currently checks if JPEG * image (by signature) @@ -225,10 +383,15 @@ public final class ExifParserFileIngestModule implements FileIngestModule { public void shutDown() { // We only need to check for this final event on the last module per job if (refCounter.decrementAndGet(jobId) == 0) { + timeZone = null; if (filesToFire) { //send the final new data event services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF)); } + if (facesDetected) { + //send the final new data event + services.fireModuleDataEvent(new ModuleDataEvent(ExifParserModuleFactory.getModuleName(), BlackboardArtifact.ARTIFACT_TYPE.TSK_FACE_DETECTED, listOfFacesDetectedArtifacts)); + } } } -} +} \ No newline at end of file