README.forensics (23488B)
1 heif -- heif_test.heic, heif_test.avif 2 Generated by contrib/gen_heif_testdata.sh (python3 only, deterministic). 3 Hand-constructed ISO base media file format containers: the box tree, 4 the item structures and the property associations are exactly what 5 ISO/IEC 14496-12, ISO/IEC 23008-12 and the AV1 Image File Format 6 specify, but the 'mdat' payload is a placeholder rather than a real 7 HEVC or AV1 bitstream, so the files identify but do not decode. That 8 is on purpose: the plugin is a container parser and the values it must 9 report are then known exactly. 10 11 heif_test.heic (916 bytes): major brand 'heic'. Three items -- the 12 primary image (id 1), a thumbnail (id 2, tied to the primary by an 13 'iref' of type 'thmb') and an 'Exif' metadata item (id 3). The 'ipco' 14 property container holds the thumbnail's 16x12 'ispe' *first* and the 15 primary item's 96x64 'ispe' second, so a parser that reports the first 16 'ispe' it finds gets the wrong answer and only a correct 17 pitm -> ipma -> ipco walk reports 96x64. Also carries pixi (10 bit), 18 irot (90 degrees), imir, colr nclx with transfer 16 (PQ, i.e. HDR) and 19 an hvcC shell. 20 21 heif_test.avif (631 bytes): major brand 'mif1' with 'avif' only among 22 the compatible brands, which is what several AVIF writers emit; both 23 the MIME type and the codec therefore have to come from that list. 24 One image item, 128x72, no 'pixi', so the bit depth has to be taken 25 from the 'av1C' configuration record. 26 27 Licence: CC0. No third party content. 28 29 webp -- webp_test.webp 30 Generated by contrib/gen_webp_testdata.sh (needs ffmpeg with libwebp, 31 Debian package "ffmpeg", plus python3; deterministic). 32 33 webp_test.webp (752 bytes): an extended (VP8X) animated WebP carrying 34 every optional chunk the container defines -- ICCP, ANIM, three ANMF 35 frames, EXIF and XMP -- with the alpha, animation, ICC, EXIF and XMP 36 feature flags set. Canvas 40x24. The three frames hold a genuine 37 lossless VP8L bitstream encoded by libwebp from a solid colour 38 generated by lavfi (nothing is read from disk); libwebp reads the 39 result back as a valid 3 frame RGBA animation. Frame durations are 40 100, 150 and 250 ms, summing to 500 ms. The ICC (136 bytes), EXIF and 41 XMP payloads are placeholders: the plugin only reports that they are 42 present and parsing them is the exiv2 plugin's job. 43 44 Licence: CC0. No third party content. 45 46 sqlite -- sqlite_test.db 47 SQLite 3 database, 5120 bytes. Generated by contrib/gen_sqlite_testdata.sh 48 with the sqlite3 CLI: 1 KiB pages, UTF-8, incremental auto-vacuum, WAL 49 journal mode, application_id 1279613012 (= "LEXT"), user_version 4242, two 50 tables and an index, with rows deleted and an index dropped so that a 51 freelist page survives. The generator then rewrites the SQLite version at 52 header offset 96 to 3045001 ("SQLite 3.45.1") and makes version-valid-for 53 agree with the change counter, so the file does not depend on the sqlite3 54 that produced it. test_sqlite asserts on the page size, journal mode, 55 change counter, page count, free space, schema cookie, user_version, 56 vacuum mode, encoding, application id (numeric and as its four-character 57 tag), writer version and logical size. Licence: CC0 / public domain. 58 59 tar -- tar_test.tar 60 POSIX ustar archive, 10240 bytes. Generated by contrib/gen_tar_testdata.sh 61 using Python's tarfile with every ownership field set explicitly: 62 uname "forensic", gname "analysts", uid 1000, gid 100, plus one member 63 owned by root/root. Contains a directory, three regular files, a symlink, 64 a hard link and one member whose 107-character path has to be split across 65 the ustar prefix field. Modification times are hard-coded (1700000000 and 66 1710506096); nothing is read from the machine that runs the generator. 67 test_tar asserts on the format, member count, summed member size, newest 68 mtime, both owners and groups, both uid/gid pairs, the five distinct 69 permission masks, all seven member names and both link targets. 70 Licence: CC0 / public domain. 71 72 iso9660 -- iso9660_test.iso 73 ISO 9660 (ECMA-119) image, 43008 bytes = 21 sectors. Generated by 74 contrib/gen_iso9660_testdata.sh, which builds the volume descriptor set 75 by hand with Python struct rather than calling genisoimage or xorriso: a 76 mastering program stamps its own name and the current time into the 77 descriptors, which would be neither reproducible nor assertable. Layout is 78 16 zero sectors of system area, then the primary volume descriptor, an 79 El Torito boot record, a Joliet supplementary descriptor (escape "%/E"), 80 the set terminator and one root directory extent whose "." record carries 81 SUSP "SP" and Rock Ridge "RR" entries. This is over the 32 KB guideline 82 and cannot be smaller: ECMA-119 puts the first descriptor at byte 32768. 83 test_iso9660 asserts on the system/volume/publisher/preparer/application 84 identifiers, the copyright, abstract and bibliographic file identifiers, 85 the block size, volume size, volume set size, all four dec-datetime fields 86 and the Joliet, Rock Ridge and El Torito detections. 87 Licence: CC0 / public domain. 88 89 90 geotiff -- geotiff_test.tif 91 Generated by contrib/gen_geotiff_testdata.sh (python3 only, 92 deterministic; no gdal-bin needed). Hand-built with struct rather 93 than produced by gdal_translate: GDAL's own output is several KB and 94 its numbers are whatever the projection happens to give, whereas a 95 hand-built file can be 1 KB and can use round numbers for the 96 tiepoint and the pixel scale so that the bounding box the plugin 97 derives from them is an exact constant to assert on. 98 99 geotiff_test.tif (1099 bytes): a classic little-endian TIFF 6.0 100 baseline image, 16x16, 8 bit greyscale, uncompressed, with the 101 GeoTIFF 1.1 tags for EPSG:32633 (WGS 84 / UTM zone 33N). 102 ModelPixelScaleTag is 10 m, ModelTiepointTag maps raster (0,0) to 103 model (500000, 5400000), so the extent is exactly 104 500000,5399840,500160,5400000 -- a value that can only come out right 105 if the tiepoint, the scale and the image dimensions were all read and 106 combined. GeoKeyDirectoryTag carries eight keys covering all three 107 TIFFTagLocation cases the specification allows (inline SHORT and 108 '|'-terminated substrings of GeoAsciiParamsTag), and GDAL's two 109 private tags (GDAL_METADATA with three <Item> elements, GDAL_NODATA) 110 are present so the provenance scan is exercised too. Byte order, 111 BigTIFF and the ModelTransformationTag path are covered by variants 112 built during development but not committed, since asserting on them 113 would only duplicate this file's expectations. 114 115 Licence: CC0. No third party content. 116 117 diskimage (QCOW2 / VMDK / VHD / VHDX) 118 ------------------------------- 119 120 Generated by contrib/gen_diskimage_testdata.sh (needs qemu-utils and 121 python3). qemu-img is the producer for all three; python3 only 122 overwrites the fields qemu-img randomises per run, so re-running the 123 script gives byte-identical files. 124 125 diskimage_test.qcow2 (1792 bytes): a QCOW2 version 3 differencing 126 image, 1 MiB virtual, 512 byte clusters, backed by 127 "diskimage_base.qcow2" (which the script creates in a temporary 128 directory and throws away -- the plugin never opens it, only reports 129 its name). Created with lazy_refcounts=on and compression_type=zstd 130 so that the compatible-feature and incompatible-feature paths and the 131 version 3 compression type byte are all exercised. Untouched 132 qemu-img output; the QCOW2 header has no per-run fields. 133 134 diskimage_test.vmdk (16384 bytes): a monolithicSparse VMDK, 1 MiB 135 virtual, 64 KiB grains, backed by "diskimage_base.vmdk". Covers both 136 the binary sparse extent header and the text descriptor embedded in 137 it at offset 512. The CID and parentCID qemu-img picks at random are 138 rewritten to 1f2e3d4c and 5a6b7c8d and the descriptor slot is padded 139 back to its original length, so no offset in the header changes. 140 TRUNCATED from the 64 KiB qemu-img writes to 16 KiB: everything past 141 the descriptor is zeroed grain tables. The file is therefore no 142 longer a mountable VMDK, but the header (80 bytes) and the descriptor 143 (offset 512, 10 KiB) are intact and are all the plugin reads. 144 145 diskimage_test.vhdx (4096 bytes): the file identifier block of a 146 VHDX, with the UTF-16LE creator string overwritten with a fixed value 147 so the test does not depend on the local qemu version. TRUNCATED, 148 and unavoidably so: [MS-VHDX] fixes the header at 64 KiB and the 149 region table at 192 KiB, and requires the metadata region to start at 150 a 1 MiB boundary, so the smallest conformant VHDX is over 2 MiB. The 151 test can only assert the signature-level items; the plugin's region 152 table, metadata table and parent locator walk was checked by hand 153 against full qemu-img output and against a hand-built conformant 154 image with a parent locator and a non-zero LogGuid. 155 156 There is no diskimage_test.vhd: the plugin reads the Microsoft VHD 157 ("conectix") footer as well, but src/plugins/Makefile.am has no 158 EXTRA_DIST entry for one. Run the generator with GEN_FULL=1 to get 159 a deterministic sample plus the untruncated VMDK and VHDX. 160 161 Licence: CC0. The images contain no data at all, only headers. 162 163 pecoff -- pecoff_test.exe, pecoff_test.dll 164 Generated by contrib/gen_pecoff_testdata.sh. The .exe needs the 165 Debian package "gcc-mingw-w64-x86-64"; the .dll needs only python3. 166 Both are deterministic: the linker is told not to insert a timestamp 167 and the script writes a fixed one afterwards. 168 169 pecoff_test.exe (4608 bytes): a real x86-64 PE32+ image built by 170 mingw-w64 from a three line program, linked without the C runtime 171 (-nostdlib) so that its whole import table is KERNEL32.dll!ExitProcess 172 and the import hash is therefore a value the test can spell out -- 173 MD5("kernel32.exitprocess"), which is what pefile computes for the 174 same file. Carries a VERSIONINFO resource written by windres, which 175 is what exercises the resource directory walk and the nested, 176 32-bit-aligned, UTF-16 StringFileInfo parser. TimeDateStamp is set to 177 1700000000 (2023-11-14T22:13:20Z) after linking. 178 179 pecoff_test.dll (3584 bytes): assembled from the PE specification with 180 python3, because mingw cannot produce the two artifacts that matter 181 most here. A Rich header (Microsoft's linker writes those and nothing 182 else does) masked with the key 0x1A2B3C4D and holding four 183 (product id, build, use count) triples; a CodeView RSDS record naming 184 C:\Users\builder\source\repos\pecoff_test\x64\Release\pecoff_test.pdb 185 with a fixed GUID and age; an export directory with three named 186 exports; a certificate table; and a .pack section filled with SHA-256 187 output so that its entropy lands where a packed section's would. The 188 certificate table is not a signature and verifies as nothing -- it is 189 the shortest DER that gives the commonName scan something to find, and 190 the name it carries is "GNU libextractor test signer". Verified 191 against pefile: machine, subsystem, Rich values, CodeView record and 192 export names all read back as intended. 193 194 Licence: CC0. No third party content. 195 196 lnk -- lnk_test.lnk 197 Generated by contrib/gen_lnk_testdata.sh (python3 only, deterministic). 198 199 lnk_test.lnk (706 bytes): a Windows shell link assembled from 200 [MS-SHLLINK] rather than produced by Windows, so that every value the 201 test asserts is a constant the generator owns. It carries a 202 LinkTargetIDList (two shell items) that the parser has to step over 203 without looking at, a LinkInfo whose VolumeID has drive type "fixed", 204 serial 1A2B-3C4D and the unicode volume label EVIDENCE, and whose 205 target path is split across LocalBasePathUnicode 206 ("C:\Users\analyst\Desktop\") and CommonPathSuffixUnicode 207 ("evidence.txt") so that the join is tested; UTF-16 string data for 208 all five optional strings; and a TrackerDataBlock naming the machine 209 "lab-ws-07" whose droid file identifier 5f3e2d1c-a4b6-11d2-8ae7- 210 001b44113ab7 is a version 1 UUID carrying the MAC address 211 00:1b:44:11:3a:b7. The three FILETIMEs in the header are the 212 target's, and all differ: 2021-03-04T05:06:07Z (creation), 213 2021-06-15T14:30:00Z (write) and 2022-07-08T09:10:11Z (access). 214 215 Cross-checked with liblnk's lnkinfo (20240423), which reads back the 216 same timestamps, serial, label, paths, strings and tracker GUIDs. 217 218 Licence: CC0. No third party content. The machine name, MAC address 219 and user name are invented. 220 221 plist -- plist_binary.plist, plist_xml.plist 222 Generated by contrib/gen_plist_testdata.sh (python3 only, using the 223 standard library's plistlib; deterministic). Both files hold the same 224 dictionary written once in each representation, so the two solution 225 sets in test_plist.c differ only where the formats genuinely do. 226 227 The dictionary mixes the keys of an application bundle Info.plist 228 (CFBundleIdentifier, CFBundleShortVersionString, DTPlatformName, 229 DTXcode, BuildMachineOSBuild, ...) with those of an iOS backup 230 Info.plist (DeviceName, ProductType, SerialNumber, LastBackupDate), 231 which is the forensic case the plugin exists for, plus one integer 232 under an unrecognised key so the "key: value" fallback is exercised. 233 234 plist_binary.plist (613 bytes): bplist00, 32 objects, two byte offsets 235 and one byte object references. NSHumanReadableCopyright is 236 "Copyright (C) 2026 Grüße" with a copyright sign and two umlauts, so 237 plistlib has to store it as a UTF-16BE string object (marker 0x6n) -- 238 the string decoder most likely to be got wrong, and the one the ASCII 239 path (0x5n) cannot fake. LastBackupDate is a date object (0x33), an 240 eight byte big-endian IEEE double counting seconds from 2001-01-01, 241 and only comes back as 2024-03-14T15:09:26Z if both the epoch shift 242 and the byte order were handled. 243 244 plist_xml.plist (1126 bytes): the same dictionary as a DTD-bound XML 245 document, which is what the bounded <key>/value scan reads. 246 247 The parser was also checked against the XML property lists shipped by 248 Debian's cmake and qt6 packages, and against a binary plist produced 249 by libplist's plistutil (an implementation independent of plistlib, 250 and one that interleaves the key and value object indices rather than 251 writing them in two runs). Those files are not committed. 252 253 Licence: CC0. Every identifier, serial number and device name in the 254 file is invented. 255 256 id3 -- id3_test.mp3 257 Generated by contrib/gen_id3_testdata.sh. Debian packages needed: 258 python3, python3-mutagen and lame. Deterministic. 259 260 id3_test.mp3 (7546 bytes): one second of a 440 Hz sine at 44100 Hz, 261 mono, written as a WAV by python and encoded by LAME 3.100 at -V 5 262 --vbr-new. LAME is the encoder rather than ffmpeg on purpose: ffmpeg 263 writes its own Xing/LAME tag stamped "Lavf lame", which does not carry 264 the lowpass, ReplayGain, preset or VBR method fields that make the 265 LAME tag the fingerprint it is. Note that the encoder version string 266 ("LAME3.100") and the settings summary derived from that tag are 267 asserted on literally, so regenerating with a different LAME release 268 means updating those two expectations. 269 270 The tag is ID3v2.4 and exercises all four text encodings in one file: 271 TIT2 is UTF-8, TPE1 is UTF-16 with a byte order mark, TALB is 272 ISO-8859-1, and all three carry characters outside ASCII, so a plugin 273 that passed the bytes through would produce invalid UTF-8 rather than 274 quietly wrong text. TCON is stored as "(52)" and has to come back 275 resolved to "Electronic". The USLT and APIC frames are both longer 276 than 127 bytes, which is where the synchsafe frame size of ID3v2.4 277 first differs from the plain big-endian integer of 2.3 -- everything 278 after them only parses if that was handled correctly. The provenance 279 frames are TENC, TSSE, TOWN, TFLT, TMED, a UFID with a MusicBrainz 280 style owner, a PRIV owned by com.apple.iTunes, two URL frames and a 281 TXXX with the description "apID". The attached picture is a 208 byte 282 PNG built by the generator with zlib. 283 284 A hand-written ID3v1 tag follows the audio. Every one of its fields 285 is deliberately different from its ID3v2 counterpart, so only the two 286 it alone supplies -- the year 1999 and the comment -- may reach the 287 caller; anything else showing up means the gap filling is broken. 288 289 The parser was also checked during development against files written 290 by three other producers, none of them committed: mutagen at ID3v2.3 291 (plain frame sizes, UTF-16 text), ffmpeg's own ID3v2.3 writer and its 292 "Lavf"/"Lavc" Xing tag, a hand-built ID3v2.2 tag with three character 293 frame identifiers, a hand-built unsynchronised ID3v2.3 tag, an 294 ID3v1-only file, a stereo file (which puts the Xing tag at offset 36 295 rather than 21) and a CBR file (an "Info" tag rather than "Xing"). 296 297 Licence: CC0. The audio is a generated sine tone; the licensee 298 address, purchase account, MusicBrainz identifier and every name in 299 the tag are invented. 300 301 apk -- apk_test.apk, apk_test.jar 302 Generated by contrib/gen_apk_testdata.sh (python3 only, deterministic). 303 304 apk_test.apk (4723 bytes): a hand-built Android package. The 305 AndroidManifest.xml is real binary XML written out from the chunk 306 layout in AOSP's ResourceTypes.h -- a UTF-16LE string pool (which is 307 what aapt emits for the manifest), a resource map with the attribute 308 resource ids read off a real aapt2 build, and START/END_ELEMENT 309 chunks. It declares package org.gnu.libextractor.apktest, 310 versionCode 1719, versionName 1.19.0, compileSdkVersion 33, 311 minSdkVersion 21, targetSdkVersion 33 and five uses-permission 312 elements. The <application> element mixes literal and reference 313 valued attributes on purpose: android:label is a literal string and 314 must be reported, android:icon and android:networkSecurityConfig are 315 resource references and a bare @0x7f... must not be reported as a 316 title. The archive also holds three classes*.dex stubs, three 317 lib/<abi>/ stubs (arm64-v8a, armeabi-v7a, x86_64), a META-INF JAR 318 manifest (so that the plugin has to prefer the Android manifest), a 319 v1 signature under the alias ANDROIDD, and an APK Signing Block with 320 v2 and v3 identifiers spliced in between the last entry and the 321 central directory. 322 323 apk_test.jar (3108 bytes): a JAR whose META-INF/MANIFEST.MF carries 324 the JDK identification (Created-By, Build-Jdk, Build-Jdk-Spec), the 325 Implementation-*/Specification-* triples, the OSGi Bundle-* headers 326 and a Class-Path. Implementation-Title is 98 bytes long and is 327 therefore folded across two lines, so reading it back proves the 328 continuation lines are unfolded. 329 330 Both archives are signed by the same PKCS#7 block, a throwaway 331 two-element chain ("libextractor test CA" issuing "libextractor test 332 signer", serial 0x1719) generated once with openssl and embedded in 333 the generator as base64; the private keys were discarded, so the 334 signature protects nothing. The certificate authority comes first 335 inside the certificates set, which is what makes the test prove that 336 the signer is resolved through the SignerInfo's issuerAndSerialNumber 337 rather than simply taken from the first certificate. 338 339 Licence: CC0. No third party content; every name, package 340 identifier and certificate subject is invented. 341 342 ebook -- ebook_test.epub, ebook_test.mobi 343 Generated by contrib/gen_ebook_testdata.sh (python3 only, 344 deterministic). Calibre is deliberately not used. 345 346 ebook_test.epub (2645 bytes): an OCF container with the uncompressed 347 `mimetype' member first as the specification requires, a 348 META-INF/container.xml pointing at OEBPS/content.opf, and an EPUB 3 349 package document with rich Dublin Core: title, creator, publisher, 350 language, date, two subjects, a description indented across three 351 lines (so that reading it back proves the white space collapsing), a 352 rights field containing an & entity, one dc:identifier with 353 opf:scheme="ISBN" and one that is a urn:uuid, a dc:contributor with 354 opf:role="bkp" naming the tool, <meta name="calibre:timestamp">, 355 <meta name="generator"> and <meta property="dcterms:modified">. The 356 manifest holds seven <item> entries and the spine three <itemref> 357 entries, so the entry count also proves the two are told apart. A 358 META-INF/encryption.xml (font obfuscation) is present. 359 360 ebook_test.mobi (946 bytes): a hand-built Palm database, type/creator 361 BOOKMOBI, three records. The PDB creation and modification times are 362 written in the 1904 epoch (2026-01-01 and 2026-02-01). Record 0 363 holds a PalmDOC header with encryption type 1, a 232 byte MOBI header 364 declaring UTF-8 text and the EXTH flag, and an EXTH header with 365 seventeen records covering author, publisher, description, ISBN, 366 subject, published date, contributor (the tool), rights, ASIN, 367 creator software triple, watermark, cdetype, last update time, 368 updated title and language, followed by the full name. 369 370 Licence: CC0. No third party content; the book, its author, its 371 ISBN and its watermark are invented. 372 373 gpx / kml / mbox 374 ---------------- 375 376 Generated by contrib/gen_gpx_testdata.sh, contrib/gen_kml_testdata.sh 377 and contrib/gen_mbox_testdata.sh. All three are plain text written 378 out literally by a POSIX shell heredoc -- no tools, no timestamps 379 taken from the clock, byte-for-byte reproducible. 380 381 gpx_test.gpx (2146 bytes): a GPX 1.1 track with creator="Garmin eTrex 382 30" (the field that names the recording device, and the one the 383 plugin also reports as a device model), a full <metadata> block with 384 name, desc carrying an & entity, author name and a split 385 <email id= domain=>, copyright with a <license>, two <link href=>, 386 a <time> of 2024-03-15T07:12:00Z, keywords, and an explicit <bounds>. 387 Two <wpt>, a <rte> of two <rtept> and one <trkseg> of five <trkpt> 388 with elevations 408.2-430.7 m and times from 07:12:33 to 07:24:53, 389 which is where the 0:12:20 duration and the 1215 m haversine sum in 390 the test come from. The coordinates are a walk through central 391 Zurich, invented, not anybody's real track. 392 393 kml_test.kml (2027 bytes): an OGC KML 2.2 Document over the same 394 coordinates, so that the two plugins can be compared -- note that the 395 tuples are lon,lat,alt, the reverse of the GPX attribute order, which 396 is what the bounding box assertion actually tests. It has a 397 <description> whose HTML sits in a CDATA section, an atom:author and 398 atom:link, a <TimeStamp>, a <LookAt> (the viewer's position, which 399 the plugin reports as a comment rather than as the data's position), 400 three Placemarks (two Points and a LineString) and a <NetworkLink> 401 whose <href> points at a remote host. 402 403 mbox_test.mbox (3504 bytes): three messages in mbox (mboxo) form. 404 The first carries a three-hop Received chain with two IPv4 literals 405 and one [IPv6:...] literal, an X-Originating-IP, a DKIM-Signature 406 with d=example.com, an Authentication-Results, X-Mailer, an RFC 2047 407 From and Subject (base64/UTF-8 and quoted-printable/ISO-8859-1 in one 408 field), In-Reply-To and References, and a multipart/mixed body with 409 two attachments -- one with a quoted filename and one that only 410 exists as RFC 2231 filename*0/filename*1 continuation segments. 411 Date: Tue, 12 Mar 2024 09:41:07 +0100 is Unix time 1710232867. The 412 third message's body holds a `>From ' line, the mboxo escaping, so 413 the message count staying at three proves the separator scan is not 414 fooled by it. 415 416 Licence: CC0. All names, addresses and hosts are from the RFC 2606 / 417 RFC 5737 / RFC 3849 reserved ranges; the coordinates and the mail are 418 invented.