diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 34d1248..e5a7441 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -30,7 +30,7 @@ jobs: - id: coverage uses: actions-rs/grcov@v0.1 - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: token: ${{secrets.CODECOV_TOKEN}} files: ${{ steps.coverage.outputs.report }} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9bc037d..3d5c361 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + [[package]] name = "ahash" version = "0.8.11" @@ -97,9 +108,9 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] @@ -120,6 +131,15 @@ version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "assert-json-diff" version = "2.0.2" @@ -147,9 +167,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c90a406b4495d129f00461241616194cb8a032c8d1c53c657f0961d5f8e0498" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "flate2", "futures-core", @@ -177,9 +197,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", @@ -301,6 +321,27 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + [[package]] name = "camino" version = "1.1.7" @@ -334,9 +375,14 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] [[package]] name = "cfg-if" @@ -381,21 +427,31 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", ] [[package]] -name = "clap-markdown" -version = "0.1.3" +name = "clap-markdown-dfir" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325f50228f76921784b6d9f2d62de6778d834483248eefecd27279174797e579" +checksum = "ab533b2e36fe7d2b5ef22ad06eb610e5232993bc15a7b8d2e49ace9d64f300df" dependencies = [ "clap", ] @@ -412,9 +468,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", @@ -425,18 +481,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.2" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" +checksum = "d2020fa13af48afc65a9a87335bda648309ab3d154cd03c7ff95b378c7ed39c4" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -446,9 +502,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clio" @@ -465,6 +521,27 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "color-print" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee543c60ff3888934877a5671f45494dd27ed4ba25c6670b9a7576b7ed7a8c0" +dependencies = [ + "color-print-proc-macro", +] + +[[package]] +name = "color-print-proc-macro" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ff1a80c5f3cb1ca7c06ffdd71b6a6dd6d8f896c42141fbd43f50ed28dcdb93" +dependencies = [ + "nom", + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "colorchoice" version = "1.0.1" @@ -506,6 +583,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + [[package]] name = "core-foundation" version = "0.9.4" @@ -531,6 +614,21 @@ dependencies = [ "libc", ] +[[package]] +name = "crc" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + [[package]] name = "crc32fast" version = "1.4.2" @@ -666,6 +764,12 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "deflate64" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" + [[package]] name = "deranged" version = "0.3.11" @@ -686,9 +790,20 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "dfir-toolkit" -version = "0.10.4" +version = "0.11.0" dependencies = [ "anyhow", "assert-json-diff", @@ -699,10 +814,11 @@ dependencies = [ "chrono", "chrono-tz", "clap", - "clap-markdown", + "clap-markdown-dfir", "clap-verbosity-flag", "clap_complete", "clio", + "color-print", "colored", "colored_json", "csv", @@ -744,9 +860,11 @@ dependencies = [ "term-table", "termsize", "thiserror", + "time", "tokio", "tokio-async-drop", "winstructs", + "zip", ] [[package]] @@ -804,6 +922,18 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", ] [[package]] @@ -1183,9 +1313,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "glob" @@ -1249,6 +1379,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.12" @@ -1273,9 +1412,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "8720bf4c5bfb5b6c350840c4cd14b787bf00ed51c148c857fbf7a6ddb7062764" [[package]] name = "httpdate" @@ -1285,9 +1424,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -1343,6 +1482,124 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1351,12 +1608,14 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.5.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", + "smallvec", + "utf8_iter", ] [[package]] @@ -1388,6 +1647,15 @@ version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -1435,6 +1703,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -1495,6 +1772,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + [[package]] name = "lnk" version = "0.5.1" @@ -1519,12 +1802,28 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] + [[package]] name = "marvin32" version = "0.1.1" @@ -1561,6 +1860,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -1589,11 +1894,10 @@ checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e" [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1605,6 +1909,16 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nt_hive2" version = "4.2.1" @@ -1763,9 +2077,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" [[package]] name = "object" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -1847,9 +2161,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1896,6 +2210,16 @@ dependencies = [ "proc-macro-hack", ] +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2035,9 +2359,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -2160,9 +2484,9 @@ checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -2172,9 +2496,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -2183,9 +2507,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" @@ -2351,18 +2675,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.202" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", @@ -2414,6 +2738,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.8" @@ -2449,6 +2784,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simplelog" version = "0.12.2" @@ -2506,6 +2847,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -2535,17 +2882,23 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", "syn 2.0.66", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -2574,6 +2927,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2722,25 +3086,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ - "tinyvec_macros", + "displaydoc", + "zerovec", ] -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -2763,9 +3122,9 @@ checksum = "2e57fbf1da3f18c8a95469f8973c138b0a99f4ae761885c3646b0c61139b0522" [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", @@ -2841,49 +3200,46 @@ dependencies = [ "version_check", ] -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "url" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" @@ -3288,6 +3644,18 @@ dependencies = [ "thiserror", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + [[package]] name = "yansi" version = "0.5.1" @@ -3300,6 +3668,30 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +[[package]] +name = "yoke" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.34" @@ -3320,8 +3712,136 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", + "synstructure", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zerovec" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "zip" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "crossbeam-utils", + "deflate64", + "displaydoc", + "flate2", + "hmac", + "indexmap", + "lzma-rs", + "memchr", + "pbkdf2", + "rand", + "sha1", + "thiserror", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index ff816ec..40886f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,16 @@ [package] name = "dfir-toolkit" -version = "0.10.4" +version = "0.11.0" edition = "2021" authors = ["Jan Starke ", "Deborah Mahn "] description = "CLI tools for digital forensics and incident response" repository = "https://github.com/dfir-dd/dfir-toolkit" license = "GPL-3.0" +# this version is required, because earlier versions handle missing `created` +# timestamps as `Uncategorized`, instead of `Unsupported` +rust-version = "1.78" + [package.metadata.deb] maintainer-scripts = "scripts/maintainer" @@ -86,10 +90,15 @@ name = "pf2bodyfile" path = "src/bin/pf2bodyfile/main.rs" required-features = ["pf2bodyfile"] +[[bin]] +name = "zip2bodyfile" +path = "src/bin/zip2bodyfile/main.rs" +required-features = ["zip2bodyfile"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] -default = ["pol_export", "mactime2", "evtxtools", "regdump", "hivescan", "cleanhive", "ipgrep", "ts2date", "lnk2bodyfile", "pf2bodyfile"] -mactime2 = ["gzip", "elastic", "chrono-tz", "thiserror", "bitflags", "encoding_rs_io"] +default = ["pol_export", "mactime2", "evtxtools", "regdump", "hivescan", "cleanhive", "ipgrep", "ts2date", "lnk2bodyfile", "pf2bodyfile", "zip2bodyfile"] +mactime2 = ["gzip", "elastic", "chrono-tz", "thiserror", "bitflags", "encoding_rs_io", "color-print"] gzip = ["flate2"] elastic = ["elasticsearch", "tokio", "futures", "serde_json", "sha2", "base64", "num-traits", "num-derive", "strum", "strum_macros", "tokio-async-drop"] evtxtools = ["evtxscan", "evtxcat", "evtxls", "evtxanalyze", "evtx2bodyfile"] @@ -103,6 +112,7 @@ ipgrep = [] ts2date = ["regex"] lnk2bodyfile = ["lnk"] pf2bodyfile = ["num", "libc", "frnsc-prefetch", "forensic-rs"] +zip2bodyfile = ["zip", "time"] regdump = ["nt_hive2"] hivescan = ["nt_hive2"] @@ -123,7 +133,7 @@ winstructs = "0.3.0" lazy_static = "1.4" regex = {version = "1", optional=true} -clap-markdown = "0.1.3" +clap-markdown-dfir = "0.2.0" clap_complete = "4" clio = {version="0.3", features=["clap-parse"] } #clio = {path="../clio", features=["clap-parse"]} @@ -135,6 +145,7 @@ flate2 = {version="1", optional=true} thiserror = {version="1", optional=true} bitflags = {version="2", optional=true} encoding_rs_io = {version="0.1", optional=true} +color-print = {version="0.3.6", optional=true} # evtxtools dfirtk-eventdata = {version="0.1.2", optional=true} @@ -180,8 +191,12 @@ lnk = {version="0.5.1", optional=true} # pf2bodyfile libc = {version="0.2", optional=true} num = {version="0", optional=true} -frnsc-prefetch = {version=">=0.9", optional=true} -forensic-rs = {version=">=0.9.1", optional=true} +frnsc-prefetch = {version="0.13", optional=true} +forensic-rs = {version="0.13", optional=true} + +# zip2bodyfile +zip = {version="2.1.3", optional=true, features=["time"]} +time = {version="0.3.36", optional=true} [dev-dependencies] diff --git a/README.md b/README.md index 3244e6a..70f3dec 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ - [ ] [`regview`](https://github.com/janstarke/regview) - [x] [`ts2date`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/ts2date.md) - [ ] [`usnjrnl_dump`](https://github.com/janstarke/usnjrnl) + - [x] [`zip2bodyfile`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/zip2bodyfile.md) # Overview of timelining tools diff --git a/doc/cleanhive.md b/doc/cleanhive.md index 4548d2e..989060c 100644 --- a/doc/cleanhive.md +++ b/doc/cleanhive.md @@ -19,8 +19,8 @@ merges logfiles into a hive file ###### **Options:** * `-L`, `--log ` — transaction LOG file(s). This argument can be specified one or two times -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity * `-O`, `--output ` — name of the file to which the cleaned hive will be written Default value: `-` diff --git a/doc/es4forensics.md b/doc/es4forensics.md index ee1f07b..efef149 100644 --- a/doc/es4forensics.md +++ b/doc/es4forensics.md @@ -22,6 +22,9 @@ This crates provides structs and functions to insert timeline data into an elast ###### **Options:** * `--strict` — strict mode: do not only warn, but abort if an error occurs + + Possible values: `true`, `false` + * `-I`, `--index ` — name of the elasticsearch index * `-H`, `--host ` — server name or IP address of elasticsearch server @@ -38,12 +41,15 @@ This crates provides structs and functions to insert timeline data into an elast * `-k`, `--insecure` — omit certificate validation Default value: `false` + + Possible values: `true`, `false` + * `-U`, `--username ` — username for elasticsearch server Default value: `elastic` * `-W`, `--password ` — password for authenticating at elasticsearch -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/evtx2bodyfile.md b/doc/evtx2bodyfile.md index 3021022..edd4c05 100644 --- a/doc/evtx2bodyfile.md +++ b/doc/evtx2bodyfile.md @@ -25,8 +25,11 @@ creates bodyfile from Windows evtx files Possible values: `json`, `bodyfile` * `-S`, `--strict` — fail upon read error -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/evtxanalyze.md b/doc/evtxanalyze.md index ebb1dae..8a913e2 100644 --- a/doc/evtxanalyze.md +++ b/doc/evtxanalyze.md @@ -23,8 +23,8 @@ crate provide functions to analyze evtx files ###### **Options:** -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity @@ -36,7 +36,7 @@ generate a process tree ###### **Arguments:** -* `` — Name of the evtx file to parse +* `` — Name of the evtx file to parse (should be the path to "Security.evtx") ###### **Options:** @@ -58,12 +58,15 @@ display sessions ###### **Arguments:** -* `` — Names of the evtx files to parse +* `` — Names of the evtx files directory to parse. Be aware that this tool assumes some file names. If you renamed the files, session analysis wil not work correctly ###### **Options:** * `--include-anonymous` — include anonymous sessions + Possible values: `true`, `false` + + ## `evtxanalyze session` @@ -74,7 +77,7 @@ display one single session ###### **Arguments:** -* `` — Names of the evtx files to parse +* `` — Names of the evtx files directory to parse. Be aware that this tool assumes some file names. If you renamed the files, session analysis wil not work correctly * `` — Session ID diff --git a/doc/evtxcat.md b/doc/evtxcat.md index 5e30f51..b0c4b05 100644 --- a/doc/evtxcat.md +++ b/doc/evtxcat.md @@ -22,14 +22,17 @@ Display one or more events from an evtx file * `--max ` — filter: maximal event record identifier * `-i`, `--id ` — show only the one event with this record identifier * `-T`, `--display-table` — don't display the records in a table format + + Possible values: `true`, `false` + * `-F`, `--format ` — output format Default value: `xml` Possible values: `json`, `xml` -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/evtxls.md b/doc/evtxls.md index 2cb1abe..e3d5d96 100644 --- a/doc/evtxls.md +++ b/doc/evtxls.md @@ -21,7 +21,12 @@ Display one or more events from an evtx file * `-d`, `--delimiter ` — use this delimiter instead of generating fixed space columns * `-i`, `--include ` — List events with only the specified event ids, separated by ',' * `-x`, `--exclude ` — Exclude events with the specified event ids, separated by ',' -* `-c`, `--colors` — highlight interesting content using colors +* `-C`, `--color ` — highlight interesting content using colors + + Default value: `auto` + + Possible values: `auto`, `always`, `never` + * `-f`, `--from ` — hide events older than the specified date (hint: use RFC 3339 syntax) * `-t`, `--to ` — hide events newer than the specified date (hint: use RFC 3339 syntax) * `-r`, `--regex ` — highlight event data based on this regular expression @@ -56,8 +61,11 @@ Display one or more events from an evtx file * `-B`, `--hide-base-fields` — don't display any common event fields at all. This corresponds to specifying '--base-fields' without any values (which is not allowed, that's why there is this flag) Default value: `false` -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/evtxscan.md b/doc/evtxscan.md index b612996..21930ea 100644 --- a/doc/evtxscan.md +++ b/doc/evtxscan.md @@ -19,11 +19,14 @@ Find time skews in an evtx file ###### **Options:** * `-S`, `--show-records` — display also the contents of the records befor and after a time skew + + Possible values: `true`, `false` + * `-N`, `--negative-tolerance ` — negative tolerance limit (in seconds): time skews to the past below this limit will be ignored Default value: `5` -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/hivescan.md b/doc/hivescan.md index 0edfb85..ae0b69b 100644 --- a/doc/hivescan.md +++ b/doc/hivescan.md @@ -19,10 +19,13 @@ scans a registry hive file for deleted entries ###### **Options:** * `-L`, `--log ` — transaction LOG file(s). This argument can be specified one or two times -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity * `-b` — output as bodyfile format + Possible values: `true`, `false` + +
diff --git a/doc/ipgrep.md b/doc/ipgrep.md index c947391..09cbac3 100644 --- a/doc/ipgrep.md +++ b/doc/ipgrep.md @@ -28,8 +28,11 @@ search for IP addresses in text files * `-I`, `--ignore-ips ` — ignore any of the specified IP addresses. Values are delimited with comma * `-c`, `--colors` — highlight interesting content using colors -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/lnk2bodyfile.md b/doc/lnk2bodyfile.md index 3d528de..1daa9d7 100644 --- a/doc/lnk2bodyfile.md +++ b/doc/lnk2bodyfile.md @@ -20,8 +20,8 @@ Parse Windows LNK files and create bodyfile output ###### **Options:** -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/mactime2.md b/doc/mactime2.md index e038dc3..de57075 100644 --- a/doc/mactime2.md +++ b/doc/mactime2.md @@ -8,10 +8,16 @@ This document contains the help content for the `mactime2` command-line program. ## `mactime2` -replacement for `mactime` +Replacement for `mactime` **Usage:** `mactime2 [OPTIONS]` +IMPORTANT + +Note that POSIX specifies that all UNIX timestamps are UTC timestamps. It is +up to you to ensure that the bodyfile only contains UNIX timestamps that +comply with the POSIX standard. + ###### **Options:** * `-b ` — path to input file or '-' for stdin (files ending with .gz will be treated as being gzipped) @@ -22,16 +28,22 @@ replacement for `mactime` Possible values: `csv`, `txt`, `json`, `elastic` * `-d` — output as CSV instead of TXT. This is a conveniance option, which is identical to `--format=csv` and will be removed in a future release. If you specified `--format` and `-d`, the latter will be ignored + + Possible values: `true`, `false` + * `-j` — output as JSON instead of TXT. This is a conveniance option, which is identical to `--format=json` and will be removed in a future release. If you specified `--format` and `-j`, the latter will be ignored -* `-f`, `--from-timezone ` — name of offset of source timezone (or 'list' to display all possible values - Default value: `UTC` + Possible values: `true`, `false` + * `-t`, `--to-timezone ` — name of offset of destination timezone (or 'list' to display all possible values Default value: `UTC` * `--strict` — strict mode: do not only warn, but abort if an error occurs -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/pf2bodyfile.md b/doc/pf2bodyfile.md index 1e5e039..8229311 100644 --- a/doc/pf2bodyfile.md +++ b/doc/pf2bodyfile.md @@ -18,8 +18,12 @@ creates bodyfile from Windows Prefetch files ###### **Options:** -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-I` — show not only the executed files, but all references files -- such as libraries -- as well + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/pol_export.md b/doc/pol_export.md index 3d0f28d..20037b7 100644 --- a/doc/pol_export.md +++ b/doc/pol_export.md @@ -18,8 +18,8 @@ Exporter for Windows Registry Policy Files ###### **Options:** -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/regdump.md b/doc/regdump.md index 4c9edd0..5cb965a 100644 --- a/doc/regdump.md +++ b/doc/regdump.md @@ -20,10 +20,19 @@ parses registry hive files and prints a bodyfile * `-L`, `--log ` — transaction LOG file(s). This argument can be specified one or two times * `-b`, `--bodyfile` — print as bodyfile format + + Possible values: `true`, `false` + * `-I`, `--ignore-base-block` — ignore the base block (e.g. if it was encrypted by some ransomware) + + Possible values: `true`, `false` + * `-T`, `--hide-timestamps` — hide timestamps, if output is in reg format -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity diff --git a/doc/ts2date.md b/doc/ts2date.md index 94bc467..02fb7a3 100644 --- a/doc/ts2date.md +++ b/doc/ts2date.md @@ -23,8 +23,8 @@ replaces UNIX timestamps in a stream by a formatted date ###### **Options:** -* `-v`, `--verbose` — More output per occurrence -* `-q`, `--quiet` — Less output per occurrence +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity * `-f`, `--from-timezone ` — name of offset of source timezone (or 'list' to display all possible values Default value: `UTC` diff --git a/doc/zip2bodyfile.md b/doc/zip2bodyfile.md new file mode 100644 index 0000000..eb7107f --- /dev/null +++ b/doc/zip2bodyfile.md @@ -0,0 +1,36 @@ +# Command-Line Help for `zip2bodyfile` + +This document contains the help content for the `zip2bodyfile` command-line program. + +**Command Overview:** + +* [`zip2bodyfile`↴](#zip2bodyfile) + +## `zip2bodyfile` + +creates bodyfile from ZIP Archives based on the contained files and folders + +**Usage:** `zip2bodyfile [OPTIONS] [ZIP_FILES]...` + +###### **Arguments:** + +* `` — names of the archive files (commonly files with 'zip' extension) + +###### **Options:** + +* `--show-archive-name` — show the name of the archive in the bodyfile output + + Possible values: `true`, `false` + +* `-v`, `--verbose` — Increase logging verbosity +* `-q`, `--quiet` — Decrease logging verbosity + + + +
+ + + This document was generated automatically by + clap-markdown. + + diff --git a/scripts/update-md.sh b/scripts/update-md.sh index 1bd88d5..071f6fd 100755 --- a/scripts/update-md.sh +++ b/scripts/update-md.sh @@ -39,6 +39,7 @@ cat >README.md <<'EOF' - [ ] [`regview`](https://github.com/janstarke/regview) - [x] [`ts2date`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/ts2date.md) - [ ] [`usnjrnl_dump`](https://github.com/janstarke/usnjrnl) + - [x] [`zip2bodyfile`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/zip2bodyfile.md) # Overview of timelining tools diff --git a/src/bin/mactime2/application.rs b/src/bin/mactime2/application.rs index e4fffcd..8fb6cb2 100644 --- a/src/bin/mactime2/application.rs +++ b/src/bin/mactime2/application.rs @@ -3,6 +3,8 @@ use clap::ValueEnum; use clio::Input; use strum_macros::Display; +use crate::output::OldCsvOutput; + use super::bodyfile::{BodyfileDecoder, BodyfileReader, BodyfileSorter}; use super::cli::Cli; use super::error::MactimeError; @@ -21,26 +23,32 @@ enum InputFormat { } #[derive(ValueEnum, Clone, Display)] -pub (crate) enum OutputFormat { +pub(crate) enum OutputFormat { + /// Comma-Separated Values compliant to RFC 4180 #[strum(serialize = "csv")] Csv, + /// legacy text format, inherited from the old mactime #[strum(serialize = "txt")] Txt, + /// Javascript Object Notation #[strum(serialize = "json")] Json, + /// JSON-format to be used by elasticsearch #[cfg(feature = "elastic")] #[strum(serialize = "elastic")] Elastic, + + /// Use the old (non RFC compliant) CSV format that was used by legacy mactime. + #[strum(serialize = "old-csv")] + OldCsv, } -//#[derive(Builder)] pub struct Mactime2Application { format: OutputFormat, bodyfile: Input, - src_zone: Tz, dst_zone: Tz, strict_mode: bool, } @@ -52,7 +60,6 @@ impl Mactime2Application { ) -> Box>> { let options = RunOptions { strict_mode: self.strict_mode, - src_zone: self.src_zone, }; if matches!(self.format, OutputFormat::Json) { @@ -62,8 +69,12 @@ impl Mactime2Application { BodyfileSorter::default().with_receiver(decoder.get_receiver(), options); sorter = sorter.with_output(match self.format { - OutputFormat::Csv => Box::new(CsvOutput::new(self.src_zone, self.dst_zone)), - OutputFormat::Txt => Box::new(TxtOutput::new(self.src_zone, self.dst_zone)), + OutputFormat::OldCsv => { + Box::new(OldCsvOutput::new(std::io::stdout(), self.dst_zone)) + } + + OutputFormat::Csv => Box::new(CsvOutput::new(std::io::stdout(), self.dst_zone)), + OutputFormat::Txt => Box::new(TxtOutput::new(std::io::stdout(), self.dst_zone)), _ => panic!("invalid execution path"), }); Box::new(sorter) @@ -73,7 +84,6 @@ impl Mactime2Application { pub fn run(&self) -> anyhow::Result<()> { let options = RunOptions { strict_mode: self.strict_mode, - src_zone: self.src_zone, }; let mut reader = >::from(self.bodyfile.clone())?; @@ -106,7 +116,6 @@ impl From for Mactime2Application { Self { format, bodyfile: cli.input_file, - src_zone: cli.src_zone.into_tz().unwrap(), dst_zone: cli.dst_zone.into_tz().unwrap(), strict_mode: cli.strict_mode, } diff --git a/src/bin/mactime2/bodyfile/bodyfile_sorter.rs b/src/bin/mactime2/bodyfile/bodyfile_sorter.rs index d2f7ea6..e88d66a 100644 --- a/src/bin/mactime2/bodyfile/bodyfile_sorter.rs +++ b/src/bin/mactime2/bodyfile/bodyfile_sorter.rs @@ -1,7 +1,8 @@ -use dfir_toolkit::common::bodyfile::{Bodyfile3Line, BehavesLikeI64}; +use dfir_toolkit::common::bodyfile::{BehavesLikeI64, Bodyfile3Line}; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::{BTreeMap, HashSet}; +use std::io::{Stdout, Write}; use std::sync::mpsc::Receiver; use std::sync::Arc; use std::thread::JoinHandle; @@ -11,18 +12,21 @@ use crate::filter::{Joinable, RunOptions, Runnable, Sorter}; use super::MACBFlags; -pub trait Mactime2Writer: Send { - fn write(&self, timestamp: &i64, entry: &ListEntry) { - println!("{}", self.fmt(timestamp, entry)); - } - fn fmt(&self, timestamp: &i64, entry: &ListEntry) -> String; +pub trait Mactime2Writer: Send +where + W: Write + Send +{ + fn write_line(&mut self, timestamp: &i64, entry: &ListEntry) -> std::io::Result<()>; + + #[allow(dead_code)] + fn into_writer(self) -> W; } #[derive(Default)] pub struct BodyfileSorter { worker: Option>>, receiver: Option>, - output: Option>, + output: Option>>, } #[derive(Debug)] @@ -105,14 +109,14 @@ impl BodyfileSorter { self } - pub fn with_output(mut self, output: Box) -> Self { + pub fn with_output(mut self, output: Box>) -> Self { self.output = Some(output); self } fn worker( decoder: Receiver, - output: Box, + mut output: Box>, ) -> Result<(), MactimeError> { let mut entries: BTreeMap> = BTreeMap::new(); let mut names: HashSet<(String, String)> = HashSet::new(); @@ -189,7 +193,7 @@ impl BodyfileSorter { for (ts, entries_at_ts) in entries.iter() { for line in entries_at_ts { - output.write(ts, line); + output.write_line(ts, line)?; } } Ok(()) diff --git a/src/bin/mactime2/bodyfile/macb_flags.rs b/src/bin/mactime2/bodyfile/macb_flags.rs index 95a40b3..bd594d6 100644 --- a/src/bin/mactime2/bodyfile/macb_flags.rs +++ b/src/bin/mactime2/bodyfile/macb_flags.rs @@ -1,6 +1,7 @@ use std::fmt; use bitflags::bitflags; +use serde::Serialize; bitflags! { #[derive(PartialEq, Debug, Clone, Copy)] @@ -22,3 +23,12 @@ impl fmt::Display for MACBFlags { write!(f, "{}{}{}{}", m, a, c, b) } } + +impl Serialize for MACBFlags { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&format!("{self}")) + } +} diff --git a/src/bin/mactime2/cli.rs b/src/bin/mactime2/cli.rs index ce87c36..faab3df 100644 --- a/src/bin/mactime2/cli.rs +++ b/src/bin/mactime2/cli.rs @@ -13,9 +13,15 @@ const BODYFILE_HELP: &str = #[cfg(not(feature = "gzip"))] const BODYFILE_HELP: &str = "path to input file or '-' for stdin"; -/// replacement for `mactime` +const AFTER_HELP: &str = color_print::cstr!(r##"IMPORTANT + +Note that POSIX specifies that all UNIX timestamps are UTC timestamps. It is +up to you to ensure that the bodyfile only contains UNIX timestamps that +comply with the POSIX standard."##); + +/// Replacement for `mactime` #[derive(Parser)] -#[clap(name="mactime2", author, version, long_about = None)] +#[clap(name="mactime2", author, version, long_about = None, after_help=AFTER_HELP)] pub struct Cli { #[clap(short('b'), value_parser, value_hint=ValueHint::FilePath, default_value="-", help=BODYFILE_HELP, display_order(100))] @@ -30,20 +36,17 @@ pub struct Cli { )] pub(crate) output_format: Option, - /// output as CSV instead of TXT. This is a conveniance option, which is identical to `--format=csv` + /// output as CSV instead of TXT. This is a convenience option, which is identical to `--format=csv` /// and will be removed in a future release. If you specified `--format` and `-d`, the latter will be ignored. #[clap(short('d'), display_order(610))] + #[arg(group="csv")] pub(crate) csv_format: bool, - /// output as JSON instead of TXT. This is a conveniance option, which is identical to `--format=json` + /// output as JSON instead of TXT. This is a convenience option, which is identical to `--format=json` /// and will be removed in a future release. If you specified `--format` and `-j`, the latter will be ignored. #[clap(short('j'), display_order(620))] pub(crate) json_format: bool, - /// name of offset of source timezone (or 'list' to display all possible values - #[clap(short('f'), long("from-timezone"), display_order(300), default_value_t=TzArgument::Tz(Tz::UTC))] - pub src_zone: TzArgument, - /// name of offset of destination timezone (or 'list' to display all possible values #[clap(short('t'), long("to-timezone"), display_order(400), default_value_t=TzArgument::Tz(Tz::UTC))] pub dst_zone: TzArgument, diff --git a/src/bin/mactime2/error.rs b/src/bin/mactime2/error.rs index 81834c8..a4685bd 100644 --- a/src/bin/mactime2/error.rs +++ b/src/bin/mactime2/error.rs @@ -2,4 +2,12 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum MactimeError { + #[error("An IO Error has occurred: {0}")] + IoError(std::io::Error) +} + +impl From for MactimeError { + fn from(value: std::io::Error) -> Self { + Self::IoError(value) + } } \ No newline at end of file diff --git a/src/bin/mactime2/filter.rs b/src/bin/mactime2/filter.rs index bd186a0..29b677a 100644 --- a/src/bin/mactime2/filter.rs +++ b/src/bin/mactime2/filter.rs @@ -1,11 +1,8 @@ use std::sync::mpsc::{Sender, Receiver}; -use chrono_tz::Tz; - #[derive(Copy, Clone)] pub struct RunOptions { pub strict_mode: bool, - pub src_zone: Tz } pub trait Provider: Joinable { diff --git a/src/bin/mactime2/main.rs b/src/bin/mactime2/main.rs index 55769d3..34923e3 100644 --- a/src/bin/mactime2/main.rs +++ b/src/bin/mactime2/main.rs @@ -15,12 +15,11 @@ use dfir_toolkit::common::{FancyParser, TzArgument}; fn main() -> Result<()> { let cli: Cli = Cli::parse_cli(); - if cli.src_zone.is_list() || cli.dst_zone.is_list() { + if cli.dst_zone.is_list() { TzArgument::display_zones(); return Ok(()); } debug_assert!(cli.dst_zone.is_tz()); - debug_assert!(cli.src_zone.is_tz()); let app: Mactime2Application = cli.into(); diff --git a/src/bin/mactime2/output/csv_output.rs b/src/bin/mactime2/output/csv_output.rs index 7bf74c7..7323c31 100644 --- a/src/bin/mactime2/output/csv_output.rs +++ b/src/bin/mactime2/output/csv_output.rs @@ -1,36 +1,81 @@ +use std::io::Write; + use chrono_tz::Tz; +use csv::WriterBuilder; use dfir_toolkit::common::ForensicsTimestamp; +use serde::Serialize; -use crate::bodyfile::{ListEntry, Mactime2Writer}; +use crate::bodyfile::{ListEntry, MACBFlags, Mactime2Writer}; -pub(crate) struct CsvOutput { - src_zone: Tz, +pub(crate) struct CsvOutput +where + W: Write + Send, +{ dst_zone: Tz, + writer: csv::Writer, } -impl CsvOutput { - pub fn new(src_zone: Tz, dst_zone: Tz) -> Self { - Self { src_zone, dst_zone } +pub const CSV_DELIMITER: u8 = b','; + +impl CsvOutput +where + W: Write + Send, +{ + pub fn new(writer: W, dst_zone: Tz) -> Self { + Self { + dst_zone, + writer: WriterBuilder::new() + .delimiter(CSV_DELIMITER) + .has_headers(false) + .from_writer(writer), + } + } + #[allow(dead_code)] + pub fn with_writer(mut self, writer: W) -> Self + where + W: Write + Send + 'static, + { + self.writer = WriterBuilder::new().from_writer(writer); + self } } -impl Mactime2Writer for CsvOutput { - fn fmt(&self, timestamp: &i64, entry: &ListEntry) -> String { - let timestamp = ForensicsTimestamp::new(*timestamp, self.src_zone, self.dst_zone); - format!( - "{},{},{},{},{},{},{},\"{}\"", - timestamp, - entry.line.get_size(), - entry.flags, - entry.line.get_mode_as_string(), - entry.line.get_uid(), - entry.line.get_gid(), - entry.line.get_inode(), - entry.line.get_name() - ) +impl Mactime2Writer for CsvOutput +where + W: Write + Send, +{ + fn write_line(&mut self, timestamp: &i64, entry: &ListEntry) -> std::io::Result<()> { + let csv_line = CsvLine { + timestamp: ForensicsTimestamp::new(*timestamp, self.dst_zone), + size: entry.line.get_size(), + flags: entry.flags, + mode: entry.line.get_mode_as_string(), + uid: entry.line.get_uid(), + gid: entry.line.get_gid(), + inode: entry.line.get_inode(), + name: entry.line.get_name(), + }; + self.writer.serialize(csv_line)?; + Ok(()) + } + + fn into_writer(self) -> W { + self.writer.into_inner().unwrap() } } +#[derive(Serialize)] +struct CsvLine<'e> { + timestamp: ForensicsTimestamp, + size: &'e u64, + flags: MACBFlags, + mode: &'e str, + uid: &'e u64, + gid: &'e u64, + inode: &'e str, + name: &'e str, +} + #[cfg(test)] mod tests { use crate::bodyfile::ListEntry; @@ -42,6 +87,9 @@ mod tests { use chrono_tz::Tz; use chrono_tz::TZ_VARIANTS; use dfir_toolkit::common::bodyfile::Bodyfile3Line; + use std::io::BufRead; + use std::io::BufReader; + use std::io::Cursor; use std::sync::Arc; fn random_tz() -> Tz { @@ -52,7 +100,6 @@ mod tests { #[allow(non_snake_case)] #[test] fn test_correct_ts_UTC() { - let output = CsvOutput::new(Tz::UTC, Tz::UTC); for _ in 1..10 { let unix_ts = rand::random::() as i64; let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into()); @@ -61,16 +108,18 @@ mod tests { line: Arc::new(bf_line), }; - let out_line = output.fmt(&unix_ts, &entry); + let mut output = CsvOutput::new(Cursor::new(vec![]), Tz::UTC); + output.write_line(&unix_ts, &entry).unwrap(); + let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines(); + let out_line = output.next().unwrap().unwrap(); + let out_ts = out_line.split(',').next().unwrap(); - let rfc3339 = DateTime::parse_from_rfc3339(out_ts).expect(out_ts); + let rfc3339 = DateTime::parse_from_rfc3339(out_ts) + .expect(out_ts) + .timestamp(); assert_eq!( - unix_ts, - rfc3339.timestamp(), - "Timestamp {} converted to '{}' and back to {}", - unix_ts, - out_ts, - rfc3339.timestamp() + unix_ts, rfc3339, + "Timestamp {unix_ts} converted to '{out_ts}' and back to {rfc3339}", ); } } @@ -80,7 +129,6 @@ mod tests { fn test_correct_ts_random_tz() -> Result<(), String> { for _ in 1..100 { let tz = random_tz(); - let output = CsvOutput::new(tz, tz); let unix_ts = rand::random::() as i64; let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into()); let entry = ListEntry { @@ -88,18 +136,21 @@ mod tests { line: Arc::new(bf_line), }; - let out_line = output.fmt(&unix_ts, &entry); - let out_ts = out_line.split(',').next().unwrap(); + let mut output = CsvOutput::new(Cursor::new(vec![]), tz); + let delimiter: char = crate::output::CSV_DELIMITER.into(); + output.write_line(&unix_ts, &entry).unwrap(); + let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines(); + let out_line = output.next().unwrap().unwrap(); + + let out_ts = out_line.split(delimiter).next().unwrap(); let rfc3339 = match DateTime::parse_from_rfc3339(out_ts) { Ok(ts) => ts, Err(e) => return Err(format!("error while parsing '{}': {}", out_ts, e)), }; - let offset = rfc3339.offset().local_minus_utc() as i64; - let calculated_ts = rfc3339.timestamp() + offset; + let calculated_ts = rfc3339.timestamp(); assert_eq!( unix_ts, calculated_ts, - "Timestamp {} converted to '{}' and back to {} (offset was {}s)", - unix_ts, out_ts, calculated_ts, offset + "Timestamp {unix_ts} converted to '{out_ts}' and back to {calculated_ts}", ); } Ok(()) diff --git a/src/bin/mactime2/output/json_sorter.rs b/src/bin/mactime2/output/json_sorter.rs index a001a1b..a80b754 100644 --- a/src/bin/mactime2/output/json_sorter.rs +++ b/src/bin/mactime2/output/json_sorter.rs @@ -5,7 +5,6 @@ use std::{ thread::JoinHandle, }; -use chrono_tz::Tz; use dfir_toolkit::{ common::bodyfile::Bodyfile3Line, es4forensics::{objects::PosixFile, Timestamp, TimelineObject}, @@ -20,7 +19,6 @@ use crate::{ pub struct JsonSorter { worker: Option>>, receiver: Option>, - src_zone: Tz, } impl Joinable> for JsonSorter { @@ -30,11 +28,10 @@ impl Joinable> for JsonSorter { } impl Consumer for JsonSorter { - fn with_receiver(previous: Receiver, options: RunOptions) -> Self { + fn with_receiver(previous: Receiver, _options: RunOptions) -> Self { Self { receiver: Some(previous), worker: None, - src_zone: options.src_zone, } } } @@ -45,9 +42,8 @@ impl Runnable for JsonSorter { .receiver .take() .expect("no receiver provided; please call with_receiver()"); - let src_zone = self.src_zone; self.worker = Some(std::thread::spawn(move || { - Self::json_worker(receiver, src_zone) + Self::json_worker(receiver) })); } } @@ -55,7 +51,7 @@ impl Runnable for JsonSorter { impl Sorter> for JsonSorter {} impl JsonSorter { - fn json_worker(decoder: Receiver, src_zone: Tz) -> Result<(), MactimeError> { + fn json_worker(decoder: Receiver) -> Result<(), MactimeError> { let mut entries: BTreeMap> = BTreeMap::new(); loop { let line = Arc::new(match decoder.recv() { @@ -66,7 +62,7 @@ impl JsonSorter { }); let bfline: &Bodyfile3Line = line.borrow(); - let pf = PosixFile::try_from((bfline, &src_zone)).unwrap(); + let pf = PosixFile::try_from(bfline).unwrap(); let lines: Vec<(Timestamp, String)> = pf .into_tuples() diff --git a/src/bin/mactime2/output/mod.rs b/src/bin/mactime2/output/mod.rs index 1296d9b..c6b8115 100644 --- a/src/bin/mactime2/output/mod.rs +++ b/src/bin/mactime2/output/mod.rs @@ -1,7 +1,9 @@ mod csv_output; +mod old_csv_output; mod txt_output; mod json_sorter; pub (crate) use csv_output::*; +pub (crate) use old_csv_output::*; pub (crate) use txt_output::*; pub (crate) use json_sorter::*; \ No newline at end of file diff --git a/src/bin/mactime2/output/old_csv_output.rs b/src/bin/mactime2/output/old_csv_output.rs new file mode 100644 index 0000000..a2be1aa --- /dev/null +++ b/src/bin/mactime2/output/old_csv_output.rs @@ -0,0 +1,129 @@ +use std::io::Write; + +use chrono_tz::Tz; +use dfir_toolkit::common::ForensicsTimestamp; + +use crate::bodyfile::{ListEntry, Mactime2Writer}; + +pub(crate) struct OldCsvOutput +where + W: Write + Send, +{ + dst_zone: Tz, + writer: W, +} + +impl OldCsvOutput +where + W: Write + Send, +{ + pub fn new(writer: W, dst_zone: Tz) -> Self { + Self { dst_zone, writer } + } +} + +impl Mactime2Writer for OldCsvOutput +where + W: Write + Send, +{ + fn write_line(&mut self, timestamp: &i64, entry: &ListEntry) -> std::io::Result<()> { + let timestamp = ForensicsTimestamp::from(*timestamp).with_timezone(self.dst_zone); + writeln!( + self.writer, + "{},{},{},{},{},{},{},\"{}\"", + timestamp, + entry.line.get_size(), + entry.flags, + entry.line.get_mode_as_string(), + entry.line.get_uid(), + entry.line.get_gid(), + entry.line.get_inode(), + entry.line.get_name() + ) + } + + fn into_writer(self) -> W { + self.writer + } +} + +#[cfg(test)] +mod tests { + use crate::bodyfile::ListEntry; + use crate::bodyfile::MACBFlags; + use crate::bodyfile::Mactime2Writer; + + use super::OldCsvOutput; + use chrono::DateTime; + use chrono_tz::Tz; + use chrono_tz::TZ_VARIANTS; + use dfir_toolkit::common::bodyfile::Bodyfile3Line; + use std::io::Cursor; + use std::io::{BufRead, BufReader}; + use std::sync::Arc; + + fn random_tz() -> Tz { + let index = rand::random::() % TZ_VARIANTS.len(); + TZ_VARIANTS[index] + } + + #[allow(non_snake_case)] + #[test] + fn test_correct_ts_UTC() { + for _ in 1..10 { + let unix_ts = rand::random::() as i64; + let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into()); + let entry = ListEntry { + flags: MACBFlags::B, + line: Arc::new(bf_line), + }; + + let mut output = OldCsvOutput::new(Cursor::new(vec![]), Tz::UTC); + + output.write_line(&unix_ts, &entry).unwrap(); + let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines(); + let out_line = output.next().unwrap().unwrap(); + + let out_ts = out_line.split(',').next().unwrap(); + let rfc3339 = DateTime::parse_from_rfc3339(out_ts) + .expect(out_ts) + .timestamp(); + assert_eq!( + unix_ts, rfc3339, + "Timestamp {unix_ts} converted to '{out_ts}' and back to {rfc3339}", + ); + } + } + + #[allow(non_snake_case)] + #[test] + fn test_correct_ts_random_tz() -> Result<(), String> { + for _ in 1..100 { + let tz = random_tz(); + let mut output = OldCsvOutput::new(Cursor::new(vec![]), tz); + + let unix_ts = rand::random::() as i64; + let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into()); + let entry = ListEntry { + flags: MACBFlags::B, + line: Arc::new(bf_line), + }; + + output.write_line(&unix_ts, &entry).unwrap(); + let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines(); + let out_line = output.next().unwrap().unwrap(); + + let out_ts = out_line.split(',').next().unwrap(); + let rfc3339 = match DateTime::parse_from_rfc3339(out_ts) { + Ok(ts) => ts, + Err(e) => return Err(format!("error while parsing '{}': {}", out_ts, e)), + }; + let calculated_ts = rfc3339.timestamp(); + assert_eq!( + unix_ts, calculated_ts, + "Timestamp {unix_ts} converted to '{out_ts}' and back to {calculated_ts}", + ); + } + Ok(()) + } +} diff --git a/src/bin/mactime2/output/txt_output.rs b/src/bin/mactime2/output/txt_output.rs index 97330eb..4ea1434 100644 --- a/src/bin/mactime2/output/txt_output.rs +++ b/src/bin/mactime2/output/txt_output.rs @@ -1,38 +1,58 @@ use chrono_tz::Tz; use dfir_toolkit::common::ForensicsTimestamp; -use std::cell::RefCell; +use std::{cell::RefCell, io::Write}; use crate::bodyfile::{ListEntry, Mactime2Writer}; -pub struct TxtOutput { - src_zone: Tz, +pub struct TxtOutput +where + W: Write + Send, +{ dst_zone: Tz, last_ts: (RefCell, RefCell), empty_ts: RefCell, + writer: W, } -impl TxtOutput { - pub fn new(src_zone: Tz, dst_zone: Tz) -> Self { +impl TxtOutput +where + W: Write + Send, +{ + pub fn new(writer: W, dst_zone: Tz) -> Self { Self { - src_zone, dst_zone, last_ts: (RefCell::new(i64::MIN), RefCell::new("".to_owned())), empty_ts: RefCell::new(" ".to_owned()), + writer, } } + + #[allow(dead_code)] + pub fn with_writer(mut self, writer: W) -> Self + where + W: Write + Send + 'static, + { + self.writer = writer; + self + } } -impl Mactime2Writer for TxtOutput { - fn fmt(&self, timestamp: &i64, entry: &ListEntry) -> String { +impl Mactime2Writer for TxtOutput +where + W: Write + Send, +{ + fn write_line(&mut self, timestamp: &i64, entry: &ListEntry) -> std::io::Result<()> { let ts = if *timestamp != *self.last_ts.0.borrow() { - *self.last_ts.1.borrow_mut() = - ForensicsTimestamp::new(*timestamp, self.src_zone, self.dst_zone).to_string(); + *self.last_ts.1.borrow_mut() = ForensicsTimestamp::from(*timestamp) + .with_timezone(self.dst_zone) + .to_string(); *self.last_ts.0.borrow_mut() = *timestamp; self.last_ts.1.borrow() } else { self.empty_ts.borrow() }; - format!( + writeln!( + &mut self.writer, "{} {:>8} {} {:<12} {:<7} {:<7} {} {}", ts, entry.line.get_size(), @@ -44,17 +64,23 @@ impl Mactime2Writer for TxtOutput { entry.line.get_name() ) } + + fn into_writer(self) -> W { + self.writer + } } #[cfg(test)] mod tests { use super::TxtOutput; - use crate::bodyfile::{ListEntry, MACBFlags, Mactime2Writer}; + use crate::bodyfile::Mactime2Writer; + use crate::bodyfile::{ListEntry, MACBFlags}; use chrono::DateTime; use chrono_tz::Tz; use chrono_tz::TZ_VARIANTS; use dfir_toolkit::common::bodyfile::Bodyfile3Line; use dfir_toolkit::common::bodyfile::Created; + use std::io::{BufRead, BufReader, Cursor}; use std::sync::Arc; fn random_tz() -> Tz { @@ -65,7 +91,6 @@ mod tests { #[allow(non_snake_case)] #[test] fn test_correct_ts_UTC() { - let output = TxtOutput::new(Tz::UTC, Tz::UTC); for _ in 1..10 { let unix_ts = rand::random::() as i64; let bf_line = Bodyfile3Line::new().with_crtime(Created::from(unix_ts)); @@ -74,19 +99,22 @@ mod tests { line: Arc::new(bf_line), }; - let out_line = output.fmt(&unix_ts, &entry); - let out_line2 = output.fmt(&unix_ts, &entry); + let mut output = TxtOutput::new(Cursor::new(vec![]), Tz::UTC); + output.write_line(&unix_ts, &entry).unwrap(); + output.write_line(&unix_ts, &entry).unwrap(); + let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines(); + + let out_line = output.next().unwrap().unwrap(); + let out_line2 = output.next().unwrap().unwrap(); assert!(out_line2.starts_with(' ')); let out_ts = out_line.split(' ').next().unwrap(); - let rfc3339 = DateTime::parse_from_rfc3339(out_ts).expect(out_ts); + let rfc3339 = DateTime::parse_from_rfc3339(out_ts) + .expect(out_ts) + .timestamp(); assert_eq!( - unix_ts, - rfc3339.timestamp(), - "Timestamp {} converted to '{}' and back to {}", - unix_ts, - out_ts, - rfc3339.timestamp() + unix_ts, rfc3339, + "Timestamp {unix_ts} converted to '{out_ts}' and back to {rfc3339}", ); } } @@ -96,7 +124,6 @@ mod tests { fn test_correct_ts_random_tz() -> Result<(), String> { for _ in 1..100 { let tz = random_tz(); - let output = TxtOutput::new(tz, tz); let unix_ts = rand::random::() as i64; let bf_line = Bodyfile3Line::new().with_crtime(Created::from(unix_ts)); let entry = ListEntry { @@ -104,8 +131,13 @@ mod tests { line: Arc::new(bf_line), }; - let out_line = output.fmt(&unix_ts, &entry); - let out_line2 = output.fmt(&unix_ts, &entry); + let mut output = TxtOutput::new(Cursor::new(vec![]), tz); + output.write_line(&unix_ts, &entry).unwrap(); + output.write_line(&unix_ts, &entry).unwrap(); + let mut output = BufReader::new(Cursor::new(output.into_writer().into_inner())).lines(); + + let out_line = output.next().unwrap().unwrap(); + let out_line2 = output.next().unwrap().unwrap(); assert!(out_line2.starts_with(' ')); let out_ts = out_line.split(' ').next().unwrap(); @@ -113,12 +145,10 @@ mod tests { Ok(ts) => ts, Err(e) => return Err(format!("error while parsing '{}': {}", out_ts, e)), }; - let offset = rfc3339.offset().local_minus_utc() as i64; - let calculated_ts = rfc3339.timestamp() + offset; + let calculated_ts = rfc3339.timestamp(); assert_eq!( unix_ts, calculated_ts, - "Timestamp {} converted to '{}' and back to {} (offset was {}s)", - unix_ts, out_ts, calculated_ts, offset + "Timestamp {unix_ts} converted to '{out_ts}' and back to {calculated_ts}", ); } Ok(()) diff --git a/src/bin/pf2bodyfile/main.rs b/src/bin/pf2bodyfile/main.rs index b393204..c33b661 100644 --- a/src/bin/pf2bodyfile/main.rs +++ b/src/bin/pf2bodyfile/main.rs @@ -26,12 +26,30 @@ fn main() -> anyhow::Result<()> { let mut fs = ChRootFileSystem::new(parent, vfs.clone()); if let Some(pf_os_filename) = input.path().file_name() { if let Some(pf_filename) = pf_os_filename.to_str() { - let pf_file = read_prefetch_file( + let virtual_file = fs.open(Path::new(&pf_filename.to_string()))?; + let created; + let modified; + match virtual_file.metadata() { + Ok(metadata) => { + created = + metadata.created_opt().and_then(|t| i64::try_from(*t).ok()); + modified = + metadata.modified_opt().and_then(|t| i64::try_from(*t).ok()); + } + Err(why) => { + log::warn!("Unable to obtain metadata for {pf_filename}: {why}"); + created = None; + modified = None; + } + } + let pf_file = read_prefetch_file(pf_filename, virtual_file).unwrap(); + + pf_file.display_prefetch_file( pf_filename, - fs.open(Path::new(&pf_filename.to_string()))?, + *cli.include_metrics(), + created, + modified, )?; - - pf_file.display_prefetch_file(pf_filename, *cli.include_metrics())?; } else { error!("invalid Unicode characters in filename: '{pf_os_filename:?}'") } @@ -52,6 +70,8 @@ trait DisplayPrefetchFile { &self, pf_file_name: &str, include_metrics: bool, + created: Option, + modified: Option, ) -> anyhow::Result<()>; } @@ -60,6 +80,8 @@ impl DisplayPrefetchFile for PrefetchFile { &self, pf_file_name: &str, include_metrics: bool, + created: Option, + modified: Option, ) -> anyhow::Result<()> { for time in &self.last_run_times { let accessed = @@ -67,12 +89,19 @@ impl DisplayPrefetchFile for PrefetchFile { .to_datetime() .into(); - let bf_line = Bodyfile3Line::new() + let mut bf_line = Bodyfile3Line::new() .with_owned_name(format!( "Prefetch: run '{}' (run {} times, read from '{pf_file_name}')", self.name, self.run_count )) .with_atime(accessed); + + if let Some(ts) = created { + bf_line = bf_line.with_crtime(ts.into()); + } + if let Some(ts) = modified { + bf_line = bf_line.with_mtime(ts.into()); + } println!("{bf_line}"); if include_metrics { diff --git a/src/bin/ts2date/main.rs b/src/bin/ts2date/main.rs index b60c7f8..3d5ea71 100644 --- a/src/bin/ts2date/main.rs +++ b/src/bin/ts2date/main.rs @@ -28,8 +28,7 @@ fn main() -> Result<()> { let out = match re.captures(&content) { Some(caps) => { - //let ndt = NaiveDateTime::from_timestamp_opt(caps.name("ts").unwrap().as_str().parse::().unwrap(),0).unwrap(); - let ts = ForensicsTimestamp::new(caps.name("ts").unwrap().as_str().parse::().unwrap(),cli.src_zone.into_tz().unwrap(), cli.dst_zone.into_tz().unwrap()); + let ts = ForensicsTimestamp::from(caps.name("ts").unwrap().as_str().parse::().unwrap()).with_timezone(cli.dst_zone.into_tz().unwrap()); format!("{}{}{}", caps.name("lhs").unwrap().as_str(), ts, caps.name("rhs").unwrap().as_str()) diff --git a/src/bin/zip2bodyfile/cli.rs b/src/bin/zip2bodyfile/cli.rs new file mode 100644 index 0000000..9b47cdf --- /dev/null +++ b/src/bin/zip2bodyfile/cli.rs @@ -0,0 +1,29 @@ +use clap::Parser; +use clap::ValueHint; +use clio::ClioPath; +use dfir_toolkit::common::HasVerboseFlag; +use getset::Getters; +use log::LevelFilter; + +/// creates bodyfile from ZIP Archives based on the contained files and folders +#[derive(Parser, Getters)] +#[clap(name=env!("CARGO_BIN_NAME"), author, version)] +#[getset(get = "pub (crate)")] +pub(crate) struct Cli { + /// names of the archive files (commonly files with 'zip' extension) + #[clap(value_hint=ValueHint::FilePath)] + zip_files: Vec, + + /// show the name of the archive in the bodyfile output + #[clap(long("show-archive-name"))] + show_archive_name: bool, + + #[clap(flatten)] + verbose: clap_verbosity_flag::Verbosity, +} + +impl HasVerboseFlag for Cli { + fn log_level_filter(&self) -> LevelFilter { + self.verbose.log_level_filter() + } +} diff --git a/src/bin/zip2bodyfile/main.rs b/src/bin/zip2bodyfile/main.rs new file mode 100644 index 0000000..67a5483 --- /dev/null +++ b/src/bin/zip2bodyfile/main.rs @@ -0,0 +1,141 @@ +mod cli; + +use std::io::{Read, Seek}; + +use chrono::FixedOffset; +use cli::Cli; +use dfir_toolkit::common::bodyfile::Bodyfile3Line; +use dfir_toolkit::common::FancyParser; +use log::{error, warn}; +use time::OffsetDateTime; +use zip::{ExtraField, ZipArchive}; + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse_cli(); + + if cli.zip_files().iter().any(|f| !f.path().exists()) { + anyhow::bail!("some files you specified do not exist"); + } + if cli.zip_files().iter().any(|f| !f.path().is_file()) { + anyhow::bail!("some paths you specified are no files"); + } + for input in cli.zip_files().iter() { + if let Some(zip_os_filename) = input.path().file_name() { + if let Some(zip_filename) = zip_os_filename.to_str() { + let mut zip_archive = ZipArchive::new(input.clone().open()?)?; + zip_archive.display_zip_file(zip_filename, *cli.show_archive_name())?; + } else { + error!("invalid Unicode characters in filename: '{zip_os_filename:?}'") + } + } else { + warn!("unable to handle directories; you must specify concrete file names"); + } + } + Ok(()) +} + +trait DisplayZipFile { + fn display_zip_file( + &mut self, + zip_file_name: &str, + show_archive_name: bool, + ) -> anyhow::Result<()>; +} + +impl DisplayZipFile for ZipArchive +where + R: Read + Seek, +{ + fn display_zip_file( + &mut self, + zip_file_name: &str, + show_archive_name: bool, + ) -> anyhow::Result<()> { + for index in 0..self.len() { + let file = self.by_index(index)?; + + let mut bf_line = Bodyfile3Line::new().with_size(file.size()); + + let mut utc_mtime = None; + for field in file.extra_data_fields() { + #[allow(irrefutable_let_patterns)] + if let ExtraField::ExtendedTimestamp(ts) = field { + if let Some(mtime) = ts.mod_time() { + bf_line = bf_line.with_mtime((mtime as i64).into()); + utc_mtime = Some(mtime as i64); + } + if let Some(atime) = ts.mod_time() { + bf_line = bf_line.with_atime((atime as i64).into()); + } + if let Some(crtime) = ts.mod_time() { + bf_line = bf_line.with_crtime((crtime as i64).into()); + } + break; + } + } + + let tz_offset = utc_mtime.and_then(|utc_mtime| { + file.last_modified().and_then(|last_modified| { + match OffsetDateTime::try_from(last_modified) { + Ok(local_ts) => { + let local_ts = local_ts.unix_timestamp(); + match i32::try_from(local_ts - utc_mtime) { + Err(_) => { + log::warn!("illegal timezone offset: {}, ", local_ts - utc_mtime); + None + } + Ok(secs) => match FixedOffset::east_opt(secs) { + None => { + log::warn!("timestamp offset (abs value) is too large: {secs} seconds"); + None + } + Some(offset) => Some(offset), + }, + } + } + Err(why) => { + log::warn!("unable to calculate timezone: {why}"); + None + } + } + }) + }); + + let tz_offset_text = match tz_offset { + None => "".to_string(), + Some(o) => format!(", [offset: {o}]"), + }; + + if utc_mtime.is_none() { + match file.last_modified() { + None => { + log::warn!("no extended timestamp header with modification time found"); + } + Some(last_modified) => { + log::warn!("no extended timestamp header with modification time found, try using the MS-DOS timestamp instead"); + match OffsetDateTime::try_from(last_modified) { + Err(why) => log::error!( + "unable to convert {last_modified} into an OffsetDateTime: {why}" + ), + Ok(ts) => bf_line = bf_line.with_mtime(ts.unix_timestamp().into()), + } + } + } + } + + let name = if show_archive_name { + format!( + "{} (in archive {zip_file_name}){tz_offset_text}", + file.name() + ) + } else { + format!("{}{tz_offset_text}", file.name()) + }; + + bf_line = bf_line.with_owned_name(name); + + println!("{bf_line}"); + } + Ok(()) + } +} diff --git a/src/common/bodyfile/bodyfile3.rs b/src/common/bodyfile/bodyfile3.rs index 009c810..e21440e 100644 --- a/src/common/bodyfile/bodyfile3.rs +++ b/src/common/bodyfile/bodyfile3.rs @@ -75,7 +75,7 @@ impl Bodyfile3Line { [with_mode] [mode_as_string]; )] pub fn method_name(mut self, attribute_name: &str) -> Self { - self.attribute_name = attribute_name.to_owned(); + attribute_name.clone_into(&mut self.attribute_name); self } diff --git a/src/common/forensics_timestamp.rs b/src/common/forensics_timestamp.rs index 6027faa..f9c1c45 100644 --- a/src/common/forensics_timestamp.rs +++ b/src/common/forensics_timestamp.rs @@ -2,9 +2,10 @@ use std::fmt::Display; use chrono::format::StrftimeItems; use chrono::offset::TimeZone; -use chrono::{DateTime, FixedOffset, LocalResult, NaiveDateTime}; +use chrono::{DateTime, FixedOffset, Utc}; use chrono_tz::Tz; use lazy_static::lazy_static; +use serde::Serialize; lazy_static! { static ref TIMESTAMP_FORMAT: Option = { @@ -35,20 +36,40 @@ lazy_static! { } pub struct ForensicsTimestamp { - unix_ts: i64, - src_zone: Tz, + timestamp: DateTime, dst_zone: Tz, } +impl From for ForensicsTimestamp { + fn from(value: i64) -> Self { + let timestamp = match DateTime::from_timestamp(value, 0) { + Some(ts) => ts, + None => panic!("unable to convert '{value}' into unix timestamp"), + }; + Self { + timestamp, + dst_zone: Tz::UTC, + } + } +} + impl ForensicsTimestamp { - pub fn new(unix_ts: i64, src_zone: Tz, dst_zone: Tz) -> Self { + pub fn new(unix_ts: i64, dst_zone: Tz) -> Self { + let timestamp = match DateTime::from_timestamp(unix_ts, 0) { + Some(ts) => ts, + None => panic!("unable to convert '{unix_ts}' into unix timestamp"), + }; Self { - unix_ts, - src_zone, + timestamp, dst_zone, } } + pub fn with_timezone(mut self, dst_zone: Tz) -> Self { + self.dst_zone = dst_zone; + self + } + fn display_datetime( dt: &DateTime, f: &mut std::fmt::Formatter<'_>, @@ -65,22 +86,30 @@ impl ForensicsTimestamp { impl Display for ForensicsTimestamp { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if self.unix_ts >= 0 { - let src_timestamp = match self.src_zone.from_local_datetime( - &NaiveDateTime::from_timestamp_opt(self.unix_ts, 0).unwrap_or_else(|| { - panic!("unable to convert '{}' into unix timestamp", self.unix_ts) - }), - ) { - LocalResult::None => { - panic!("INVALID DATETIME"); - } - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, _t2) => t1, - }; + Self::display_datetime(&self.timestamp.with_timezone(&self.dst_zone), f) + } +} - Self::display_datetime(&src_timestamp.with_timezone(&self.dst_zone), f) - } else { - Self::display_datetime(&*ZERO, f) - } +#[cfg(test)] +mod tests { + use chrono_tz::{Europe, UTC}; + + use crate::common::ForensicsTimestamp; + + #[test] + fn test_time_import() { + let ts = ForensicsTimestamp::from(1715845546).with_timezone(Europe::Berlin); + assert_eq!(ts.to_string(), "2024-05-16T09:45:46+02:00"); + + let ts = ForensicsTimestamp::from(1715845546).with_timezone(UTC); + assert_eq!(ts.to_string(), "2024-05-16T07:45:46+00:00"); + } +} + +impl Serialize for ForensicsTimestamp { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer { + serializer.serialize_str(&format!("{self}")) } } diff --git a/src/common/parse_cli.rs b/src/common/parse_cli.rs index 97f3241..3ed2faa 100644 --- a/src/common/parse_cli.rs +++ b/src/common/parse_cli.rs @@ -40,7 +40,7 @@ where .get_matches(); if matches.contains_id("markdown-help") { - clap_markdown::print_help_markdown::

(); + clap_markdown_dfir::print_help_markdown::

(Default::default()); exit(0); } } diff --git a/src/es4forensics/ecs/objects/posix_file.rs b/src/es4forensics/ecs/objects/posix_file.rs index 5b6d961..60ee71c 100644 --- a/src/es4forensics/ecs/objects/posix_file.rs +++ b/src/es4forensics/ecs/objects/posix_file.rs @@ -86,13 +86,6 @@ impl IntoIterator for PosixFile { } } -impl TryFrom<(Bodyfile3Line, &Tz)> for PosixFile { - type Error = anyhow::Error; - fn try_from((bfline, src_tz): (Bodyfile3Line, &Tz)) -> Result { - Self::try_from((&bfline, src_tz)) - } -} - impl TryFrom for PosixFile { type Error = anyhow::Error; fn try_from(bfline: Bodyfile3Line) -> Result { diff --git a/src/es4forensics/timestamp.rs b/src/es4forensics/timestamp.rs index 9d29c86..a093c1d 100644 --- a/src/es4forensics/timestamp.rs +++ b/src/es4forensics/timestamp.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use chrono::{DateTime, LocalResult, NaiveDateTime, TimeZone, Utc}; +use chrono::{DateTime, TimeZone, Utc}; use chrono_tz::Tz; use serde::Serialize; use serde_json::{json, Value}; @@ -36,14 +36,11 @@ impl TryFrom<(i64, &Tz)> for Timestamp { type Error = anyhow::Error; fn try_from((unix_ts, src_tz): (i64, &Tz)) -> Result { - let ts = match src_tz - .from_local_datetime(&NaiveDateTime::from_timestamp_opt(unix_ts, 0).unwrap()) - { - LocalResult::None => { + let ts = match DateTime::from_timestamp(unix_ts, 0) { + Some(ts) => ts.with_timezone(src_tz), + None => { return Err(anyhow!("INVALID DATETIME")); } - LocalResult::Single(t) => t, - LocalResult::Ambiguous(t1, _t2) => t1, }; Ok(Self { ts: ts.timestamp_millis(), diff --git a/tests/data/mactime2/csv_test.bodyfile b/tests/data/mactime2/csv_test.bodyfile new file mode 100644 index 0000000..cee425d --- /dev/null +++ b/tests/data/mactime2/csv_test.bodyfile @@ -0,0 +1 @@ +0|{"activity_id":null,"channel_name":"Microsoft-Windows-WER-PayloadHealth/Operational","computer":"WIN-J56D9ENVG6H","custom_data":{"EventData":{"#attributes":{"Name":"WER_PAYLOAD_HEALTH_FAIL"},"BytesUploaded":0,"HttpExchangeResult":2147954402,"PayloadSize":4569,"Protocol":"Watson","RequestStatusCode":0,"ServerName":"umwatson.events.data.microsoft.com","Stage":"s1event","TransportHr":2147954402,"UploadDuration":21094}},"event_id":2,"event_record_id":1,"level":4,"provider_name":"Microsoft-Windows-WER-PayloadHealth","timestamp":"2022-11-16T08:26:43.409044Z"}|0||0|0|0|-1|1668587203|-1|-1 \ No newline at end of file diff --git a/tests/data/zip2bodyfile/hello.zip b/tests/data/zip2bodyfile/hello.zip new file mode 100644 index 0000000..ccf7d5d Binary files /dev/null and b/tests/data/zip2bodyfile/hello.zip differ diff --git a/tests/data/zip2bodyfile/hello2.zip b/tests/data/zip2bodyfile/hello2.zip new file mode 100644 index 0000000..2782382 Binary files /dev/null and b/tests/data/zip2bodyfile/hello2.zip differ diff --git a/tests/mactime2/csv_output.rs b/tests/mactime2/csv_output.rs new file mode 100644 index 0000000..f370122 --- /dev/null +++ b/tests/mactime2/csv_output.rs @@ -0,0 +1,27 @@ +use std::{ + io::{BufReader, Cursor}, + path::PathBuf, +}; + +use assert_cmd::Command; + +/// tests if the result of `mactime2` is always sorted +#[test] +fn csv_output() { + let mut cmd = Command::cargo_bin("mactime2").unwrap(); + let mut data_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + data_path.push("tests"); + data_path.push("data"); + data_path.push("mactime2"); + data_path.push("csv_test.bodyfile"); + + let result = cmd.arg("-d").arg("-b").arg(data_path).ok(); + assert!(result.is_ok()); + + let mut reader = csv::ReaderBuilder::new() + .has_headers(false) + .from_reader(BufReader::new(Cursor::new(result.unwrap().stdout))); + let first_line = reader.records().next().unwrap().unwrap(); + + assert_eq!(first_line.get(7).unwrap(), r##"{"activity_id":null,"channel_name":"Microsoft-Windows-WER-PayloadHealth/Operational","computer":"WIN-J56D9ENVG6H","custom_data":{"EventData":{"#attributes":{"Name":"WER_PAYLOAD_HEALTH_FAIL"},"BytesUploaded":0,"HttpExchangeResult":2147954402,"PayloadSize":4569,"Protocol":"Watson","RequestStatusCode":0,"ServerName":"umwatson.events.data.microsoft.com","Stage":"s1event","TransportHr":2147954402,"UploadDuration":21094}},"event_id":2,"event_record_id":1,"level":4,"provider_name":"Microsoft-Windows-WER-PayloadHealth","timestamp":"2022-11-16T08:26:43.409044Z"}"##); +} diff --git a/tests/mactime2/is_stable_sorting.rs b/tests/mactime2/is_stable_sorting.rs index 740f096..c83b47e 100644 --- a/tests/mactime2/is_stable_sorting.rs +++ b/tests/mactime2/is_stable_sorting.rs @@ -1,6 +1,5 @@ -use std::io::{BufReader, Cursor, BufRead}; -use lazy_regex::regex; use assert_cmd::Command; +use std::io::{BufReader, Cursor}; /// tests if the result of `mactime2` is a stable sort, i.e. if pos(a)<=pos(b) then sorted_pos(a) <= sorted_pos(b) #[test] @@ -20,25 +19,13 @@ fn is_stable_sorted() { .ok(); assert!(result.is_ok()); - let reader = BufReader::new(Cursor::new(result.unwrap().stdout)); - let lines: Vec<_> = reader - .lines() - .map_while(Result::ok) - .map(name_of) + let mut reader = csv::ReaderBuilder::new() + .has_headers(false) + .from_reader(BufReader::new(Cursor::new(result.unwrap().stdout))); + let names: Vec<_> = reader + .records() + .filter_map(Result::ok) + .map(|record| record.get(7).unwrap().to_owned()) .collect(); - assert_eq!(lines.len(), 3); - assert_eq!(lines[0].as_ref().unwrap(), "a"); - assert_eq!(lines[1].as_ref().unwrap(), "b"); - assert_eq!(lines[2].as_ref().unwrap(), "c"); + assert_eq!(names, vec!["a", "b", "c"]); } - -fn name_of(line: String) -> Option { - let re = regex!(r#""(?P[^"]*)""#); - let result = re.captures_iter(&line); - for c in result { - if let Some(name) = c.name("name") { - return Some(name.as_str().to_owned()) - } - } - None -} \ No newline at end of file diff --git a/tests/mactime2/mod.rs b/tests/mactime2/mod.rs index 8a5875e..007535d 100644 --- a/tests/mactime2/mod.rs +++ b/tests/mactime2/mod.rs @@ -1,4 +1,6 @@ mod help; mod autocomplete; mod is_sorted; -mod is_stable_sorting; \ No newline at end of file +mod is_stable_sorting; + +mod csv_output; \ No newline at end of file diff --git a/tests/mod.rs b/tests/mod.rs index e8addcc..548a7bf 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,4 +1,5 @@ mod mactime2; mod ts2date; mod lnk2bodyfile; -mod hivescan; \ No newline at end of file +mod zip2bodyfile; +mod hivescan; diff --git a/tests/ts2date.rs b/tests/ts2date.rs index bf8db3e..df6c640 100644 --- a/tests/ts2date.rs +++ b/tests/ts2date.rs @@ -49,29 +49,6 @@ fn ts2date_utc2berlin() { ); } -#[test] -fn ts2date_berlin2utc() { - const SAMPLE_TIMELINE_OUT: &str = r#"2023-08-30T14:08:37+00:00|REG|||App Paths - protocolhandler.exe - C:\Program Files\WindowsApps\Microsoft.Office.Desktop_16051.16626.20170.0_x86__8wekyb3d8bbwe\Office16\protocolhandler.exe -2023-08-30T14:08:37+00:00|REG|||App Paths - sdxhelper.exe - C:\Program Files\WindowsApps\Microsoft.Office.Desktop_16051.16626.20170.0_x86__8wekyb3d8bbwe\Office16\SDXHelper.exe -2023-08-30T14:08:37+00:00|REG|||App Paths - selfcert.exe - C:\Program Files\WindowsApps\Microsoft.Office.Desktop_16051.16626.20170.0_x86__8wekyb3d8bbwe\Office16\SELFCERT.exe -2023-08-30T14:06:21+00:00|REG|||App Paths - msaccess.exe - C:\Program Files\WindowsApps\Microsoft.Office.Desktop.Access_16051.16626.20170.0_x86__8wekyb3d8bbwe\Office16\MSACCESS.exe -"#; - - let mut cmd = Command::cargo_bin("ts2date").unwrap(); - let result = cmd - .arg("-f") - .arg("Europe/Berlin") - .arg("-t") - .arg("UTC") - .write_stdin(SAMPLE_TIMELINE) - .ok(); - assert!(result.is_ok()); - - assert_eq!( - SAMPLE_TIMELINE_OUT, - String::from_utf8(result.unwrap().stdout).unwrap() - ); -} #[test] fn ts2date_list1() { diff --git a/tests/zip2bodyfile.rs b/tests/zip2bodyfile.rs new file mode 100644 index 0000000..64284dd --- /dev/null +++ b/tests/zip2bodyfile.rs @@ -0,0 +1,107 @@ +use std::{ + io::{BufRead, BufReader, Cursor}, + path::PathBuf, +}; + +use assert_cmd::Command; +use dfir_toolkit::common::bodyfile::{Bodyfile3Line, Accessed, Modified, Changed, Created}; + +#[test] +fn test_hello() { + do_test_hello(r#"hello.txt, [offset: +01:00]"#, vec![].into_iter()); +} + +#[test] +fn test_hello_with_archive_name() { + do_test_hello(r#"hello.txt (in archive hello.zip), [offset: +01:00]"#, vec!["--show-archive-name"].into_iter()); +} + +fn do_test_hello(expected_name: &str, args: impl Iterator) { + let mut cmd = Command::cargo_bin("zip2bodyfile").unwrap(); + let mut data_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + data_path.push("tests"); + data_path.push("data"); + data_path.push("zip2bodyfile"); + data_path.push("hello.zip"); + + let result = cmd.arg(data_path).args(args).ok(); + if result.is_err() { + println!("{}", result.as_ref().err().unwrap()); + } + + assert!(result.is_ok()); + + // parse the result as bodyfile 😈 + let reader = BufReader::new(Cursor::new(result.unwrap().stdout)); + let mut lines_iterator = reader.lines(); + let first_line = lines_iterator.next().unwrap().unwrap(); + + println!("{first_line}"); + let bfline = Bodyfile3Line::try_from(&first_line[..]).unwrap(); + assert_eq!(bfline.get_name(), expected_name); + assert_eq!(*bfline.get_size(), 12); + assert_eq!(*bfline.get_atime(), Accessed::from(1709194030)); + assert_eq!(*bfline.get_mtime(), Modified::from(1709194030)); + assert_eq!(*bfline.get_ctime(), Changed::default()); + assert_eq!(*bfline.get_crtime(), Created::from(1709194030)); + + assert!(lines_iterator.next().is_none()); +} + + + +#[test] +fn test_hello2() { + do_test_hello2(vec![].into_iter()); +} + + +fn do_test_hello2(args: impl Iterator) { + let mut cmd = Command::cargo_bin("zip2bodyfile").unwrap(); + let mut data_path = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + data_path.push("tests"); + data_path.push("data"); + data_path.push("zip2bodyfile"); + data_path.push("hello2.zip"); + + let result = cmd.arg(data_path).args(args).ok(); + if result.is_err() { + println!("{}", result.as_ref().err().unwrap()); + } + + assert!(result.is_ok()); + + let reader = BufReader::new(Cursor::new(result.unwrap().stdout)); + let mut lines_iterator = reader.lines(); + let first_line = lines_iterator.next().unwrap().unwrap(); + + /* + * Archive: ~/dfir-toolkit/tests/data/zip2bodyfile/hello2.zip + * c3636a8166a5e10268c90ee3fc37fe44b1b23d80 + * Length Date Time Name + * --------- ---------- ----- ---- + * 0 2024-02-28 20:16 dfir-toolkit-feature-zip2bodyfile/ + * 35149 2024-02-28 20:16 dfir-toolkit-feature-zip2bodyfile/LICENSE + */ + + let mut expected_name = String::from("dfir-toolkit-feature-zip2bodyfile/"); + let mut bfline = Bodyfile3Line::try_from(&first_line[..]).unwrap(); + + assert_eq!(bfline.get_name(), &format!("{expected_name}, [offset: -08:00]")); + assert_eq!(*bfline.get_size(), 0); + // 1709151390 = 2024-02-28 20:16:30 + // 1709118990 = 2024-02-28 11:16:30 + // 1709147790 = 2024-02-28 19:16:30 + assert_eq!(*bfline.get_mtime(), Modified::from(1709147790)); + + let second_line = lines_iterator.next().unwrap().unwrap(); + bfline = Bodyfile3Line::try_from(&second_line[..]).unwrap(); + expected_name = String::from("dfir-toolkit-feature-zip2bodyfile/LICENSE"); + assert_eq!(bfline.get_name(), &format!("{expected_name}, [offset: -08:00]")); + assert_eq!(*bfline.get_size(), 35149); + // 1709151390 = 2024-02-28 20:16:30 + assert_eq!(*bfline.get_mtime(), Modified::from(1709147790)); + + + assert!(lines_iterator.next().is_none()); +}