diff --git a/Cargo.lock b/Cargo.lock index cc4cf9c..ee06e48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.4" +version = "4.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136" +checksum = "824956d0dca8334758a5b7f7e50518d66ea319330cbceedcf76905c2f6ab30e3" dependencies = [ "clap_builder", "clap_derive", @@ -396,7 +396,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325f50228f76921784b6d9f2d62de6778d834483248eefecd27279174797e579" dependencies = [ - "clap 4.4.4", + "clap 4.4.5", ] [[package]] @@ -405,15 +405,15 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1eef05769009513df2eb1c3b4613e7fad873a14c600ff025b08f250f59fee7de" dependencies = [ - "clap 4.4.4", + "clap 4.4.5", "log", ] [[package]] name = "clap_builder" -version = "4.4.4" +version = "4.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56" +checksum = "122ec64120a49b4563ccaedcbea7818d069ed8e9aa6d829b82d8a4128936b2ab" dependencies = [ "anstream", "anstyle", @@ -424,11 +424,11 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.4.1" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4110a1e6af615a9e6d0a36f805d5c99099f8bab9b8042f5bc1fa220a4a89e36f" +checksum = "8baeccdb91cd69189985f87f3c7e453a3a451ab5746cf3be6acc92120bd16d24" dependencies = [ - "clap 4.4.4", + "clap 4.4.5", ] [[package]] @@ -465,7 +465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "746ce4269bee03af43b3349f37f420cf5957f8431c531c08dea0441b298b10e0" dependencies = [ "cfg-if", - "clap 4.4.4", + "clap 4.4.5", "is-terminal", "libc", "tempfile", @@ -702,7 +702,7 @@ dependencies = [ [[package]] name = "dfir-toolkit" -version = "0.8.1" +version = "0.9.0" dependencies = [ "anyhow", "assert-json-diff", @@ -712,7 +712,7 @@ dependencies = [ "bitflags 2.4.0", "chrono", "chrono-tz", - "clap 4.4.4", + "clap 4.4.5", "clap-markdown", "clap-verbosity-flag", "clap_complete", @@ -732,6 +732,7 @@ dependencies = [ "getset", "indicatif", "lazy-regex", + "lnk", "log", "matches", "more-asserts", @@ -1007,9 +1008,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "flate2" @@ -1471,6 +1472,20 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +[[package]] +name = "lnk" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e066ce29d4da51727b57c404c1270e3fa2a5ded0db1a4cb67c61f7a132421b2c" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "chrono", + "log", + "num-derive 0.3.3", + "num-traits", +] + [[package]] name = "lock_api" version = "0.4.10" @@ -2295,9 +2310,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479fb9d862239e610720565ca91403019f2f00410f1864c5aa7479b950a76ed8" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2533,18 +2548,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 91d760f..32f34b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dfir-toolkit" -version = "0.8.2" +version = "0.9.0" edition = "2021" authors = ["Jan Starke ", "Deborah Mahn "] description = "CLI tools for digital forensics and incident response" @@ -72,9 +72,14 @@ name = "ts2date" path = "src/bin/ts2date/main.rs" required-features = ["ts2date"] +[[bin]] +name = "lnk2bodyfile" +path = "src/bin/lnk2bodyfile/main.rs" +required-features = ["lnk2bodyfile"] + # 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"] +default = ["pol_export", "mactime2", "evtxtools", "regdump", "hivescan", "cleanhive", "ipgrep", "ts2date", "lnk2bodyfile"] mactime2 = ["gzip", "elastic", "chrono-tz", "thiserror", "bitflags", "encoding_rs_io"] gzip = ["flate2"] elastic = ["elasticsearch", "tokio", "futures", "serde_json", "sha2", "base64", "num-traits", "num-derive", "strum", "strum_macros", "tokio-async-drop"] @@ -87,6 +92,7 @@ evtxanalyze = ["evtx", "dfirtk-sessionevent-derive", "dfirtk-eventdata"] evtx2bodyfile = ["evtx", "getset", "ouroboros", "indicatif"] ipgrep = [] ts2date = ["regex"] +lnk2bodyfile = ["lnk"] regdump = ["nt_hive2"] hivescan = ["nt_hive2"] @@ -156,6 +162,9 @@ strum_macros = {version="0", optional=true} # nt-hive2 nt_hive2 = {version="4.0.1", optional=true} +# lnk2bodyfile +lnk = {version="0.5.1", optional=true} + [dev-dependencies] # mactime2 diff --git a/README.md b/README.md index 157d62a..b4c7d3b 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ - [x] [`es4forensics`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/es4forensics.md) - [x] [`hivescan`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/hivescan.md) - [x] [`ipgrep`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/ipgrep.md) - - [ ] [`lnk2bodyfile`](https://github.com/janstarke/lnk2bodyfile) + - [x] [`lnk2bodyfile`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/lnk2bodyfile.md) - [x] [`mactime2`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/mactime2.md) - [ ] [`mft2bodyfile`](https://github.com/janstarke/mft2bodyfile) - [ ] [`ntdsextract2`](https://github.com/janstarke/ntdsextract2) diff --git a/doc/cleanhive.md b/doc/cleanhive.md index 4548d2e..291166f 100644 --- a/doc/cleanhive.md +++ b/doc/cleanhive.md @@ -27,6 +27,42 @@ merges logfiles into a hive file +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `cleanhive` + +This document contains the help content for the `cleanhive` command-line program. + +**Command Overview:** + +* [`cleanhive`↴](#cleanhive) + +## `cleanhive` + +merges logfiles into a hive file + +**Usage:** `cleanhive [OPTIONS] ` + +###### **Arguments:** + +* `` — name of the file to dump + +###### **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 +* `-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..ba37dde 100644 --- a/doc/es4forensics.md +++ b/doc/es4forensics.md @@ -53,6 +53,86 @@ This crates provides structs and functions to insert timeline data into an elast +## `es4forensics import` + +**Usage:** `es4forensics import [OPTIONS] [INPUT_FILE]` + +###### **Arguments:** + +* `` — path to input file or '-' for stdin (files ending with .gz will be treated as being gzipped) + + Default value: `-` + +###### **Options:** + +* `--bulk-size ` — number of timeline entries to combine in one bulk operation + + Default value: `1000` + + + +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `es4forensics` + +This document contains the help content for the `es4forensics` command-line program. + +**Command Overview:** + +* [`es4forensics`↴](#es4forensics) +* [`es4forensics create-index`↴](#es4forensics-create-index) +* [`es4forensics import`↴](#es4forensics-import) + +## `es4forensics` + +This crates provides structs and functions to insert timeline data into an elasticsearch index + +**Usage:** `es4forensics [OPTIONS] --index --password ` + +###### **Subcommands:** + +* `create-index` — +* `import` — + +###### **Options:** + +* `--strict` — strict mode: do not only warn, but abort if an error occurs +* `-I`, `--index ` — name of the elasticsearch index +* `-H`, `--host ` — server name or IP address of elasticsearch server + + Default value: `localhost` +* `-P`, `--port ` — API port number of elasticsearch server + + Default value: `9200` +* `--proto ` — protocol to be used to connect to elasticsearch + + Default value: `https` + + Possible values: `http`, `https` + +* `-k`, `--insecure` — omit certificate validation + + Default value: `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 + + + +## `es4forensics create-index` + +**Usage:** `es4forensics create-index` + + + ## `es4forensics import` **Usage:** `es4forensics import [OPTIONS] [INPUT_FILE]` diff --git a/doc/evtx2bodyfile.md b/doc/evtx2bodyfile.md index 3021022..c5e50e7 100644 --- a/doc/evtx2bodyfile.md +++ b/doc/evtx2bodyfile.md @@ -30,6 +30,45 @@ creates bodyfile from Windows evtx files +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `evtx2bodyfile` + +This document contains the help content for the `evtx2bodyfile` command-line program. + +**Command Overview:** + +* [`evtx2bodyfile`↴](#evtx2bodyfile) + +## `evtx2bodyfile` + +creates bodyfile from Windows evtx files + +**Usage:** `evtx2bodyfile [OPTIONS] [EVTX_FILES]...` + +###### **Arguments:** + +* `` — names of the evtx files + +###### **Options:** + +* `-F`, `--format ` — select output format + + Default value: `bodyfile` + + Possible values: `json`, `bodyfile` + +* `-S`, `--strict` — fail upon read error +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence + + +
diff --git a/doc/evtxanalyze.md b/doc/evtxanalyze.md index ebb1dae..47dd0c9 100644 --- a/doc/evtxanalyze.md +++ b/doc/evtxanalyze.md @@ -50,6 +50,94 @@ generate a process tree +## `evtxanalyze sessions` + +display sessions + +**Usage:** `evtxanalyze sessions [OPTIONS] ` + +###### **Arguments:** + +* `` — Names of the evtx files to parse + +###### **Options:** + +* `--include-anonymous` — include anonymous sessions + + + +## `evtxanalyze session` + +display one single session + +**Usage:** `evtxanalyze session ` + +###### **Arguments:** + +* `` — Names of the evtx files to parse +* `` — Session ID + + + +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `evtxanalyze` + +This document contains the help content for the `evtxanalyze` command-line program. + +**Command Overview:** + +* [`evtxanalyze`↴](#evtxanalyze) +* [`evtxanalyze pstree`↴](#evtxanalyze-pstree) +* [`evtxanalyze sessions`↴](#evtxanalyze-sessions) +* [`evtxanalyze session`↴](#evtxanalyze-session) + +## `evtxanalyze` + +crate provide functions to analyze evtx files + +**Usage:** `evtxanalyze [OPTIONS] ` + +###### **Subcommands:** + +* `pstree` — generate a process tree +* `sessions` — display sessions +* `session` — display one single session + +###### **Options:** + +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence + + + +## `evtxanalyze pstree` + +generate a process tree + +**Usage:** `evtxanalyze pstree [OPTIONS] ` + +###### **Arguments:** + +* `` — Name of the evtx file to parse + +###### **Options:** + +* `-U`, `--username ` — display only processes of this user (case insensitive regex search) +* `-F`, `--format ` — output format + + Default value: `csv` + + Possible values: `json`, `markdown`, `csv`, `latex`, `dot` + + + + ## `evtxanalyze sessions` display sessions diff --git a/doc/evtxcat.md b/doc/evtxcat.md index 5e30f51..3ebc824 100644 --- a/doc/evtxcat.md +++ b/doc/evtxcat.md @@ -33,6 +33,48 @@ Display one or more events from an evtx file +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `evtxcat` + +This document contains the help content for the `evtxcat` command-line program. + +**Command Overview:** + +* [`evtxcat`↴](#evtxcat) + +## `evtxcat` + +Display one or more events from an evtx file + +**Usage:** `evtxcat [OPTIONS] ` + +###### **Arguments:** + +* `` — Name of the evtx file to read from + +###### **Options:** + +* `--min ` — filter: minimal event record identifier +* `--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 +* `-F`, `--format ` — output format + + Default value: `xml` + + Possible values: `json`, `xml` + +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence + + +
diff --git a/doc/evtxls.md b/doc/evtxls.md index 2cb1abe..e571dc9 100644 --- a/doc/evtxls.md +++ b/doc/evtxls.md @@ -61,6 +61,76 @@ Display one or more events from an evtx file +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `evtxls` + +This document contains the help content for the `evtxls` command-line program. + +**Command Overview:** + +* [`evtxls`↴](#evtxls) + +## `evtxls` + +Display one or more events from an evtx file + +**Usage:** `evtxls [OPTIONS] [EVTX_FILES]...` + +###### **Arguments:** + +* `` — Name of the evtx files to read from + +###### **Options:** + +* `-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 +* `-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 +* `-s`, `--sort ` — sort order + + Default value: `storage` + + Possible values: + - `storage`: + don't change order, output records as they are stored + - `record-id`: + sort by event record id + - `time`: + sort by date and time + +* `-b`, `--base-fields ` — display fields common to all events. multiple values must be separated by ',' + + Default values: `event-id`, `event-record-id` + + Possible values: + - `event-id`: + The identifier that the provider used to identify the event + - `event-record-id`: + The record number assigned to the event when it was logged + - `activity-id`: + A globally unique identifier that identifies the current activity. The events that are published with this identifier are part of the same activity + - `related-activity-id`: + A globally unique identifier that identifies the activity to which control was transferred to. The related events would then have this identifier as their ActivityID identifier + - `process-id`: + The ID of the process that created the event + +* `-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 + + +
diff --git a/doc/evtxscan.md b/doc/evtxscan.md index b612996..696e7c9 100644 --- a/doc/evtxscan.md +++ b/doc/evtxscan.md @@ -27,6 +27,42 @@ Find time skews in an evtx file +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `evtxscan` + +This document contains the help content for the `evtxscan` command-line program. + +**Command Overview:** + +* [`evtxscan`↴](#evtxscan) + +## `evtxscan` + +Find time skews in an evtx file + +**Usage:** `evtxscan [OPTIONS] ` + +###### **Arguments:** + +* `` — name of the evtx file to scan + +###### **Options:** + +* `-S`, `--show-records` — display also the contents of the records befor and after a time skew +* `-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 + + +
diff --git a/doc/hivescan.md b/doc/hivescan.md index 0edfb85..3040695 100644 --- a/doc/hivescan.md +++ b/doc/hivescan.md @@ -25,6 +25,40 @@ scans a registry hive file for deleted entries +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `hivescan` + +This document contains the help content for the `hivescan` command-line program. + +**Command Overview:** + +* [`hivescan`↴](#hivescan) + +## `hivescan` + +scans a registry hive file for deleted entries + +**Usage:** `hivescan [OPTIONS] ` + +###### **Arguments:** + +* `` — name of the file to scan + +###### **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 +* `-b` — output as bodyfile format + + +
diff --git a/doc/ipgrep.md b/doc/ipgrep.md index c947391..2f1dae8 100644 --- a/doc/ipgrep.md +++ b/doc/ipgrep.md @@ -33,6 +33,48 @@ search for IP addresses in text files +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `ipgrep` + +This document contains the help content for the `ipgrep` command-line program. + +**Command Overview:** + +* [`ipgrep`↴](#ipgrep) + +## `ipgrep` + +search for IP addresses in text files + +**Usage:** `ipgrep [OPTIONS] [FILE]...` + +###### **Arguments:** + +* `` + +###### **Options:** + +* `-i`, `--include ` — display only lines who match ALL of the specified criteria. Values are delimited with comma + + Possible values: `ipv4`, `ipv6`, `public`, `private`, `loopback` + +* `-x`, `--exclude ` — hide lines who match ANY of the specified criteria. Values are delimited with comma + + Possible values: `ipv4`, `ipv6`, `public`, `private`, `loopback` + +* `-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 + + +
diff --git a/doc/lnk2bodyfile.md b/doc/lnk2bodyfile.md new file mode 100644 index 0000000..3d528de --- /dev/null +++ b/doc/lnk2bodyfile.md @@ -0,0 +1,34 @@ +# Command-Line Help for `lnk2bodyfile` + +This document contains the help content for the `lnk2bodyfile` command-line program. + +**Command Overview:** + +* [`lnk2bodyfile`↴](#lnk2bodyfile) + +## `lnk2bodyfile` + +Parse Windows LNK files and create bodyfile output + +**Usage:** `lnk2bodyfile [OPTIONS] [LNK_FILES]...` + +###### **Arguments:** + +* `` — Names of the LNK files to read from + + Default value: `-` + +###### **Options:** + +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence + + + +
+ + + This document was generated automatically by + clap-markdown. + + diff --git a/doc/mactime2.md b/doc/mactime2.md index e038dc3..a309f31 100644 --- a/doc/mactime2.md +++ b/doc/mactime2.md @@ -35,6 +35,50 @@ replacement for `mactime` +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `mactime2` + +This document contains the help content for the `mactime2` command-line program. + +**Command Overview:** + +* [`mactime2`↴](#mactime2) + +## `mactime2` + +replacement for `mactime` + +**Usage:** `mactime2 [OPTIONS]` + +###### **Options:** + +* `-b ` — path to input file or '-' for stdin (files ending with .gz will be treated as being gzipped) + + Default value: `-` +* `-F`, `--format ` — output format, if not specified, default value is 'txt' + + 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 +* `-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` +* `-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 + + +
diff --git a/doc/pol_export.md b/doc/pol_export.md index 3d0f28d..a0f5dd1 100644 --- a/doc/pol_export.md +++ b/doc/pol_export.md @@ -23,6 +23,38 @@ Exporter for Windows Registry Policy Files +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `pol_export` + +This document contains the help content for the `pol_export` command-line program. + +**Command Overview:** + +* [`pol_export`↴](#pol_export) + +## `pol_export` + +Exporter for Windows Registry Policy Files + +**Usage:** `pol_export [OPTIONS] ` + +###### **Arguments:** + +* `` — Name of the file to read + +###### **Options:** + +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence + + +
diff --git a/doc/regdump.md b/doc/regdump.md index 4c9edd0..77d91d8 100644 --- a/doc/regdump.md +++ b/doc/regdump.md @@ -27,6 +27,42 @@ parses registry hive files and prints a bodyfile +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `regdump` + +This document contains the help content for the `regdump` command-line program. + +**Command Overview:** + +* [`regdump`↴](#regdump) + +## `regdump` + +parses registry hive files and prints a bodyfile + +**Usage:** `regdump [OPTIONS] ` + +###### **Arguments:** + +* `` — name of the file to dump + +###### **Options:** + +* `-L`, `--log ` — transaction LOG file(s). This argument can be specified one or two times +* `-b`, `--bodyfile` — print as bodyfile format +* `-I`, `--ignore-base-block` — ignore the base block (e.g. if it was encrypted by some ransomware) +* `-T`, `--hide-timestamps` — hide timestamps, if output is in reg format +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence + + +
diff --git a/doc/ts2date.md b/doc/ts2date.md index 94bc467..cf7a032 100644 --- a/doc/ts2date.md +++ b/doc/ts2date.md @@ -34,6 +34,49 @@ replaces UNIX timestamps in a stream by a formatted date +
+ + + This document was generated automatically by + clap-markdown. + + +# Command-Line Help for `ts2date` + +This document contains the help content for the `ts2date` command-line program. + +**Command Overview:** + +* [`ts2date`↴](#ts2date) + +## `ts2date` + +replaces UNIX timestamps in a stream by a formatted date + +**Usage:** `ts2date [OPTIONS] [INPUT_FILE] [OUTPUT_FILE]` + +###### **Arguments:** + +* `` — name of the file to read (default from stdin) + + Default value: `-` +* `` — name of the file to write (default to stdout) + + Default value: `-` + +###### **Options:** + +* `-v`, `--verbose` — More output per occurrence +* `-q`, `--quiet` — Less output per occurrence +* `-f`, `--from-timezone ` — name of offset of source timezone (or 'list' to display all possible values + + Default value: `UTC` +* `-t`, `--to-timezone ` — name of offset of destination timezone (or 'list' to display all possible values + + Default value: `UTC` + + +
diff --git a/scripts/update-md.sh b/scripts/update-md.sh index 5bfa3fa..e4be5a4 100755 --- a/scripts/update-md.sh +++ b/scripts/update-md.sh @@ -28,7 +28,7 @@ cat >README.md <<'EOF' - [x] [`es4forensics`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/es4forensics.md) - [x] [`hivescan`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/hivescan.md) - [x] [`ipgrep`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/ipgrep.md) - - [ ] [`lnk2bodyfile`](https://github.com/janstarke/lnk2bodyfile) + - [x] [`lnk2bodyfile`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/lnk2bodyfile.md) - [x] [`mactime2`](https://github.com/dfir-dd/dfir-toolkit/blob/main/doc/mactime2.md) - [ ] [`mft2bodyfile`](https://github.com/janstarke/mft2bodyfile) - [ ] [`ntdsextract2`](https://github.com/janstarke/ntdsextract2) diff --git a/src/bin/evtx2bodyfile/bf_data.rs b/src/bin/evtx2bodyfile/bf_data.rs index 842a602..e6d0762 100644 --- a/src/bin/evtx2bodyfile/bf_data.rs +++ b/src/bin/evtx2bodyfile/bf_data.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use anyhow::{anyhow, bail, Result}; use chrono::{DateTime, Utc}; -use dfir_toolkit::common::bodyfile::Bodyfile3Line; +use dfir_toolkit::common::bodyfile::{Bodyfile3Line, Modified}; use dfir_toolkit::es4forensics::{objects::WindowsEvent, TimelineObject}; use evtx::SerializedEvtxRecord; use getset::{Getters, Setters}; @@ -32,7 +32,7 @@ pub(crate) struct BfData<'a> { impl<'a> BfData<'a> { pub(crate) fn try_into_mactime(&self) -> Result { let bf_line = Bodyfile3Line::new() - .with_mtime(self.timestamp.timestamp()) + .with_mtime(Modified::from(self.timestamp.timestamp())) .with_owned_name(json!(self).to_string()); Ok(bf_line.to_string()) } diff --git a/src/bin/evtxanalyze/pstree/mod.rs b/src/bin/evtxanalyze/pstree/mod.rs index 24366bd..6ca0a7e 100644 --- a/src/bin/evtxanalyze/pstree/mod.rs +++ b/src/bin/evtxanalyze/pstree/mod.rs @@ -42,7 +42,7 @@ pub(crate) fn display_pstree(cli: &Cli) -> anyhow::Result<()> { .map(|r| r.expect("error reading event")) .map(Process::try_from) .filter_map(|r| r.expect("invalid event")) - .filter(|p| has_username(p)) + .filter(has_username) .map(|e| { let pid = UniquePid::from(&e); unique_pids diff --git a/src/bin/evtxanalyze/pstree/unique_pid.rs b/src/bin/evtxanalyze/pstree/unique_pid.rs index 2ff0df6..8d29d30 100644 --- a/src/bin/evtxanalyze/pstree/unique_pid.rs +++ b/src/bin/evtxanalyze/pstree/unique_pid.rs @@ -36,6 +36,8 @@ impl Ord for UniquePid { self.timestamp.cmp(&other.timestamp) } } + +#[allow(clippy::incorrect_partial_ord_impl_on_ord_type)] impl PartialOrd for UniquePid { fn partial_cmp(&self, other: &Self) -> Option { if self.pid != other.pid { diff --git a/src/bin/hivescan/hivescanapplication.rs b/src/bin/hivescan/hivescanapplication.rs index a289337..9e2826d 100644 --- a/src/bin/hivescan/hivescanapplication.rs +++ b/src/bin/hivescan/hivescanapplication.rs @@ -68,7 +68,7 @@ impl HiveScanApplication { let bf_line = Bodyfile3Line::new() .with_owned_name(bf_name) .with_inode(&format!("{:x}", entry.offset().0)) - .with_ctime(entry.nk().timestamp().timestamp()); + .with_ctime(entry.nk().timestamp().into()); println!("{}", bf_line); } else if entry.is_deleted() || force_print { println!( diff --git a/src/bin/lnk2bodyfile/cli.rs b/src/bin/lnk2bodyfile/cli.rs new file mode 100644 index 0000000..b9405e9 --- /dev/null +++ b/src/bin/lnk2bodyfile/cli.rs @@ -0,0 +1,22 @@ +use clap::{Parser, ValueHint}; +use dfir_toolkit::common::HasVerboseFlag; +use log::LevelFilter; +use clio::Input; + +/// Parse Windows LNK files and create bodyfile output +#[derive(Parser, Debug)] +#[clap(name=env!("CARGO_BIN_NAME"), author, version, long_about = None)] +pub (crate) struct Cli { + /// Names of the LNK files to read from + #[clap(value_parser, value_hint=ValueHint::FilePath, default_value="-", display_order(100))] + pub(crate) lnk_files: Vec, + + #[clap(flatten)] + pub (crate) verbose: clap_verbosity_flag::Verbosity, +} + +impl HasVerboseFlag for Cli { + fn log_level_filter(&self) -> LevelFilter { + self.verbose.log_level_filter() + } +} \ No newline at end of file diff --git a/src/bin/lnk2bodyfile/lnk_file.rs b/src/bin/lnk2bodyfile/lnk_file.rs new file mode 100644 index 0000000..cccdcb7 --- /dev/null +++ b/src/bin/lnk2bodyfile/lnk_file.rs @@ -0,0 +1,65 @@ +use anyhow::bail; +use clio::Input; +use dfir_toolkit::common::bodyfile::Bodyfile3Line; +use lnk::{LinkInfo, ShellLink, ShellLinkHeader}; + +pub struct LnkFile { + lnk_file: ShellLink, + file_name: String, +} + +impl LnkFile { + pub fn print_bodyfile(&self) { + self.print_bodyfile_for_me(); + } + + fn print_bodyfile_for_me(&self) { + let header = self.lnk_file.header(); + let localpath = match self.lnk_file.link_info() { + Some(s1) => match LinkInfo::local_base_path(s1) { + Some(s2) => s2, + None => "-", + }, + None => "-", + }; + let arguments = match self.lnk_file.arguments() { + Some(s) => s, + None => "-", + }; + let atime = ShellLinkHeader::access_time(header); + let mtime = ShellLinkHeader::write_time(header); + let crtime = ShellLinkHeader::creation_time(header); + + let bfline = Bodyfile3Line::new() + .with_name(&format!( + "{} {} (referred to by \"{}\")", + localpath, arguments, self.file_name + )) + .with_size(ShellLinkHeader::file_size(header).into()) + .with_crtime(crtime.datetime().into()) + .with_mtime(mtime.datetime().into()) + .with_atime(atime.datetime().into()); + + println!("{bfline}"); + } +} + +impl TryFrom<&Input> for LnkFile { + type Error = anyhow::Error; + + fn try_from(input: &Input) -> Result { + let file_path = input.path().to_path_buf(); + let file_name = file_path.file_name().unwrap().to_str().unwrap().to_string(); + match ShellLink::open(file_path) { + Ok(lnk_file) => Ok(Self { + lnk_file, + file_name, + }), + Err(e) => bail!( + "{:?}: The file {} is not in a valid ShellLink format", + e, + file_name + ), + } + } +} diff --git a/src/bin/lnk2bodyfile/main.rs b/src/bin/lnk2bodyfile/main.rs new file mode 100644 index 0000000..e634af1 --- /dev/null +++ b/src/bin/lnk2bodyfile/main.rs @@ -0,0 +1,32 @@ +use anyhow::{bail, Result}; +use cli::Cli; +use dfir_toolkit::common::FancyParser; + +use crate::lnk_file::LnkFile; + +mod cli; +mod lnk_file; + +fn main() -> Result<()> { + let cli = Cli::parse_cli(); + + if cli.lnk_files.iter().any(|f| !f.can_seek()) { + bail!( + "{} cannot read from a stream; you must specify a file", + env!("CARGO_BIN_NAME") + ); + } + + for filename in cli.lnk_files.iter() { + let lnkfile = match LnkFile::try_from(filename) { + Ok(file) => file, + Err(why) => { + log::error!("{why}"); + continue; + } + }; + lnkfile.print_bodyfile(); + } + + Ok(()) +} diff --git a/src/bin/mactime2/bodyfile/bodyfile_sorter.rs b/src/bin/mactime2/bodyfile/bodyfile_sorter.rs index fcd9045..d2f7ea6 100644 --- a/src/bin/mactime2/bodyfile/bodyfile_sorter.rs +++ b/src/bin/mactime2/bodyfile/bodyfile_sorter.rs @@ -1,4 +1,4 @@ -use dfir_toolkit::common::bodyfile::Bodyfile3Line; +use dfir_toolkit::common::bodyfile::{Bodyfile3Line, BehavesLikeI64}; use std::borrow::Borrow; use std::cmp::Ordering; use std::collections::{BTreeMap, HashSet}; @@ -59,13 +59,13 @@ fn insert_timestamp( line: Arc, ) { let timestamp = if flag.contains(MACBFlags::M) { - *line.get_mtime() + *line.get_mtime().as_ref().unwrap() } else if flag.contains(MACBFlags::A) { - *line.get_atime() + *line.get_atime().as_ref().unwrap() } else if flag.contains(MACBFlags::C) { - *line.get_ctime() + *line.get_ctime().as_ref().unwrap() } else if flag.contains(MACBFlags::B) { - *line.get_crtime() + *line.get_crtime().as_ref().unwrap() } else { -1 }; @@ -139,10 +139,10 @@ impl BodyfileSorter { } // delete the borrow to line // we need *some* value in mactimes! - if *line.get_mtime() == -1 - && *line.get_atime() == -1 - && *line.get_ctime() == -1 - && *line.get_crtime() == -1 + if line.get_mtime().is_none() + && line.get_atime().is_none() + && line.get_ctime().is_none() + && line.get_crtime().is_none() { insert_timestamp(&mut entries, MACBFlags::NONE, Arc::clone(&line)); continue; @@ -150,31 +150,31 @@ impl BodyfileSorter { let mut flags: [MACBFlags; 4] = [MACBFlags::NONE; 4]; - if *line.get_mtime() != -1 { + if line.get_mtime().is_some() { flags[0] |= MACBFlags::M; } - if *line.get_atime() != -1 { - if line.get_mtime() == line.get_atime() { + if line.get_atime().is_some() { + if line.get_mtime().as_ref() == line.get_atime().as_ref() { flags[0] |= MACBFlags::A; } else { flags[1] |= MACBFlags::A; } } - if *line.get_ctime() != -1 { - if line.get_mtime() == line.get_ctime() { + if line.get_ctime().is_some() { + if line.get_mtime().as_ref() == line.get_ctime().as_ref() { flags[0] |= MACBFlags::C; - } else if line.get_atime() == line.get_ctime() { + } else if line.get_atime().as_ref() == line.get_ctime().as_ref() { flags[1] |= MACBFlags::C; } else { flags[2] |= MACBFlags::C; } } - if *line.get_crtime() != -1 { - if line.get_mtime() == line.get_crtime() { + if line.get_crtime().is_some() { + if line.get_mtime().as_ref() == line.get_crtime().as_ref() { flags[0] |= MACBFlags::B; - } else if line.get_atime() == line.get_crtime() { + } else if line.get_atime().as_ref() == line.get_crtime().as_ref() { flags[1] |= MACBFlags::B; - } else if line.get_ctime() == line.get_crtime() { + } else if line.get_ctime().as_ref() == line.get_crtime().as_ref() { flags[2] |= MACBFlags::B; } else { flags[3] |= MACBFlags::B; diff --git a/src/bin/mactime2/output/csv_output.rs b/src/bin/mactime2/output/csv_output.rs index 28cd50c..7bf74c7 100644 --- a/src/bin/mactime2/output/csv_output.rs +++ b/src/bin/mactime2/output/csv_output.rs @@ -55,7 +55,7 @@ mod tests { 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); + let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into()); let entry = ListEntry { flags: MACBFlags::B, line: Arc::new(bf_line), @@ -82,7 +82,7 @@ mod tests { 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); + let bf_line = Bodyfile3Line::new().with_crtime(unix_ts.into()); let entry = ListEntry { flags: MACBFlags::B, line: Arc::new(bf_line), diff --git a/src/bin/mactime2/output/json_sorter.rs b/src/bin/mactime2/output/json_sorter.rs index de9c528..a001a1b 100644 --- a/src/bin/mactime2/output/json_sorter.rs +++ b/src/bin/mactime2/output/json_sorter.rs @@ -78,7 +78,7 @@ impl JsonSorter { log::warn!("raw entry is {}", line.to_string()); } else { for (ts, line) in lines { - entries.entry(ts).or_insert(BTreeSet::new()).insert(line); + entries.entry(ts).or_default().insert(line); } } } diff --git a/src/bin/mactime2/output/txt_output.rs b/src/bin/mactime2/output/txt_output.rs index 4a29786..97330eb 100644 --- a/src/bin/mactime2/output/txt_output.rs +++ b/src/bin/mactime2/output/txt_output.rs @@ -54,6 +54,7 @@ mod tests { use chrono_tz::Tz; use chrono_tz::TZ_VARIANTS; use dfir_toolkit::common::bodyfile::Bodyfile3Line; + use dfir_toolkit::common::bodyfile::Created; use std::sync::Arc; fn random_tz() -> Tz { @@ -67,7 +68,7 @@ mod tests { 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(unix_ts); + let bf_line = Bodyfile3Line::new().with_crtime(Created::from(unix_ts)); let entry = ListEntry { flags: MACBFlags::B, line: Arc::new(bf_line), @@ -97,7 +98,7 @@ mod tests { let tz = random_tz(); let output = TxtOutput::new(tz, tz); let unix_ts = rand::random::() as i64; - let bf_line = Bodyfile3Line::new().with_crtime(unix_ts); + let bf_line = Bodyfile3Line::new().with_crtime(Created::from(unix_ts)); let entry = ListEntry { flags: MACBFlags::B, line: Arc::new(bf_line), diff --git a/src/bin/pol_export/policy_file_entry.rs b/src/bin/pol_export/policy_file_entry.rs index b69f192..c79e556 100644 --- a/src/bin/pol_export/policy_file_entry.rs +++ b/src/bin/pol_export/policy_file_entry.rs @@ -56,7 +56,7 @@ fn parse_wide_string(reader: &mut R, _ro: &ReadOptions, _args: ( fn read_char(reader: &mut R, _ro: &ReadOptions, _args: ()) -> BinResult { let b: [u16; 1] = reader.read_le()?; - Ok(char::decode_utf16(b.into_iter()) + Ok(char::decode_utf16(b) .map(|r| r.unwrap_or(char::REPLACEMENT_CHARACTER)) .next() .unwrap()) diff --git a/src/bin/regdump/main.rs b/src/bin/regdump/main.rs index eacea07..8c19f2d 100644 --- a/src/bin/regdump/main.rs +++ b/src/bin/regdump/main.rs @@ -74,7 +74,7 @@ where if cli.display_bodyfile { let bf_line = Bodyfile3Line::new() .with_name(¤t_path) - .with_ctime(keynode.timestamp().timestamp()); + .with_ctime(keynode.timestamp().into()); println!("{}", bf_line); } else { if cli.hide_timestamps { diff --git a/src/common/bodyfile/bodyfile3.rs b/src/common/bodyfile/bodyfile3.rs index 770b024..009c810 100644 --- a/src/common/bodyfile/bodyfile3.rs +++ b/src/common/bodyfile/bodyfile3.rs @@ -4,6 +4,8 @@ use std::convert::TryFrom; use std::error::Error; use std::fmt; +use super::{Accessed, Modified, Changed, Created}; + /// /// This struct implements the bodyfile format generated by TSK 3.x /// @@ -17,10 +19,10 @@ pub struct Bodyfile3Line { uid: u64, gid: u64, size: u64, - atime: i64, - mtime: i64, - ctime: i64, - crtime: i64, + atime: Accessed, + mtime: Modified, + ctime: Changed, + crtime: Created, } impl Default for Bodyfile3Line { @@ -34,7 +36,7 @@ impl Bodyfile3Line { /// /// # Example /// ``` - /// use dfir_toolkit::common::bodyfile::Bodyfile3Line; + /// use dfir_toolkit::common::bodyfile::{BehavesLikeI64, Bodyfile3Line}; /// /// let bf = Bodyfile3Line::new(); /// assert_eq!(bf.get_md5(), "0"); @@ -44,10 +46,10 @@ impl Bodyfile3Line { /// assert_eq!(*bf.get_uid(), 0); /// assert_eq!(*bf.get_gid(), 0); /// assert_eq!(*bf.get_size(), 0); - /// assert_eq!(*bf.get_atime(), -1); - /// assert_eq!(*bf.get_mtime(), -1); - /// assert_eq!(*bf.get_ctime(), -1); - /// assert_eq!(*bf.get_crtime(), -1); + /// assert!(bf.get_atime().is_none()); + /// assert!(bf.get_mtime().is_none()); + /// assert!(bf.get_ctime().is_none()); + /// assert!(bf.get_crtime().is_none()); /// ``` pub fn new() -> Self { Self { @@ -58,10 +60,10 @@ impl Bodyfile3Line { uid: 0, gid: 0, size: 0, - atime: -1, - mtime: -1, - ctime: -1, - crtime: -1, + atime: Default::default(), + mtime: Default::default(), + ctime: Default::default(), + crtime: Default::default(), } } @@ -86,10 +88,10 @@ impl Bodyfile3Line { [with_uid] [uid] [u64]; [with_gid] [gid] [u64]; [with_size] [size] [u64]; - [with_atime] [atime] [i64]; - [with_mtime] [mtime] [i64]; - [with_ctime] [ctime] [i64]; - [with_crtime] [crtime] [i64]; + [with_atime] [atime] [Accessed]; + [with_mtime] [mtime] [Modified]; + [with_ctime] [ctime] [Changed]; + [with_crtime] [crtime] [Created]; )] pub fn method_name(mut self, attribute_name: attribute_type) -> Self { self.attribute_name = attribute_name; @@ -112,10 +114,10 @@ impl fmt::Display for Bodyfile3Line { /// .with_uid(1003) /// .with_gid(500) /// .with_size(126378) - /// .with_atime(12341) - /// .with_mtime(12342) - /// .with_ctime(12343) - /// .with_crtime(12344); + /// .with_atime(12341.into()) + /// .with_mtime(12342.into()) + /// .with_ctime(12343.into()) + /// .with_crtime(12344.into()); /// let line = bf.to_string(); /// assert_eq!(line, "4bad420da66571dac7f1ace995cc55c6|sample.txt|87915-128-1|r/rrwxrwxrwx|1003|500|126378|12341|12342|12343|12344") /// ``` @@ -216,7 +218,7 @@ pub enum Bodyfile3ParserError { /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|X|-1|-1|-1"), Err(Bodyfile3ParserError::IllegalATime)); /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-5|-1|-1|-1"), Err(Bodyfile3ParserError::IllegalATime)); /// let valid_bf = Bodyfile3Line::try_from("0||0||1|0|0|5|-1|-1|-1").unwrap(); - /// assert_eq!(*valid_bf.get_atime(), 5); + /// assert_eq!(*valid_bf.get_atime(), 5.into()); /// ``` IllegalATime, @@ -232,7 +234,7 @@ pub enum Bodyfile3ParserError { /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-1|X|-1|-1"), Err(Bodyfile3ParserError::IllegalMTime)); /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-1|-5|-1|-1"), Err(Bodyfile3ParserError::IllegalMTime)); /// let valid_bf = Bodyfile3Line::try_from("0||0||1|0|0|-1|5|-1|-1").unwrap(); - /// assert_eq!(*valid_bf.get_mtime(), 5); + /// assert_eq!(*valid_bf.get_mtime(), 5.into()); /// ``` IllegalMTime, @@ -248,7 +250,7 @@ pub enum Bodyfile3ParserError { /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-1|-1|X|-1"), Err(Bodyfile3ParserError::IllegalCTime)); /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-1|-1|-5|-1"), Err(Bodyfile3ParserError::IllegalCTime)); /// let valid_bf = Bodyfile3Line::try_from("0||0||1|0|0|-1|-1|5|-1").unwrap(); - /// assert_eq!(*valid_bf.get_ctime(), 5); + /// assert_eq!(*valid_bf.get_ctime(), 5.into()); /// ``` IllegalCTime, @@ -264,7 +266,7 @@ pub enum Bodyfile3ParserError { /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-1|-1|-1|X"), Err(Bodyfile3ParserError::IllegalCRTime)); /// assert_matches!(Bodyfile3Line::try_from("0||0||0|0|0|-1|-1|-1|-5"), Err(Bodyfile3ParserError::IllegalCRTime)); /// let valid_bf = Bodyfile3Line::try_from("0||0||1|0|0|-1|-1|-1|5").unwrap(); - /// assert_eq!(*valid_bf.get_crtime(), 5); + /// assert_eq!(*valid_bf.get_crtime(), 5.into()); /// ``` IllegalCRTime, } @@ -293,7 +295,7 @@ impl TryFrom<&str> for Bodyfile3Line { /// /// # Example /// ``` - /// use dfir_toolkit::common::bodyfile::{Bodyfile3Line, Bodyfile3ParserError}; + /// use dfir_toolkit::common::bodyfile::{Bodyfile3Line, Bodyfile3ParserError, Accessed, Modified, Changed, Created}; /// use std::convert::TryFrom; /// let bf_line = Bodyfile3Line::try_from("0|ls -l |wc|1|2|3|4|5|6|7|8|9").unwrap(); /// assert_eq!(bf_line.get_md5(), "0"); @@ -303,10 +305,10 @@ impl TryFrom<&str> for Bodyfile3Line { /// assert_eq!(*bf_line.get_uid(), 3); /// assert_eq!(*bf_line.get_gid(), 4); /// assert_eq!(*bf_line.get_size(), 5); - /// assert_eq!(*bf_line.get_atime(), 6); - /// assert_eq!(*bf_line.get_mtime(), 7); - /// assert_eq!(*bf_line.get_ctime(), 8); - /// assert_eq!(*bf_line.get_crtime(), 9); + /// assert_eq!(*bf_line.get_atime(), Accessed::from(6)); + /// assert_eq!(*bf_line.get_mtime(), Modified::from(7)); + /// assert_eq!(*bf_line.get_ctime(), Changed::from(8)); + /// assert_eq!(*bf_line.get_crtime(), Created::from(9)); /// ``` fn try_from(line: &str) -> Result { @@ -325,26 +327,11 @@ impl TryFrom<&str> for Bodyfile3Line { let size = str::parse::(parts[6 + name_chunks - 1]).or(Err(Self::Error::IllegalSize))?; - let atime = - str::parse::(parts[7 + name_chunks - 1]).or(Err(Self::Error::IllegalATime))?; - if atime < -1 { - return Err(Self::Error::IllegalATime); - } - let mtime = - str::parse::(parts[8 + name_chunks - 1]).or(Err(Self::Error::IllegalMTime))?; - if mtime < -1 { - return Err(Self::Error::IllegalMTime); - } - let ctime = - str::parse::(parts[9 + name_chunks - 1]).or(Err(Self::Error::IllegalCTime))?; - if ctime < -1 { - return Err(Self::Error::IllegalCTime); - } - let crtime = - str::parse::(parts[10 + name_chunks - 1]).or(Err(Self::Error::IllegalCRTime))?; - if crtime < -1 { - return Err(Self::Error::IllegalCRTime); - } + let atime = Accessed::try_from(parts[7 + name_chunks - 1])?; + let mtime = Modified::try_from(parts[8 + name_chunks - 1])?; + let ctime = Changed::try_from(parts[9 + name_chunks - 1])?; + let crtime = Created::try_from(parts[10 + name_chunks - 1])?; + Ok(Self { md5: md5.to_owned(), name: name.to_owned(), diff --git a/src/common/bodyfile/mod.rs b/src/common/bodyfile/mod.rs index 4f7853f..379cb4a 100644 --- a/src/common/bodyfile/mod.rs +++ b/src/common/bodyfile/mod.rs @@ -52,6 +52,9 @@ pub mod bodyfile3; pub use bodyfile3::*; +mod times; +pub use times::*; + #[cfg(test)] mod tests { use super::Bodyfile3Line; diff --git a/src/common/bodyfile/times.rs b/src/common/bodyfile/times.rs new file mode 100644 index 0000000..dc7f6a0 --- /dev/null +++ b/src/common/bodyfile/times.rs @@ -0,0 +1,107 @@ +use crate::common::bodyfile::Bodyfile3ParserError; +use std::fmt::Display; +use chrono::{DateTime, NaiveDateTime, Utc}; + +#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct Accessed(Option); +#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct Modified(Option); +#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct Changed(Option); +#[derive(Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct Created(Option); + +pub trait BehavesLikeI64: From + From> { + fn as_ref(&self) -> Option<&i64>; + fn is_none(&self) -> bool; + fn is_some(&self) -> bool; +} + +macro_rules! behaves_like_i64 { + ($t: ty, $error: expr) => { + impl BehavesLikeI64 for $t { + fn as_ref(&self) -> Option<&i64> { + self.0.as_ref() + } + fn is_none(&self) -> bool { + self.0.is_none() + } + fn is_some(&self) -> bool { + self.0.is_some() + } + } + + impl From for $t { + fn from(v: i64) -> Self { + if v == -1 { + Self(None) + } else { + Self(Some(v)) + } + } + } + + impl From> for $t { + fn from(v: Option) -> Self { + Self(v) + } + } + + impl From for $t { + fn from(v: NaiveDateTime) -> Self { + Self(Some(DateTime::::from_naive_utc_and_offset(v, Utc).timestamp())) + } + } + + impl From<&NaiveDateTime> for $t { + fn from(v: &NaiveDateTime) -> Self { + Self(Some(DateTime::::from_naive_utc_and_offset(*v, Utc).timestamp())) + } + } + + impl From> for $t { + fn from(v: DateTime::) -> Self { + Self(Some(v.timestamp())) + } + } + + impl From<&DateTime::> for $t { + fn from(v: &DateTime::) -> Self { + Self(Some(v.timestamp())) + } + } + + impl TryFrom<&str> for $t { + type Error = Bodyfile3ParserError; + fn try_from( + val: &str, + ) -> std::result::Result>::Error> { + let my_time = str::parse::(val).or(Err($error))?; + if my_time < -1 { + Err($error) + } else if my_time == -1 { + Ok(Self::default()) + } else { + Ok(Self(Some(my_time))) + } + } + } + + impl Display for $t { + fn fmt( + &self, + f: &mut std::fmt::Formatter<'_>, + ) -> std::result::Result<(), std::fmt::Error> { + match self.0 { + None => write!(f, "-1"), + Some(v) => write!(f, "{v}"), + } + } + } + }; +} + +behaves_like_i64!(Accessed, Bodyfile3ParserError::IllegalATime); +behaves_like_i64!(Modified, Bodyfile3ParserError::IllegalMTime); +behaves_like_i64!(Changed, Bodyfile3ParserError::IllegalCTime); +behaves_like_i64!(Created, Bodyfile3ParserError::IllegalCRTime); diff --git a/src/es4forensics/ecs/objects/posix_file.rs b/src/es4forensics/ecs/objects/posix_file.rs index 67cb09e..5b6d961 100644 --- a/src/es4forensics/ecs/objects/posix_file.rs +++ b/src/es4forensics/ecs/objects/posix_file.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use anyhow::Result; -use crate::common::bodyfile::Bodyfile3Line; +use crate::common::bodyfile::{Bodyfile3Line, BehavesLikeI64}; use chrono_tz::Tz; use serde::Serialize; @@ -22,11 +22,11 @@ pub struct PosixFile { } impl PosixFile { - fn load_timestamp(ts: i64, tz: &Tz) -> Result> { - match ts { - -1 => Ok(None), - _ => { - Ok(Some((ts, tz).try_into()?)) + fn load_timestamp(ts: &TS, tz: &Tz) -> Result> where TS: BehavesLikeI64 { + match ts.as_ref() { + None => Ok(None), + Some(ts) => { + Ok(Some((*ts, tz).try_into()?)) } } } @@ -118,10 +118,10 @@ impl TryFrom<(&Bodyfile3Line, &Tz)> for PosixFile { uid: *bfline.get_uid(), gid: *bfline.get_gid(), size: *bfline.get_size(), - atime: Self::load_timestamp(*bfline.get_atime(), src_tz)?, - mtime: Self::load_timestamp(*bfline.get_mtime(), src_tz)?, - ctime: Self::load_timestamp(*bfline.get_ctime(), src_tz)?, - crtime: Self::load_timestamp(*bfline.get_crtime(), src_tz)?, + atime: Self::load_timestamp(bfline.get_atime(), src_tz)?, + mtime: Self::load_timestamp(bfline.get_mtime(), src_tz)?, + ctime: Self::load_timestamp(bfline.get_ctime(), src_tz)?, + crtime: Self::load_timestamp(bfline.get_crtime(), src_tz)?, }) } } diff --git a/tests/data/lnk2bodyfile/Obsidian.lnk b/tests/data/lnk2bodyfile/Obsidian.lnk new file mode 100644 index 0000000..48808f8 Binary files /dev/null and b/tests/data/lnk2bodyfile/Obsidian.lnk differ diff --git a/tests/data/lnk2bodyfile/x64dbg.lnk b/tests/data/lnk2bodyfile/x64dbg.lnk new file mode 100755 index 0000000..a9f589e Binary files /dev/null and b/tests/data/lnk2bodyfile/x64dbg.lnk differ diff --git a/tests/lnk2bodyfile/mod.rs b/tests/lnk2bodyfile/mod.rs new file mode 100644 index 0000000..15d2d19 --- /dev/null +++ b/tests/lnk2bodyfile/mod.rs @@ -0,0 +1,2 @@ +mod x64dbg; +mod obsidian; \ No newline at end of file diff --git a/tests/lnk2bodyfile/obsidian.rs b/tests/lnk2bodyfile/obsidian.rs new file mode 100644 index 0000000..b81ebad --- /dev/null +++ b/tests/lnk2bodyfile/obsidian.rs @@ -0,0 +1,39 @@ +use std::{ + io::{BufRead, BufReader, Cursor}, + path::PathBuf, +}; + +use assert_cmd::Command; +use dfir_toolkit::common::bodyfile::Bodyfile3Line; + +#[test] +fn test_obsidian() { + let mut cmd = Command::cargo_bin("lnk2bodyfile").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("lnk2bodyfile"); + data_path.push("Obsidian.lnk"); + + let result = cmd.arg(data_path).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(); + + let bfline = Bodyfile3Line::try_from(&first_line[..]).unwrap(); + assert_eq!(bfline.get_name(), r#"C:\Users\Administrator\AppData\Local\Obsidian\Obsidian.exe - (referred to by "Obsidian.lnk")"#); + assert_eq!(*bfline.get_size(), 163290848); + assert_eq!(*bfline.get_atime(), 1698927512.into()); + assert_eq!(*bfline.get_mtime(), 1697213650.into()); + assert_eq!(*bfline.get_ctime(), Default::default()); + assert_eq!(*bfline.get_crtime(), 1698927512.into()); + + assert!(lines_iterator.next().is_none()); +} diff --git a/tests/lnk2bodyfile/x64dbg.rs b/tests/lnk2bodyfile/x64dbg.rs new file mode 100644 index 0000000..79d0d7e --- /dev/null +++ b/tests/lnk2bodyfile/x64dbg.rs @@ -0,0 +1,39 @@ +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_x64dbg() { + let mut cmd = Command::cargo_bin("lnk2bodyfile").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("lnk2bodyfile"); + data_path.push("x64dbg.lnk"); + + let result = cmd.arg(data_path).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(); + + let bfline = Bodyfile3Line::try_from(&first_line[..]).unwrap(); + assert_eq!(bfline.get_name(), r#"C:\Program Files\x64dbg\release\x64\x64dbg.exe - (referred to by "x64dbg.lnk")"#); + assert_eq!(*bfline.get_size(), 172768); + assert_eq!(*bfline.get_atime(), Accessed::from(1695724808)); + assert_eq!(*bfline.get_mtime(), Modified::from(1695724422)); + assert_eq!(*bfline.get_ctime(), Changed::default()); + assert_eq!(*bfline.get_crtime(), Created::from(1695250410)); + + assert!(lines_iterator.next().is_none()); +} diff --git a/tests/mod.rs b/tests/mod.rs index 9d99919..b6c5636 100644 --- a/tests/mod.rs +++ b/tests/mod.rs @@ -1,2 +1,3 @@ mod mactime2; -mod ts2date; \ No newline at end of file +mod ts2date; +mod lnk2bodyfile; \ No newline at end of file