diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 5cb562e5..f4f0e522 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -45,7 +45,7 @@ jobs: - name: Compile script tester binary Linux if: matrix.info.os == 'ubuntu-24.04' - run: git clone https://github.com/puffyCid/artemis.git && cd artemis/forensics && cargo build --release --examples && cp ../target/release/examples/script_tester ../../tests/linux && cp ../target/release/examples/script_tester ../../tests/applications + run: git clone https://github.com/puffyCid/artemis.git && cd artemis/forensics && cargo build --release --examples && cp ../target/release/examples/script_tester ../../tests/linux && cp ../target/release/examples/script_tester ../../tests/applications && cp ../target/release/examples/script_tester ../../tests/esxi - name: Install esbuild and colorama macOS if: matrix.info.os == 'macOS-latest' @@ -74,3 +74,7 @@ jobs: - name: Compile and run Application tests if: matrix.info.os == 'ubuntu-24.04' run: cd tests/applications && sudo python3 compile_tests.py + + - name: Compile and run ESXi tests + if: matrix.info.os == 'ubuntu-24.04' + run: cd tests/esxi && sudo python3 compile_tests.py diff --git a/artemis-docs/docs/API/API Scenarios/acquire_files.md b/artemis-docs/docs/API/API Scenarios/acquire_files.md deleted file mode 100644 index 226ae7d3..00000000 --- a/artemis-docs/docs/API/API Scenarios/acquire_files.md +++ /dev/null @@ -1,143 +0,0 @@ ---- -description: Acquiring local files ---- - -# Acquiring Files - -Using the artemis API you may -[acquire](http://localhost:3000/artemis-api/docs/API/Helper/filesystem) files -from a system using OS APIs.\ -Currently artemis supports copying file(s) to a local location or uploading to -the cloud. - -In order to acquire a file you will need two things: - -- Path to file to acquire -- An Output object structure - -The Output object is used to tell artemis how to output the acquired file. This -format is described in the artemis -[format docs](../../Intro/Collections/format.md) - -```typescript -/** - * An interface to output data using `artemis` - */ -export interface Output { - /**Name of output directory */ - name: string; - /**Target directory for output */ - directory: string; - /**Format of output: JSON or JSONL */ - format: Format; - /**Compress data with GZIP and all files with ZIP */ - compress: boolean; - /**Endpoint ID */ - endpoint_id: string; - /**ID for collection. Must be positive number */ - collection_id: number; - /**Output type: local, azure, aws, or gcp */ - output: OutputType; - /**URL associated with remote upload */ - url?: string; - /**API key required for remote upload */ - api_key?: string; -} -``` - -When acquiring files there are three caveats in regards to the Output object: - -- Format setting. This is option not applied to file acquisitions -- Compressing setting. File acquisitions are always compressed regardless of - this setting. -- OutputType setting. Currently only local or GCP output types can be used. - -## How to acquire files - -You have to use the artemis [api](../../API/overview.md) in order to acquire -files - -When you acquire a file and output locally artemis will compress the target file -using gzip compression. In addition, artemis will acquire metadata associated -with the target file and then compress both the target file and metadata using -zip compression. - -When you acquire a file and output to the cloud artemis will compress the target -file using gzip compression. In addition, artemis will acquire metadata and add -the metadata as custom tags to the uploaded file. - -:::warning - -Currently artemis does not perform any disk size checks when acquiring a file. -If you choose to output a file locally, you will need to ensure that there is -enough space on the target path to store the file - -::: - -:::warning - -As described already in the [uploads docs](../../Intro/Collections/uploads.md), -currently artemis does not securely protect the remote API key. Make sure the -account associated with the API has only permissions needed by artemis. - -::: - -## Sample API Script - -If you want to acquire a file and upload to GCP you would use a script like -below: - -```typescript -import { - Format, - Output, - OutputType, -} from "./artemis-api/src/system/output"; -import { acquireFile } from "./artemis-api/src/filesystem/acquire"; - -function main() { - const out: Output = { - name: "js_acquire", - directory: "tmp", - format: Format.JSON, - compress: false, - endpoint_id: "adbcd", - collection_id: 0, - output: OutputType.GCP, - url: "https://storage.googleapis.com/upload/storage/v1/b/", - api_key: "api_key_for_gcp", - }; - const status = acquireFile("/Users/dev/Downloads/gentoo.iso", out); - console.log(status); -} - -main(); -``` - -If you want to acquire a file and output locally you would use a script like -below: - -```typescript -import { - Format, - Output, - OutputType, -} from "./artemis-api/src/system/output"; -import { acquireFile } from "./artemis-api/src/filesystem/acquire"; - -function main() { - const out: Output = { - name: "js_acquire", - directory: "./tmp", - format: Format.JSON, - compress: false, - endpoint_id: "adbcd", - collection_id: 0, - output: OutputType.Local, - }; - const status = acquireFile("/Users/dev/Downloads/gentoo.iso", out); - console.log(status); -} - -main(); -``` diff --git a/artemis-docs/docs/API/API Scenarios/browsers.md b/artemis-docs/docs/API/API Scenarios/browsers.md index 6e626ce2..26a1dc08 100644 --- a/artemis-docs/docs/API/API Scenarios/browsers.md +++ b/artemis-docs/docs/API/API Scenarios/browsers.md @@ -1,217 +1,263 @@ ---- -description: How to extract data from browsers ---- - -# Browser Artifacts - -Artemis supports extracting forensic artifacts for a lot of different browser applications. Some notable browsers include: - -- Chrome -- Edge -- Chromium -- FireFox -- Epiphany -- Falkon -- Safari - -Example artifacts artemis supports include: URL history, cookies, bookmarks, preferences, and a lot more - -Using the API we can easily extract all supported artifacts and timeline them into a format that we can then easily upload to [Timesketch](https://timesketch.org/). - -## Retrospect - -The easiest want to extract browser artifacts is to use the API function `retrospect`. -This function has very similar capabilities as the tool [Hindsight](https://github.com/obsidianforensics/hindsight). -It will automatically parse and extract all supported artifacts from a provided browser application. - -An example is below: - -```typescript -import { Chrome, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; - -function main() { - // Change this if you are going to run on a different platform. - // Ex: Linux (PlatformType.Linux) or macOS (PlatformType.Darwin) - const plat = PlatformType.Windows; - - // If we acquired a user's Chrome profile - // We could also provide that as an alternative path - // Ex: /home/analyst/Downloads/user1_chrome - // This is optional - const alt_path = undefined; - // This is optional. The default value is false - const enable_unfold = false; - - // Initialize a Chrome application class - // By default this will extract Chrome artifacts for all users on the PlatformType - // This example will attempt to extract Chrome artifacts for the Platform.Type Windows - // If the alt_path != undefined. That will override the default Chrome paths for the provided platform - // Ex: If alt_path = "/home/analyst/Downloads/user1_chrome" then that will override the default Chrome paths - const chrome = new Chrome(plat, enable_unfold, alt_path); - - // Since `retrospect` is handling everything - // We have to specify how `retrospect` should output our data - const out: Output = { - /** Directory will be created if it does not exist */ - name: "chrome_info", - /** Directory will be created if it does not exist */ - directory: "./tmp", - /** JSONL is the easiest format to upload to Timesketch */ - format: Format.JSONL, - compress: false, - /** This can be set to false. The artemis API will automatically timeline for us */ - timeline: false, - endpoint_id: "", - collection_id: 0, - /** Artemis also supports uploading to GCP, AZURE, and AWS */ - output: OutputType.LOCAL - }; - - // Now timeline all supported artifacts - chrome.retrospect(out); -} - -main(); -``` - -Now we can compile the code above to JavaScript using [esbuild](https://esbuild.github.io/) and then run with artemis! - -- esbuild --bundle --outfile=main.js main.ts -- artemis -j main.js - -You should get output at `./tmp/chrome_info`. - -### Multi Browser Support - -Retrospect supports additional browsers besides Chromium based browsers. You can run against all browsers supported by artemis! - -```typescript -import { Chrome, Firefox, Chromium, Edge, Epiphany, Falkon, - Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; - -function main() { - // Change this if you are going to run on a different platform. - // Ex: Windows (PlatformType.Windows) or macOS (PlatformType.Darwin) - const plat = PlatformType.Linux; - - // If we acquired a user's Chrome profile - // We could also provide that as an alternative path - // Ex: /home/analyst/Downloads/user1_chrome - // This is optional - const alt_path = undefined; - // This is optional. The default value is false - const enable_unfold = false; - - // Initialize a Chrome application class - // By default this will extract Chrome artifacts for all users on the PlatformType - // This example will attempt to extract Chrome artifacts for the Platform.Type Windows - // If the alt_path != undefined. That will override the default Chrome paths for the provided platform - // Ex: If alt_path = "/home/analyst/Downloads/user1_chrome" then that will override the default Chrome paths - const chrome = new Chrome(plat, enable_unfold, alt_path); - - const fire = new Firefox(plat); - const edge = new Edge(plat); - const chromium = new Chromium(plat); - // Epiphany only supports the Linux platform. - // But we can also provide an alt_path if we want to parse on a non-linux platform - const epiphany = new Epiphany(); - const falkon = new Falkon(plat); - - // Since `retrospect` is handling everything - // We have to specify how `retrospect` should output our data - const out: Output = { - /** Directory will be created if it does not exist */ - name: "browsers_info", - /** Directory will be created if it does not exist */ - directory: "./tmp", - /** JSONL is the easiest format to upload to Timesketch */ - format: Format.JSONL, - compress: false, - /** This can be set to false. The artemis API will automatically timeline for us */ - timeline: false, - endpoint_id: "", - collection_id: 0, - /** Artemis also supports uploading to GCP, AZURE, and AWS */ - output: OutputType.LOCAL - }; - - // Now timeline all supported artifacts for all browsers! - chrome.retrospect(out); - edge.retrospect(out); - fire.retrospect(out); - chromium.retrospect(out); - epiphany.retrospect(out); - falkon.retrospect(out); -} - -main(); -``` - -Now we can compile the code above to JavaScript using [esbuild](https://esbuild.github.io/) and then run with artemis! - -- esbuild --bundle --outfile=main.js main.ts -- artemis -j main.js - -You should get output at `./tmp/browsers_info`. - -:::info - -You can shrink to size of you JavaScript script by using the minify option in esbuild -- esbuild --minify --bundle --outfile=main.js main.ts - -::: - -### Unfold - -Artemis has one optional feature that can be used to enhance browser artifacts. This feature is called `unfold`. -Unfold is a URL parser that attempts to extract data in URLs. It is very similar to the tool [unfurl](https://github.com/obsidianforensics/unfurl). -By default unfold is **disabled**. - -But enabling unfold is very easy! Just add it as an parameter when initializing a browser artifact class. - -An example is below: -```typescript -import { Chrome, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; - -function main() { - // Change this if you are going to run on a different platform. - // Ex: Linux (PlatformType.Linux) or macOS (PlatformType.Darwin) - const plat = PlatformType.Windows; - const enable_unfold = true; - - // Initialize a Chrome application class - // By default this will extract Chrome artifacts for all users on the PlatformType - // When unfold is enabled, artemis will attempt to extract additional information from URLs - const chrome = new Chrome(plat, enable_unfold); - - // Since `retrospect` is handling everything - // We have to specify how `retrospect` should output our data - const out: Output = { - /** Directory will be created if it does not exist */ - name: "chrome_info", - /** Directory will be created if it does not exist */ - directory: "./tmp", - /** JSONL is the easiest format to upload to Timesketch */ - format: Format.JSONL, - compress: false, - /** This can be set to false. The artemis API will automatically timeline for us */ - timeline: false, - endpoint_id: "", - collection_id: 0, - /** Artemis also supports uploading to GCP, AZURE, and AWS */ - output: OutputType.LOCAL - }; - - // Now timeline all supported artifacts - chrome.retrospect(out); -} - -main(); -``` - -Now we can compile the code above to JavaScript using [esbuild](https://esbuild.github.io/) and then run with artemis! - -- esbuild --bundle --outfile=main.js main.ts -- artemis -j main.js - -You should get output at `./tmp/chrome_info`. \ No newline at end of file +--- +description: How to extract data from browsers +--- + +# Browser Artifacts + +Artemis supports extracting forensic artifacts for a lot of different browser applications. Some notable browsers include: + +- Chrome +- Edge +- Chromium +- FireFox +- Epiphany +- Falkon +- Safari + +Example artifacts artemis supports include: URL history, cookies, bookmarks, preferences, and a lot more + +Using the API we can easily extract all supported artifacts and timeline them into a format that we can then easily upload to [Timesketch](https://timesketch.org/). + +## Retrospect + +The easiest want to extract browser artifacts is to use the API function `retrospect`. +This function has very similar capabilities as the tool [Hindsight](https://github.com/obsidianforensics/hindsight). +It will automatically parse and extract all supported artifacts from a provided browser application. + +An example is below: + +```typescript +import { Chrome, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; + +function main() { + // Change this if you are going to run on a different platform. + // Ex: Linux (PlatformType.Linux) or macOS (PlatformType.Darwin) + const plat = PlatformType.Windows; + + // If we acquired a user's Chrome profile + // We could also provide that as an alternative path + // Ex: /home/analyst/Downloads/user1_chrome + // This is optional + const alt_path = undefined; + // This is optional. The default value is false + const enable_unfold = false; + + // Initialize a Chrome application class + // By default this will extract Chrome artifacts for all users on the PlatformType + // This example will attempt to extract Chrome artifacts for the Platform.Type Windows + // If the alt_path != undefined. That will override the default Chrome paths for the provided platform + // Ex: If alt_path = "/home/analyst/Downloads/user1_chrome" then that will override the default Chrome paths + const chrome = new Chrome(plat, enable_unfold, alt_path); + + // Since `retrospect` is handling everything + // We have to specify how `retrospect` should output our data + const out: Output = { + /** Directory will be created if it does not exist */ + name: "chrome_info", + /** Directory will be created if it does not exist */ + directory: "./tmp", + /** JSONL is the easiest format to upload to Timesketch */ + format: Format.JSONL, + compress: false, + /** This can be set to false. The artemis API will automatically timeline for us */ + timeline: false, + endpoint_id: "", + collection_id: 0, + /** Artemis also supports uploading to GCP, AZURE, and AWS */ + output: OutputType.LOCAL + }; + + // Now timeline all supported artifacts + chrome.retrospect(out); +} + +main(); +``` + +Now we can compile the code above to JavaScript using [esbuild](https://esbuild.github.io/) and then run with artemis! + +- esbuild --bundle --outfile=main.js main.ts +- artemis -j main.js + +You should get output at `./tmp/chrome_info`. + +### Multi Browser Support + +Retrospect supports additional browsers besides Chromium based browsers. You can run against all browsers supported by artemis! + +```typescript +import { Chrome, Firefox, Chromium, Edge, Epiphany, Falkon, + Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; + +function main() { + // Change this if you are going to run on a different platform. + // Ex: Windows (PlatformType.Windows) or macOS (PlatformType.Darwin) + const plat = PlatformType.Linux; + + // If we acquired a user's Chrome profile + // We could also provide that as an alternative path + // Ex: /home/analyst/Downloads/user1_chrome + // This is optional + const alt_path = undefined; + // This is optional. The default value is false + const enable_unfold = false; + + // Initialize a Chrome application class + // By default this will extract Chrome artifacts for all users on the PlatformType + // This example will attempt to extract Chrome artifacts for the Platform.Type Windows + // If the alt_path != undefined. That will override the default Chrome paths for the provided platform + // Ex: If alt_path = "/home/analyst/Downloads/user1_chrome" then that will override the default Chrome paths + const chrome = new Chrome(plat, enable_unfold, alt_path); + + const fire = new Firefox(plat); + const edge = new Edge(plat); + const chromium = new Chromium(plat); + // Epiphany only supports the Linux platform. + // But we can also provide an alt_path if we want to parse on a non-linux platform + const epiphany = new Epiphany(); + const falkon = new Falkon(plat); + + // Since `retrospect` is handling everything + // We have to specify how `retrospect` should output our data + const out: Output = { + /** Directory will be created if it does not exist */ + name: "browsers_info", + /** Directory will be created if it does not exist */ + directory: "./tmp", + /** JSONL is the easiest format to upload to Timesketch */ + format: Format.JSONL, + compress: false, + /** This can be set to false. The artemis API will automatically timeline for us */ + timeline: false, + endpoint_id: "", + collection_id: 0, + /** Artemis also supports uploading to GCP, AZURE, and AWS */ + output: OutputType.LOCAL + }; + + // Now timeline all supported artifacts for all browsers! + chrome.retrospect(out); + edge.retrospect(out); + fire.retrospect(out); + chromium.retrospect(out); + epiphany.retrospect(out); + falkon.retrospect(out); +} + +main(); +``` + +Now we can compile the code above to JavaScript using [esbuild](https://esbuild.github.io/) and then run with artemis! + +- esbuild --bundle --outfile=main.js main.ts +- artemis -j main.js + +You should get output at `./tmp/browsers_info`. + +:::info + +You can shrink to size of you JavaScript script by using the minify option in esbuild +- esbuild --minify --bundle --outfile=main.js main.ts + +::: + +### Unfold + +Artemis has one optional feature that can be used to enhance browser artifacts. This feature is called `unfold`. +Unfold is a URL parser that attempts to extract data in URLs. It is very similar to the tool [unfurl](https://github.com/obsidianforensics/unfurl). +By default unfold is **disabled**. + +But enabling unfold is very easy! Just add it as an parameter when initializing a browser artifact class. + +An example is below: +```typescript +import { Chrome, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; + +function main() { + // Change this if you are going to run on a different platform. + // Ex: Linux (PlatformType.Linux) or macOS (PlatformType.Darwin) + const plat = PlatformType.Windows; + const enable_unfold = true; + + // Initialize a Chrome application class + // By default this will extract Chrome artifacts for all users on the PlatformType + // When unfold is enabled, artemis will attempt to extract additional information from URLs + const chrome = new Chrome(plat, enable_unfold); + + // Since `retrospect` is handling everything + // We have to specify how `retrospect` should output our data + const out: Output = { + /** Directory will be created if it does not exist */ + name: "chrome_info", + /** Directory will be created if it does not exist */ + directory: "./tmp", + /** JSONL is the easiest format to upload to Timesketch */ + format: Format.JSONL, + compress: false, + /** This can be set to false. The artemis API will automatically timeline for us */ + timeline: false, + endpoint_id: "", + collection_id: 0, + /** Artemis also supports uploading to GCP, AZURE, and AWS */ + output: OutputType.LOCAL + }; + + // Now timeline all supported artifacts + chrome.retrospect(out); +} + +main(); +``` + +Now we can compile the code above to JavaScript using [esbuild](https://esbuild.github.io/) and then run with artemis! + +- esbuild --bundle --outfile=main.js main.ts +- artemis -j main.js + +You should get output at `./tmp/chrome_info`. + + +## Chromium Based Browsers + +Chromium is a very popular open source browser. Many browsers are based on the Chromium source code such as: +- Chrome +- Edge +- Brave +- Comet +- Many more + +Luckily all of these Chromium based browsers have the same artifact and database formats. If you encounter a new Chromium based browser, it is really easy to parse the data with artemis! + +### Opera + +Modern versions of [Opera](https://en.wikipedia.org/wiki/Opera_(web_browser)) are based on Chromium. As of 2026-03-29, the artemis API does not expose a Opera API class to extract data. + +However, we can still extract data by leveraging the Chromium API class since Opera is based on Chromium. + +The easiest way to parse Opera browser data is to initialize the Chromium API class by providing the path to the Opera profile. + +```typescript +import { Chromium, extractAppCrash, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; + +function main() { + const path = "C:\\Users\\user\\AppData\\Local\\Opera Path\\User Data" + + // The `path` variable overrides the default Chromium path + const client = new Chromium(PlatformType.Windows, path); + const out:Output = { + name: "opera_script", + directory: "./tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + output: OutputType.LOCAL + }; + + // Since Opera is based on Chromium. All Chromium artifacts should work! + client.retrospect(out); +} + +main(); +``` diff --git a/artemis-docs/docs/API/API Scenarios/esxi.md b/artemis-docs/docs/API/API Scenarios/esxi.md new file mode 100644 index 00000000..cc6e6d8c --- /dev/null +++ b/artemis-docs/docs/API/API Scenarios/esxi.md @@ -0,0 +1,595 @@ +--- +description: How to extract data from ESXi +--- + +# ESXi Artifacts + +Artemis supports running and parsing artifacts on an ESXi system. Most ESXi artifacts are plaintext files, so we will need to use the artemis API (TypeScript) in order to parse the data. + +:::info + +You do not have to run artemis on ESXi in order to parse ESXi artifacts. +You can collect ESXi artifacts using tools like [UAC](https://github.com/tclahr/uac) and parse locally. + +All ESXi artifacts parsed via the artemis API can be parsed locally! + +::: + +## Example Script + +One of the benefits of parsing ESXi data with artemis is that the data can be saved to csv, json, or jsonl. The output is Timesketch compatible! + +An example ESXi parsing script (`main.ts`) is below. It parses several ESXi artifacts. + +```typescript +import { dumpData, esxiAccounts, Format, getVibs, Output, OutputType, shellLogHistory, sysLogEsxi } from "./artemis-api/mod"; +import { EsxiError } from "./artemis-api/src/esxi/error"; + +function main() { + const out: Output = { + name: "esxi_artifacts", + directory: "./tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + /** + * Remote uploads are not supported when **running** on ESXi + */ + output: OutputType.LOCAL + }; + + console.log("Parsing VIBs..."); + const vib_results = getVibs(); + if (vib_results instanceof EsxiError) { + console.error(vib_results); + return; + } + + dumpData(vib_results, "esxi_vibs", out); + + console.log("Parsing syslog..."); + const log_results = sysLogEsxi(); + if (log_results instanceof EsxiError) { + console.error(log_results); + return; + } + + dumpData(log_results, "esxi_syslog", out); + + console.log("Parsing shell.log..."); + const shell_log = shellLogHistory(); + if (shell_log instanceof EsxiError) { + console.error(shell_log); + return; + } + + dumpData(shell_log, "esxi_shelllog", out); + + console.log("Parsing ESXi accounts..."); + const accounts = esxiAccounts(); + if (accounts instanceof EsxiError) { + console.error(accounts); + return; + } + + dumpData(accounts, "esxi_accounts", out); +} + +main(); +``` + +The TypeScript script above parses: + +- vSphere Installation Bundles (VIB) +- Shell log data +- Syslog data +- ESXi User accounts + +We can bundle and compile to JavaScript with esbuild: + +- esbuild --bundle --outfile=main.js main.ts + +``` + esbuild --bundle --outfile=main.js main.ts + + main.js 14.5kb + +⚡ Done in 34ms +``` + +:::info + +Run esbuild with the --minify argument to make your script smaller! + +``` +esbuild --bundle --minify --outfile=main.js main.ts + + main.js 6.9kb + +⚡ Done in 35ms +``` + +::: + +### Parsing local ESXi data + +If you ran UAC or have ESXi data locally you can still leverage the artemis API. +Every ESXi artifact function accepts an optional alternative path to the artifact. + +For example: + +```typescript +import { dumpData, Format, getVibs, Output, OutputType } from "./artemis-api/mod"; +import { EsxiError } from "./artemis-api/src/esxi/error"; + +function main() { + const out: Output = { + name: "esxi_artifacts", + directory: "./tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + /** + * Remote uploads are not supported when **running** on ESXi + */ + output: OutputType.LOCAL + }; + + console.log("Grabbing VIBs..."); + // We can provide an alternative glob to a directory containing the VIB xml files + const vib_results = getVibs("directory/containing/vibs/*.xml"); + if (vib_results instanceof EsxiError) { + console.error(vib_results); + return; + } + + dumpData(vib_results, "esxi_vibs", out); +} + +main(); +``` + +## Install and Run Artemis on ESXi + +The recommended way to execute artemis is to package artemis as a VIB file and install it. +Since the artemis.vib file is not signed, you will need to force install the vib package (requires root privileges). + +``` +esxcli software vib install -f -v file:///vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/artemis.vib +``` + +You will then need to upload the `main.js` file to ESXi via SSH/SCP. Once the `main.js` file is uploaded you can execute with: + +- artemis -j main.js + + +``` +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] artemis -j main.js +[artemis] Starting artemis collection! +Parsing VIBs... +Parsing syslog... +Parsing shell.log... +Parsing ESXi accounts... +[artemis] Finished artemis collection! +``` + +## Artifact Output + +Once you run the script above you should see several output files under `./tmp/esxi_artifacts/*.jsonl|.log` + +Sample output for `syslog` + +```jsonl +{"message":"Partially resolved path: /usr/lib/vmware/config","datetime":"2026-04-05T02:14:36.173Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /.ash_history","datetime":"2026-04-05T02:14:36.173Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /.profile","datetime":"2026-04-05T02:14:36.173Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /etc/motd-dev","datetime":"2026-04-05T02:14:36.173Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /opt/hp/hpssacli","datetime":"2026-04-05T02:14:36.174Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /opt/smartstorageadmin","datetime":"2026-04-05T02:14:36.174Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /dev/char/vmkdriver/ipmi0","datetime":"2026-04-05T02:14:36.174Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /dev/char/vmkdriver/ipmi1","datetime":"2026-04-05T02:14:36.174Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +{"message":"Partially resolved path: /dev/char/vmkdriver/ipmi2","datetime":"2026-04-05T02:14:36.174Z","pid":131678.0,"evidence":"/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log","category":"In(14)","process":"secpolicytools","timestamp_desc":"Syslog Entry Generated","artifact":"ESXi Syslog","data_type":"esxi:syslog:entry"} +``` + +Sample output for `accounts` + +```jsonl +{"message":"ESXi account 'root'","datetime":"2026-04-05T17:18:59.000Z","timestamp_desc":"Passwd File Modified","artifact":"ESXi User Account","data_type":"esxi:accounts:entry","evidence":"/etc/passwd","uid":0.0,"gid":0.0,"info":"Administrator","shell":"/bin/sh","home":"/"} +{"message":"ESXi account 'dcui'","datetime":"2026-04-05T17:18:59.000Z","timestamp_desc":"Passwd File Modified","artifact":"ESXi User Account","data_type":"esxi:accounts:entry","evidence":"/etc/passwd","uid":100.0,"gid":100.0,"info":"DCUI User","shell":"/bin/sh","home":"/"} +{"message":"ESXi account 'vpxuser'","datetime":"2026-04-05T17:18:59.000Z","timestamp_desc":"Passwd File Modified","artifact":"ESXi User Account","data_type":"esxi:accounts:entry","evidence":"/etc/passwd","uid":500.0,"gid":100.0,"info":"VMware VirtualCenter administration account","shell":"/bin/sh","home":"/"} +{"message":"ESXi account 'testUser'","datetime":"2026-04-05T17:18:59.000Z","timestamp_desc":"Passwd File Modified","artifact":"ESXi User Account","data_type":"esxi:accounts:entry","evidence":"/etc/passwd","uid":1000.0,"gid":1000.0,"info":"ESXi User","shell":"/bin/sh","home":"/"} +``` + +## Native Rust Artifacts + +Currently artemis can generate a filelisting and parse ELF binaries on an ESXi device. You do not need to leverage the artemis API for this artifact. Yara rules are also supported. + +To timeline a filelisting run the command below: + +``` +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] artemis acquire --timeline filelisting -h +Pull filelisting + +Usage: artemis acquire filelisting [OPTIONS] + +Options: + --md5 MD5 hash files + --sha1 SHA1 hash files + --sha256 SHA256 hash files + --metadata Parse executable binaries + --start-path Start path for listing [default: /] + --depth Depth for file listing. Max is 255 [default: 2] + --regex-filter Regex to only include entries that match + --yara-rule Base64 encoded Yara rule to only include entries that match + -h, --help Print help +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] artemis acquire --timeline filelisting --metadata --md5 --start-path / --depth 99 +[artemis] Starting artemis collection! +[artemis] Writing output to: ./tmp +[artemis] Finished artemis collection! +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] +``` + +Once the filelisting is complete you should see output similar to below: + +``` +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] cd tmp/local_collector/ +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/tmp/local_collector] ls -lh +total 198668 +-rw-r--r-- 1 root root 2.5K Apr 5 19:03 13f12932-7bca-41dc-98df-d1cd6a1f442a.log +-rw-r--r-- 1 root root 15.8M Apr 5 19:03 files_06f2c2fd-c9de-4871-91e5-f2ba9790b77e.jsonl +-rw-r--r-- 1 root root 2.8M Apr 5 19:03 files_248c83f8-aaea-422f-a439-937847f2b863.jsonl +-rw-r--r-- 1 root root 3.6M Apr 5 19:03 files_4bf1e03a-b90d-49b2-a2dc-ae3e84b8b544.jsonl +-rw-r--r-- 1 root root 4.5M Apr 5 18:42 files_4fc0b926-bfd9-456e-a08b-658890884fe4.jsonl +-rw-r--r-- 1 root root 2.5M Apr 5 18:42 files_5f579b3d-d01c-45f2-8fdc-7f060a3813e1.jsonl +-rw-r--r-- 1 root root 20.9M Apr 5 19:03 files_736c85a9-66e1-490c-8530-b1759c4d178c.jsonl +-rw-r--r-- 1 root root 30.1M Apr 5 19:03 files_75a6f3b1-7abb-4332-8c99-715e8b61ab51.jsonl +-rw-r--r-- 1 root root 86.1M Apr 5 19:03 files_b8c3100e-b179-4946-8e63-d478862f1059.jsonl +-rw-r--r-- 1 root root 10.7M Apr 5 19:03 files_c8717793-1205-4046-9615-f707f2e9a27e.jsonl +-rw-r--r-- 1 root root 204.9K Apr 5 19:03 files_d560a7e1-ef98-4c90-9cf2-d9498a595e28.jsonl +-rw-r--r-- 1 root root 11.3M Apr 5 19:03 files_f90894f9-69a9-4823-a048-832ecc225025.jsonl +-rw-r--r-- 1 root root 1.8K Apr 5 19:03 report_10bb4934-cdd2-426c-b22f-86750f838b57.json +-rw-r--r-- 1 root root 588 Apr 5 19:03 status_localhost.log +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/tmp/local_collector] +``` + +## Triage Files + +Artemis also supports acquiring files on ESXi systems via TOML collections. + +:::info + +[UAC](https://github.com/tclahr/uac) has a collection of YAML files that it uses for ESXi collections. + +::: + +For example if you want to acquire all `syslog` files from an ESXi system. You could use the TOML collection below: + +```toml +[output] +name = "acquire_syslogs" +directory = "./tmp" +format = "json" +compress = true +timeline = false +endpoint_id = "13ba1e33-4899-4843-adf1-c7e6b20d759a" +collection_id = 1 +output = "local" + +[[artifacts]] +artifact_name = "triage" +[[artifacts.triage]] +name = "Acquire syslog" +category = "Shell" +path = "/vmfs/volumes/*/log/" +file_mask = "syslog.*" +recursive = false +recreate_directories = true +``` + +Then upload the TOML file to the ESXi system and collect the data: + +``` +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] artemis -t triage.toml +[artemis] Starting artemis collection! +[artemis] Finished artemis collection! +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] ls tmp/ +acquire_syslogs.zip +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] +``` + +If you unzip `acquire_syslogs.zip` and its files you should see the following data: + +``` +~/Downloads/acquire_syslogs$ ls -lh +total 8.0K +-rw-r--r--. 1 dev dev 0 Jan 1 1980 69cd7d10-7316-4c84-8f2f-1fe734618b3e.log +drwxr-xr-x. 1 dev dev 54 Apr 5 15:14 files +-rw-r--r--. 1 dev dev 2.2K Apr 5 15:15 report_fe3f3f19-9e9d-41d8-9da4-438a6e4d2a33.json +-rw-r--r--. 1 dev dev 49 Jan 1 1980 status_localhost.log +``` + +The artemis report file contains: + +```json +{ + "boot_time": "1970-01-01T00:00:00.000Z", + "hostname": "localhost", + "os_version": "Unknown OS version", + "uptime": 9201, + "kernel_version": "8.0.3", + "platform": "Unknown system name", + "cpu": [], + "disks": [], + "memory": { + "available_memory": 0, + "free_memory": 0, + "free_swap": 0, + "total_memory": 0, + "total_swap": 0, + "used_memory": 0, + "used_swap": 0 + }, + "interfaces": [], + "performance": { + "avg_one_min": 0.0, + "avg_five_min": 0.0, + "avg_fifteen_min": 0.0 + }, + "version": "0.19.0", + "rust_version": "1.94.1", + "build_date": "2026-04-04", + "product_name": "", + "product_family": "", + "product_serial": "", + "product_uuid": "", + "product_version": "", + "vendor": "", + "collection_id": 1, + "endpoint_id": "13ba1e33-4899-4843-adf1-c7e6b20d759a", + "start_time": "2026-04-05T19:12:00.000Z", + "end_time": "2026-04-05T19:12:00.000Z", + "total_output_files": 16, + "artifacts": [ + "triage" + ], + "log_file": "./tmp/acquire_syslogs/69cd7d10-7316-4c84-8f2f-1fe734618b3e.log", + "artifact_runs": [ + { + "name": "triage", + "hash": "95e05cefc5fe7cfde47112839013d577", + "last_run": "2026-04-05T19:12:00.000Z", + "unixepoch": 1775416320, + "output_count": 16, + "output_files": [ + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.0.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.1.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.2.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.3.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.4.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.5.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.6.gz", + "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.0.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.1.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.2.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.3.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.4.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.5.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.6.gz", + "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log" + ], + "status": "completed" + } + ] +} +``` + +Since we enabled `recreate_directories` artemis ensured the full path to the syslog files was retained. + +The `files` directory contains: + +``` +~/Downloads/acquire_syslogs/files$ ls +acquisition_report.json vmfs +``` + +The acquisition_report file contains: + +```json +[ + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T02:14:36.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-05T02:14:36.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.0.gz", + "filename": "syslog.0.gz", + "md5": "1785852e22ead98dfa79f9ba4973d7a8", + "size": 94131 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:18:59.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-05T01:18:59.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.1.gz", + "filename": "syslog.1.gz", + "md5": "d761464b31538368cd019442035f34cc", + "size": 74430 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:18:44.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-05T01:18:44.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.2.gz", + "filename": "syslog.2.gz", + "md5": "1025f87250f59d408dbfbc4e9ffc7b41", + "size": 80009 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:10:17.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-05T01:10:17.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.3.gz", + "filename": "syslog.3.gz", + "md5": "a8dffbb530c92ade5695aaa1e8ed3b81", + "size": 88799 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:09:11.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-05T01:09:11.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.4.gz", + "filename": "syslog.4.gz", + "md5": "295e39f1b4394afb10b9aff0139167a9", + "size": 89023 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T00:01:01.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-05T00:01:01.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.5.gz", + "filename": "syslog.5.gz", + "md5": "625d1e808fbcae0c52bac5fd3e5fe603", + "size": 102866 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-04T00:16:07.000Z", + "accessed": "2026-04-05T19:03:28.000Z", + "changed": "2026-04-04T00:16:07.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.6.gz", + "filename": "syslog.6.gz", + "md5": "2405417fed1c681822e5063f9ac7ddc9", + "size": 96309 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T19:11:47.000Z", + "accessed": "2026-04-05T19:05:23.000Z", + "changed": "2026-04-05T19:11:34.000Z", + "full_path": "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log", + "filename": "syslog.log", + "md5": "18bf0cf04b1f89dc81e8a0a2d5ddc2b2", + "size": 876065 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T02:14:36.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-05T02:14:36.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.0.gz", + "filename": "syslog.0.gz", + "md5": "1785852e22ead98dfa79f9ba4973d7a8", + "size": 94131 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:18:59.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-05T01:18:59.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.1.gz", + "filename": "syslog.1.gz", + "md5": "d761464b31538368cd019442035f34cc", + "size": 74430 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:18:44.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-05T01:18:44.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.2.gz", + "filename": "syslog.2.gz", + "md5": "1025f87250f59d408dbfbc4e9ffc7b41", + "size": 80009 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:10:17.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-05T01:10:17.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.3.gz", + "filename": "syslog.3.gz", + "md5": "a8dffbb530c92ade5695aaa1e8ed3b81", + "size": 88799 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T01:09:11.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-05T01:09:11.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.4.gz", + "filename": "syslog.4.gz", + "md5": "295e39f1b4394afb10b9aff0139167a9", + "size": 89023 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T00:01:01.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-05T00:01:01.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.5.gz", + "filename": "syslog.5.gz", + "md5": "625d1e808fbcae0c52bac5fd3e5fe603", + "size": 102866 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-04T00:16:07.000Z", + "accessed": "2026-04-05T19:12:00.000Z", + "changed": "2026-04-04T00:16:07.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.6.gz", + "filename": "syslog.6.gz", + "md5": "2405417fed1c681822e5063f9ac7ddc9", + "size": 96309 + }, + { + "created": "1970-01-01T00:00:00.000Z", + "modified": "2026-04-05T19:11:47.000Z", + "accessed": "2026-04-05T19:05:23.000Z", + "changed": "2026-04-05T19:11:34.000Z", + "full_path": "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/log/syslog.log", + "filename": "syslog.log", + "md5": "18bf0cf04b1f89dc81e8a0a2d5ddc2b2", + "size": 876065 + } +] +``` + +The acquired files are below: + +``` +~/Downloads/acquire_syslogs/files$ tree vmfs/ +vmfs/ +└── volumes + ├── 69d0473d-ded27d57-be04-52540075d1a0 + │   └── log + │   ├── syslog.0.gz + │   ├── syslog.1.gz + │   ├── syslog.2.gz + │   ├── syslog.3.gz + │   ├── syslog.4.gz + │   ├── syslog.5.gz + │   ├── syslog.6.gz + │   └── syslog.log + └── OSDATA-69d0473d-ded27d57-be04-52540075d1a0 + └── log + ├── syslog.0.gz + ├── syslog.1.gz + ├── syslog.2.gz + ├── syslog.3.gz + ├── syslog.4.gz + ├── syslog.5.gz + ├── syslog.6.gz + └── syslog.log + +6 directories, 16 files +``` diff --git a/artemis-docs/docs/API/API Scenarios/itunes.md b/artemis-docs/docs/API/API Scenarios/itunes.md index 1d3d2be3..ea7d39f2 100644 --- a/artemis-docs/docs/API/API Scenarios/itunes.md +++ b/artemis-docs/docs/API/API Scenarios/itunes.md @@ -57,7 +57,7 @@ Do not forget to transpile the script above to JavaScript as mentioned in [API](../../Intro/Scripting/bundling.md) section! **Strongly** suggest you `minify` the transpiled script.\ -It will make it alot smaller! +It will make it a lot smaller! ::: diff --git a/artemis-docs/docs/API/Artifacts/applications.md b/artemis-docs/docs/API/Artifacts/applications.md index 9faa33d1..b8906458 100644 --- a/artemis-docs/docs/API/Artifacts/applications.md +++ b/artemis-docs/docs/API/Artifacts/applications.md @@ -1,606 +1,610 @@ ---- -description: Interact with Application Artifacts ---- - -# Applications - -These functions can be used to pull data related to common third-party software. - -You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). -Then you may import them into your TypeScript code. - -For example: -```typescript -import { vscodeRecentFiles, PlatformType } from "./artemis-api/mod"; - -function main() { - const results = vscodeRecentFiles(PlatformType.Linux); - return results; -} - -main(); -``` - -### Chromium Browser class - -A basic TypeScript class to extract data from the Chromium browser. You may optionally enable Unfold URL parsing (default is disabled) and provide an alternative glob to the base Chromium directory. - -Chrome and Edge parsers can also be created and are derived from the Chromium class. - -Sample TypeScript code: -```typescript -import { Chromium, Edge, PlatformType } from "./artemis-api/mod"; - -function main() { - const enable_unfold = true; - const client = new Chromium(PlatformType.Linux, enable_unfold); - const edge = new Edge(PlatformType.Windows); - - const start = 0; - const limit = 300; - const history_data = client.history(start, limit); - - const ext = client.extensions(); - const edge_history = edge.history(); - return ext; -} - -main(); -``` - -#### history(offset, limit) -> ChromiumHistory[] - -Return Chromium history for all users. Chromium history exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying history. -You may provide a starting offset and limit when querying history. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### downloads(offset, limit) -> ChromiumDownloads[] - -Return Chromium downloads for all users. Chromium downloads exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying downloads. -You may provide a starting offset and limit when querying downloads. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### cookies(offset, limit) -> ChromiumCookies[] - -Return Chromium cookies for all users. Chromium cookies exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying cookies. -You may provide a starting offset and limit when querying cookies. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### autofill(offset, limit) -> ChromiumAutofill[] - -Return Chromium autofill for all users. Chromium autofill exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying autofill. -You may provide a starting offset and limit when querying autofill. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### logins(offset, limit) -> ChromiumLogins[] - -Return Chromium logins for all users. Chromium logins exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying logins. -You may provide a starting offset and limit when querying logins. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### dips(offset, limit) -> ChromiumDips[] - -Return Chromium Detect Incidental Party State (DIPS) for all users. Chromium DIPS exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying DIPS. -You may provide a starting offset and limit when querying DIPS. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### extensions() -> Record<string, unknown>[] - -Return Chromium extensions for all users. - -#### preferences() -> Record<string, unknown>[] - -Return Chromium preferences for all users. - -#### bookmarks() -> ChromiumBookmarks[] - -Return Chromium bookmarks for all users. - -#### localStorage() -> ChromiumLocalStorage[] - -Return Chromium local storage data for all users. - -#### sessions() -> ChromiumSession[] - -Return Chromium sessions for all users. - -#### retrospect(output) - -A powerful function that will timeline all supported Chromium artifacts - -| Param | Type | Description | -| ---------------- | ------------- | ----------------------------------- | -| output | Output | Output object to output all results | - -Sample TypeScript code: -```typescript -import { Chrome, Edge, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; -function main() { - // Unfurls URLs. Based on [Unfurl](https://github.com/obsidianforensics/unfurl) - const enable_unfold = true; - const client = new Edge(PlatformType.Windows, enable_unfold); - const out: Output = { - name: "browsers", - directory: "./tmp", - format: Format.JSONL, - compress: false, - timeline: false, - endpoint_id: "", - collection_id: 0, - output: OutputType.LOCAL - }; - - // All artifacts will be parsed and timelined to JSONL - client.retrospect(out); - - const chrome_client = new Chrome(PlatformType.Windows, enable_unfold); - // All artifacts will be parsed and timelined to JSONL - chrome_client.retrospect(out); -} - -main(); -``` - -### FireFox Browser Class - -A basic TypeScript class to extract data from the FireFox browser. You may optionally enable Unfold URL parsing (default is disabled) and provide an alternative glob to the base FireFox directory. - -Sample TypeScript code: -```typescript -import { FireFox, PlatformType } from "./artemis-api/mod"; - -function main() { - const enable_unfold = true; - const fox = new FireFox(PlatformType.Linux, enable_unfold); - - const start = 0; - const limit = 300; - const history_data = fox.history(start, limit); - - const addons = fox.addons(); - return addons; -} - -main(); -``` - -#### history(offset, limit) -> FirefoxHistory[] - -Return FireFox history for all users. FireFox history exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying history. -You may provide a starting offset and limit when querying history. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### cookies(offset, limit) -> FirefoxCookies[] | ApplicationError - -Return FireFox cookies for all users. FireFox cookies exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying cookies. -You may provide a starting offset and limit when querying cookies. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### downloads(offset, limit) -> FirefoxDownloads[] | ApplicationError - -Return FireFox file downloads for all users. FireFox file downloads exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying file downloads. -You may provide a starting offset and limit when querying file downloads. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### storage(offset, limit) -> FirefoxStorage[] | ApplicationError - -Return FireFox storage entries for all users. FireFox storage entries exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying storage entries. -You may provide a starting offset and limit when querying storage entries. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### favicons(offset, limit) -> FirefoxFavicons[] | ApplicationError - -Return FireFox favicons for all users. FireFox favicons exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying favicons. -You may provide a starting offset and limit when querying favicons. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### formhistory(offset, limit) -> FirefoxFormhistory[] | ApplicationError - -Return FireFox form history for all users. FireFox form history exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying form history. -You may provide a starting offset and limit when querying form history. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### addons() -> Record<string, unknown>[] | ApplicationError - -Return FireFox addons for all users. FireFox addons exists as a JSON file. - -#### retrospect(output) - -A powerfull function that will timeline all supported FireFox artifacts - -| Param | Type | Description | -| ---------------- | ------------- | ----------------------------------- | -| output | Output | Output object to output all results | - -### Nextcloud Client Class - -A basic TypeScript class to extract data from the Nextcloud desktop client. You may optionally provide an alternative glob to the base Nextcloud client directory. - -Sample TypeScript code: -```typescript -import { NextcloudClient } from "./artemis-api/mod"; -import { PlatformType } from "./artemis-api/src/system/systeminfo"; - -function main() { - const client = new NextcloudClient(PlatformType.Linux); - - console.log(JSON.stringify(client.activityLogs())); -} - -main(); - -``` - -#### config() -> NextcloudClientConfig[] - -Returns an array of Nextcloud client configs for all users. - -#### syncLogs(max_size) -> NextcloudClientSyncLog[] - -Returns an array of sync log entries for the Nextcloud desktop client. -You may provide an optional max log size. By default artemis will read all sync logs less than 15MB. -Typically sync log(s) are ~3.6 MBs in size. - -| Param | Type | Description | -| --------- | ------ | ------------------------------ | -| max_size | number | Max sync log file size to read | - -#### activityLogs(max_size) -> NextcloudClientActivityLog[] - -Returns an array of activity log entries for the Nextcloud desktop client. -You may provide an optional max log size. By default artemis will read all activity logs less than 18MB. The Nextcloud client will compress logs with gzip once they reach ~15.6 MBs in size. Compressed log size is typically ~1.2MB. - -| Param | Type | Description | -| --------- | ------ | ---------------------------------- | -| max_size | number | Max activity log file size to read | - - -### recentFiles(platform) -> History[] | ApplicationError - -Return a list of files opened by LibreOffice for all users. - -| Param | Type | Description | -| -------- | ------------ | -------------------- | -| platform | PlatformType | OS platform to parse | - -### fileHistory(platform, include_content, alt_glob) -> FileHistory[] | ApplicationError - -Parse the local file history for VSCode. Returns list of history entries. Also -supports VSCodium. - -You may also provide an optional alternative glob path to the entries.json file. -By default artemis will parse the default locations for VSCode. - -An alternative glob will override the platform type. -You may also choose to include the content of the history files. By default this is not included. -If you include the file content the output will be very large. - -| Param | Type | Description | -| --------------- | ------------ | ------------------------------------------------------ | -| platform | PlatformType | OS platform to parse | -| include_content | boolean | Include content of the history files. Default is false | -| alt_glob | string | optional alternative glob path to entries.json | - -### getExtensions(platform, path) -> Extensions[] | ApplicationError - -Get installed VSCode or VSCodium extensions. Can also provide an optional -alternative path to the extensions.json file. Otherwise will use default paths. - -| Param | Type | Description | -| -------- | ------------ | ---------------------------------------- | -| platform | PlatformType | OS platform to parse | -| path | string | Optional path to an extensions.json file | - -### vscodeRecentFiles(platform, path) -> RecentFiles[] | ApplicationError - -Get recent files and folders opened by VScode. - -| Param | Type | Description | -| -------- | ------------ | --------------------------------------- | -| platform | PlatformType | OS platform to parse | -| path | string | Optional path to a storage.json file | - -### querySqlite(path, query) -> Record<string, unknown>[] | ApplicationError - -Execute a SQLITe query against a provided database file. Databases are opened in -read-only mode. In addition, this function will bypass locked SQLITE databases. - -| Param | Type | Description | -| ----- | ------ | -------------------------------------- | -| path | string | Path to the sqlite db | -| query | string | Query to execute against the sqlite db | - - -### extractDefenderRules(platform, alt_file, limit) -> DefinitionRule[] | ApplicationError - -An experimental function to attempt to extract Windows Defender Signatures. -Defender can contain thousands/millions? of signatures so this function can -potentially run for a long time. - -By default it will only extract 30 signatures. You can extract all signatures by -setting the limit to 0. - -By default it will attempt to extract all Defender signatures at: - -- %SYSTEMDRIVE%\\ProgramData\\Microsoft\\Windows Defender\\Definition - Updates\\\{\*\\\*.vdm -- /Library/Application Support/Microsoft/Defender/definitions.noindex/\*/\*.vdm - -You may also provide an optional alternative path to the vmd file - -| Param | Type | Description | -| -------- | ------------ | ------------------------------------------------------ | -| platform | PlatformType | OS platform to extract rules from | -| alt_dir | string | Alternative directory containing the UAL log databases | -| limit | number | Number of rules to return. Default is 30 | - -### officeMruFiles(platform, alt_file) -> OfficeRecentFilesMacos[] | OfficeRecentFilesWindows[] | ApplicationError - -Extract Microsoft Office MRU entries. Supports both macOS and Windows. By -default will parse MRU entries for all users.\ -You may also provide an optional alternative path to the MRU plist or NTUSER.DAT -file. - -| Param | Type | Description | -| -------- | ------------ | --------------------------------------------------------- | -| platform | PlatformType | OS platform to parse. Supports Windows and macOS (Darwin) | -| alt_file | string | Optional path to a MRU plist or NTUSER.DAT | - -### OneDrive Class - -A basic TypeScript class to extract OneDrive forensic artifacts. Supports both macOS and Windows. -By default artemis will parse OneDrive artifacts for **all** users. You may provide a single user as an optional argument to only parse data for a specific user. - -You may also provide an optional alternative path to a folder containing -OneDrive artifacts. You must include the trailing slash. The folder should -contain the following artifacts: - -- \*odl\* files -- NTUSER.DAT file or \*.OneDriveStandaloneSuite.plist -- general.keystore -- SyncEngineDatabase.db - -Sample TypeScript code: -```typescript -import { Format, OneDrive, Output, OutputType, PlatformType } from "./artemis-api/mod"; - -function main() { - const results = new OneDrive(PlatformType.Windows); - const output: Output = { - name: "local", - directory: "tmp", - format: Format.JSONL, - compress: false, - timeline: false, - endpoint_id: "", - collection_id: 0, - output: OutputType.LOCAL - }; - - results.retrospect(output); -} - -main(); -``` - -#### oneDriveProfiles() -> OnedriveProfile[] - -Returns an array of all file path atifacts associated with OneDrive users - -#### oneDriveKeys(files, output, metadata_runtime) -> KeyInfo[] - -Returns an array of keys used by OneDrive. By default this function will find the keys for all users. You may provide a specific subset of files or users instead of all users. - -You may also provide an optional Output object to output results to a file instead of returning an array of KeyInfo. - -| Param | Type | Description | -| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| files | string[] | Parse only key files from the provided array | -| output | Output | Output object to output all results to a file instead of return an array | -| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | - -#### oneDriveAccounts(files, output, metadata_runtime) -> OneDriveAccount[] - -Returns an array of OneDrive account information. By default this function will find the accounts for all users. You may provide a specific subset of files or users instead of all users. - -You may also provide an optional Output object to output results to a file instead of returning an array of OneDriveAccount. - -| Param | Type | Description | -| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| files | string[] | Parse only account files from the provided array | -| output | Output | Output object to output all results to a file instead of return an array | -| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | - -#### oneDriveLogs(files, output, metadata_runtime) -> OneDriveLog[] - -Returns an array of OneDrive log information. By default this function will find the logs for all users. You may provide a specific subset of files or users instead of all users. - -You may also provide an optional Output object to output results to a file instead of returning an array of OneDriveLog. - -| Param | Type | Description | -| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| files | string[] | Parse only log files from the provided array | -| output | Output | Output object to output all results to a file instead of return an array | -| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | - -#### oneDriveSyncDatabase(files, output, metadata_runtime) -> OneDriveSyncEngineRecord[] - -Returns an array of OneDrive Sync database information. By default this function will find the databases for all users. You may provide a specific subset of files or users instead of all users. - -You may also provide an optional Output object to output results to a file instead of returning an array of OneDriveSyncEngineRecord. - -| Param | Type | Description | -| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | -| files | string[] | Parse only database files from the provided array | -| output | Output | Output object to output all results to a file instead of return an array | -| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | - -#### retrospect(output) - -A powerful function that will timeline all supported OneDrive artifacts - -| Param | Type | Description | -| ---------------- | ------------- | ----------------------------------- | -| output | Output | Output object to output all results | - -### LevelDb Class - -A basic TypeScript class to extract data from LevelDb files. - -Sample TypeScript code: -```typescript -import { LevelDb, PlatformType, } from "./artemis-api/mod"; - -function main() { - const info = new LevelDb("Path to leveldb directory", PlatformType.Linux); - console.log(JSON.stringify(info.tables())); -} - -main(); -``` - -#### current() -> string - -Returns the active manifest file for the level database - -#### manifest() -> LevelManifest[] | ApplicationError - -Parse the level database manifest - -#### wal() -> LevelDbEntry[] | ApplicationError - -Parse the level database write ahead log - -#### tables() -> LevelDbEntry[] | ApplicationError - -Parse the level database write ahead log - -### AnyDesk Class - -A basic TypeScript class to extract data from AnyDesk application. By default artemis will parse the default AnyDesk paths based on the provided PlatformType. -You may provide an optional alternative directory that contains the AnyDesk files. The alternative directory should contain all AnyDesk related files. - -Sample TypeScript code: -```typescript -import { AnyDesk, PlatformType } from "../artemis-api/mod"; - -function main() { - console.log('Running AnyDesk tests....'); - const results = new AnyDesk(PlatformType.Linux); - const hits = results.traceFiles(); - console.log(JSON.stringify(hits[0])); -} - -main(); -``` - -#### traceFiles(is_alt) -> TraceEntry[] - -Log entries from trace log files. - -| Param | Type | Description | -| -------- | ------------ | --------------------------------------------------------------------------------------------------- | -| is_alt | boolean | Optional. Set to true if you provided an alternative directory when initializing the AnyDesk class | - -#### configs(is_alt) -> Config[] - -AnyDesk config information. - -| Param | Type | Description | -| -------- | ------------ | --------------------------------------------------------------------------------------------------- | -| is_alt | boolean | Optional. Set to true if you provided an alternative directory when initializing the AnyDesk class | - -### Syncthing Class - -A basic TypeScript class to extract data from Syncthing logs. - -Sample TypeScript code: -```typescript -import { PlatformType, Syncthing } from "./artemis-api/mod"; - - -function main() { - const client = new Syncthing(PlatformType.Linux); - const results = client.logs(); - console.log(JSON.stringify(results)); -} - -main(); -``` - - -#### logs() -> SyncthingLogs[] - -Return all plaintext log entries for the Syncthing application +--- +description: Interact with Application Artifacts +--- + +# Applications + +These functions can be used to pull data related to common third-party software. + +You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). +Then you may import them into your TypeScript code. + +For example: +```typescript +import { vscodeRecentFiles, PlatformType } from "./artemis-api/mod"; + +function main() { + const results = vscodeRecentFiles(PlatformType.Linux); + return results; +} + +main(); +``` + +### Chromium Browser class + +A basic TypeScript class to extract data from the Chromium browser. You may optionally enable Unfold URL parsing (default is disabled) and provide an alternative glob to the base Chromium directory. + +Chrome and Edge parsers can also be created and are derived from the Chromium class. + +Sample TypeScript code: +```typescript +import { Chromium, Edge, PlatformType } from "./artemis-api/mod"; + +function main() { + const enable_unfold = true; + const client = new Chromium(PlatformType.Linux, enable_unfold); + const edge = new Edge(PlatformType.Windows); + + const start = 0; + const limit = 300; + const history_data = client.history(start, limit); + + const ext = client.extensions(); + const edge_history = edge.history(); + return ext; +} + +main(); +``` + +#### history(offset, limit) -> ChromiumHistory[] + +Return Chromium history for all users. Chromium history exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying history. +You may provide a starting offset and limit when querying history. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### downloads(offset, limit) -> ChromiumDownloads[] + +Return Chromium downloads for all users. Chromium downloads exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying downloads. +You may provide a starting offset and limit when querying downloads. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### cookies(offset, limit) -> ChromiumCookies[] + +Return Chromium cookies for all users. Chromium cookies exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying cookies. +You may provide a starting offset and limit when querying cookies. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### autofill(offset, limit) -> ChromiumAutofill[] + +Return Chromium autofill for all users. Chromium autofill exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying autofill. +You may provide a starting offset and limit when querying autofill. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### logins(offset, limit) -> ChromiumLogins[] + +Return Chromium logins for all users. Chromium logins exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying logins. +You may provide a starting offset and limit when querying logins. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### dips(offset, limit) -> ChromiumDips[] + +Return Chromium Detect Incidental Party State (DIPS) for all users. Chromium DIPS exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying DIPS. +You may provide a starting offset and limit when querying DIPS. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### extensions() -> Record<string, unknown>[] + +Return Chromium extensions for all users. + +#### preferences() -> Record<string, unknown>[] + +Return Chromium preferences for all users. + +#### bookmarks() -> ChromiumBookmarks[] + +Return Chromium bookmarks for all users. + +#### localStorage() -> ChromiumLocalStorage[] + +Return Chromium local storage data for all users. + +#### sessions() -> ChromiumSession[] + +Return Chromium sessions for all users. + +#### cache() -> ChromiumCache[] + +Return Chromium cache for all users. + +#### retrospect(output) + +A powerful function that will timeline all supported Chromium artifacts + +| Param | Type | Description | +| ---------------- | ------------- | ----------------------------------- | +| output | Output | Output object to output all results | + +Sample TypeScript code: +```typescript +import { Chrome, Edge, Format, Output, OutputType, PlatformType } from "./artemis-api/mod"; +function main() { + // Unfurls URLs. Based on [Unfurl](https://github.com/obsidianforensics/unfurl) + const enable_unfold = true; + const client = new Edge(PlatformType.Windows, enable_unfold); + const out: Output = { + name: "browsers", + directory: "./tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + output: OutputType.LOCAL + }; + + // All artifacts will be parsed and timelined to JSONL + client.retrospect(out); + + const chrome_client = new Chrome(PlatformType.Windows, enable_unfold); + // All artifacts will be parsed and timelined to JSONL + chrome_client.retrospect(out); +} + +main(); +``` + +### FireFox Browser Class + +A basic TypeScript class to extract data from the FireFox browser. You may optionally enable Unfold URL parsing (default is disabled) and provide an alternative glob to the base FireFox directory. + +Sample TypeScript code: +```typescript +import { FireFox, PlatformType } from "./artemis-api/mod"; + +function main() { + const enable_unfold = true; + const fox = new FireFox(PlatformType.Linux, enable_unfold); + + const start = 0; + const limit = 300; + const history_data = fox.history(start, limit); + + const addons = fox.addons(); + return addons; +} + +main(); +``` + +#### history(offset, limit) -> FirefoxHistory[] + +Return FireFox history for all users. FireFox history exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying history. +You may provide a starting offset and limit when querying history. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### cookies(offset, limit) -> FirefoxCookies[] | ApplicationError + +Return FireFox cookies for all users. FireFox cookies exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying cookies. +You may provide a starting offset and limit when querying cookies. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### downloads(offset, limit) -> FirefoxDownloads[] | ApplicationError + +Return FireFox file downloads for all users. FireFox file downloads exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying file downloads. +You may provide a starting offset and limit when querying file downloads. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### storage(offset, limit) -> FirefoxStorage[] | ApplicationError + +Return FireFox storage entries for all users. FireFox storage entries exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying storage entries. +You may provide a starting offset and limit when querying storage entries. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### favicons(offset, limit) -> FirefoxFavicons[] | ApplicationError + +Return FireFox favicons for all users. FireFox favicons exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying favicons. +You may provide a starting offset and limit when querying favicons. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### formhistory(offset, limit) -> FirefoxFormhistory[] | ApplicationError + +Return FireFox form history for all users. FireFox form history exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying form history. +You may provide a starting offset and limit when querying form history. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### addons() -> Record<string, unknown>[] | ApplicationError + +Return FireFox addons for all users. FireFox addons exists as a JSON file. + +#### retrospect(output) + +A powerfull function that will timeline all supported FireFox artifacts + +| Param | Type | Description | +| ---------------- | ------------- | ----------------------------------- | +| output | Output | Output object to output all results | + +### Nextcloud Client Class + +A basic TypeScript class to extract data from the Nextcloud desktop client. You may optionally provide an alternative glob to the base Nextcloud client directory. + +Sample TypeScript code: +```typescript +import { NextcloudClient } from "./artemis-api/mod"; +import { PlatformType } from "./artemis-api/src/system/systeminfo"; + +function main() { + const client = new NextcloudClient(PlatformType.Linux); + + console.log(JSON.stringify(client.activityLogs())); +} + +main(); + +``` + +#### config() -> NextcloudClientConfig[] + +Returns an array of Nextcloud client configs for all users. + +#### syncLogs(max_size) -> NextcloudClientSyncLog[] + +Returns an array of sync log entries for the Nextcloud desktop client. +You may provide an optional max log size. By default artemis will read all sync logs less than 15MB. +Typically sync log(s) are ~3.6 MBs in size. + +| Param | Type | Description | +| --------- | ------ | ------------------------------ | +| max_size | number | Max sync log file size to read | + +#### activityLogs(max_size) -> NextcloudClientActivityLog[] + +Returns an array of activity log entries for the Nextcloud desktop client. +You may provide an optional max log size. By default artemis will read all activity logs less than 18MB. The Nextcloud client will compress logs with gzip once they reach ~15.6 MBs in size. Compressed log size is typically ~1.2MB. + +| Param | Type | Description | +| --------- | ------ | ---------------------------------- | +| max_size | number | Max activity log file size to read | + + +### recentFiles(platform) -> History[] | ApplicationError + +Return a list of files opened by LibreOffice for all users. + +| Param | Type | Description | +| -------- | ------------ | -------------------- | +| platform | PlatformType | OS platform to parse | + +### fileHistory(platform, include_content, alt_glob) -> FileHistory[] | ApplicationError + +Parse the local file history for VSCode. Returns list of history entries. Also +supports VSCodium. + +You may also provide an optional alternative glob path to the entries.json file. +By default artemis will parse the default locations for VSCode. + +An alternative glob will override the platform type. +You may also choose to include the content of the history files. By default this is not included. +If you include the file content the output will be very large. + +| Param | Type | Description | +| --------------- | ------------ | ------------------------------------------------------ | +| platform | PlatformType | OS platform to parse | +| include_content | boolean | Include content of the history files. Default is false | +| alt_glob | string | optional alternative glob path to entries.json | + +### getExtensions(platform, path) -> Extensions[] | ApplicationError + +Get installed VSCode or VSCodium extensions. Can also provide an optional +alternative path to the extensions.json file. Otherwise will use default paths. + +| Param | Type | Description | +| -------- | ------------ | ---------------------------------------- | +| platform | PlatformType | OS platform to parse | +| path | string | Optional path to an extensions.json file | + +### vscodeRecentFiles(platform, path) -> RecentFiles[] | ApplicationError + +Get recent files and folders opened by VScode. + +| Param | Type | Description | +| -------- | ------------ | --------------------------------------- | +| platform | PlatformType | OS platform to parse | +| path | string | Optional path to a storage.json file | + +### querySqlite(path, query) -> Record<string, unknown>[] | ApplicationError + +Execute a SQLITe query against a provided database file. Databases are opened in +read-only mode. In addition, this function will bypass locked SQLITE databases. + +| Param | Type | Description | +| ----- | ------ | -------------------------------------- | +| path | string | Path to the sqlite db | +| query | string | Query to execute against the sqlite db | + + +### extractDefenderRules(platform, alt_file, limit) -> DefinitionRule[] | ApplicationError + +An experimental function to attempt to extract Windows Defender Signatures. +Defender can contain thousands/millions? of signatures so this function can +potentially run for a long time. + +By default it will only extract 30 signatures. You can extract all signatures by +setting the limit to 0. + +By default it will attempt to extract all Defender signatures at: + +- %SYSTEMDRIVE%\\ProgramData\\Microsoft\\Windows Defender\\Definition + Updates\\\{\*\\\*.vdm +- /Library/Application Support/Microsoft/Defender/definitions.noindex/\*/\*.vdm + +You may also provide an optional alternative path to the vmd file + +| Param | Type | Description | +| -------- | ------------ | ------------------------------------------------------ | +| platform | PlatformType | OS platform to extract rules from | +| alt_dir | string | Alternative directory containing the UAL log databases | +| limit | number | Number of rules to return. Default is 30 | + +### officeMruFiles(platform, alt_file) -> OfficeRecentFilesMacos[] | OfficeRecentFilesWindows[] | ApplicationError + +Extract Microsoft Office MRU entries. Supports both macOS and Windows. By +default will parse MRU entries for all users.\ +You may also provide an optional alternative path to the MRU plist or NTUSER.DAT +file. + +| Param | Type | Description | +| -------- | ------------ | --------------------------------------------------------- | +| platform | PlatformType | OS platform to parse. Supports Windows and macOS (Darwin) | +| alt_file | string | Optional path to a MRU plist or NTUSER.DAT | + +### OneDrive Class + +A basic TypeScript class to extract OneDrive forensic artifacts. Supports both macOS and Windows. +By default artemis will parse OneDrive artifacts for **all** users. You may provide a single user as an optional argument to only parse data for a specific user. + +You may also provide an optional alternative path to a folder containing +OneDrive artifacts. You must include the trailing slash. The folder should +contain the following artifacts: + +- \*odl\* files +- NTUSER.DAT file or \*.OneDriveStandaloneSuite.plist +- general.keystore +- SyncEngineDatabase.db + +Sample TypeScript code: +```typescript +import { Format, OneDrive, Output, OutputType, PlatformType } from "./artemis-api/mod"; + +function main() { + const results = new OneDrive(PlatformType.Windows); + const output: Output = { + name: "local", + directory: "tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + output: OutputType.LOCAL + }; + + results.retrospect(output); +} + +main(); +``` + +#### oneDriveProfiles() -> OnedriveProfile[] + +Returns an array of all file path atifacts associated with OneDrive users + +#### oneDriveKeys(files, output, metadata_runtime) -> KeyInfo[] + +Returns an array of keys used by OneDrive. By default this function will find the keys for all users. You may provide a specific subset of files or users instead of all users. + +You may also provide an optional Output object to output results to a file instead of returning an array of KeyInfo. + +| Param | Type | Description | +| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| files | string[] | Parse only key files from the provided array | +| output | Output | Output object to output all results to a file instead of return an array | +| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | + +#### oneDriveAccounts(files, output, metadata_runtime) -> OneDriveAccount[] + +Returns an array of OneDrive account information. By default this function will find the accounts for all users. You may provide a specific subset of files or users instead of all users. + +You may also provide an optional Output object to output results to a file instead of returning an array of OneDriveAccount. + +| Param | Type | Description | +| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| files | string[] | Parse only account files from the provided array | +| output | Output | Output object to output all results to a file instead of return an array | +| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | + +#### oneDriveLogs(files, output, metadata_runtime) -> OneDriveLog[] + +Returns an array of OneDrive log information. By default this function will find the logs for all users. You may provide a specific subset of files or users instead of all users. + +You may also provide an optional Output object to output results to a file instead of returning an array of OneDriveLog. + +| Param | Type | Description | +| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| files | string[] | Parse only log files from the provided array | +| output | Output | Output object to output all results to a file instead of return an array | +| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | + +#### oneDriveSyncDatabase(files, output, metadata_runtime) -> OneDriveSyncEngineRecord[] + +Returns an array of OneDrive Sync database information. By default this function will find the databases for all users. You may provide a specific subset of files or users instead of all users. + +You may also provide an optional Output object to output results to a file instead of returning an array of OneDriveSyncEngineRecord. + +| Param | Type | Description | +| ---------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| files | string[] | Parse only database files from the provided array | +| output | Output | Output object to output all results to a file instead of return an array | +| metadata_runtime | boolean | Specify if artemis should append runtime metadata when outputting to a file based on the Output object. Default is no metadata will be appended | + +#### retrospect(output) + +A powerful function that will timeline all supported OneDrive artifacts + +| Param | Type | Description | +| ---------------- | ------------- | ----------------------------------- | +| output | Output | Output object to output all results | + +### LevelDb Class + +A basic TypeScript class to extract data from LevelDb files. + +Sample TypeScript code: +```typescript +import { LevelDb, PlatformType, } from "./artemis-api/mod"; + +function main() { + const info = new LevelDb("Path to leveldb directory", PlatformType.Linux); + console.log(JSON.stringify(info.tables())); +} + +main(); +``` + +#### current() -> string + +Returns the active manifest file for the level database + +#### manifest() -> LevelManifest[] | ApplicationError + +Parse the level database manifest + +#### wal() -> LevelDbEntry[] | ApplicationError + +Parse the level database write ahead log + +#### tables() -> LevelDbEntry[] | ApplicationError + +Parse the level database write ahead log + +### AnyDesk Class + +A basic TypeScript class to extract data from AnyDesk application. By default artemis will parse the default AnyDesk paths based on the provided PlatformType. +You may provide an optional alternative directory that contains the AnyDesk files. The alternative directory should contain all AnyDesk related files. + +Sample TypeScript code: +```typescript +import { AnyDesk, PlatformType } from "../artemis-api/mod"; + +function main() { + console.log('Running AnyDesk tests....'); + const results = new AnyDesk(PlatformType.Linux); + const hits = results.traceFiles(); + console.log(JSON.stringify(hits[0])); +} + +main(); +``` + +#### traceFiles(is_alt) -> TraceEntry[] + +Log entries from trace log files. + +| Param | Type | Description | +| -------- | ------------ | --------------------------------------------------------------------------------------------------- | +| is_alt | boolean | Optional. Set to true if you provided an alternative directory when initializing the AnyDesk class | + +#### configs(is_alt) -> Config[] + +AnyDesk config information. + +| Param | Type | Description | +| -------- | ------------ | --------------------------------------------------------------------------------------------------- | +| is_alt | boolean | Optional. Set to true if you provided an alternative directory when initializing the AnyDesk class | + +### Syncthing Class + +A basic TypeScript class to extract data from Syncthing logs. + +Sample TypeScript code: +```typescript +import { PlatformType, Syncthing } from "./artemis-api/mod"; + + +function main() { + const client = new Syncthing(PlatformType.Linux); + const results = client.logs(); + console.log(JSON.stringify(results)); +} + +main(); +``` + + +#### logs() -> SyncthingLogs[] + +Return all plaintext log entries for the Syncthing application diff --git a/artemis-docs/docs/API/Artifacts/esxi.md b/artemis-docs/docs/API/Artifacts/esxi.md new file mode 100644 index 00000000..e203b7a6 --- /dev/null +++ b/artemis-docs/docs/API/Artifacts/esxi.md @@ -0,0 +1,67 @@ +--- +description: Interact with ESXi Artifacts +--- + +# ESXi + +These functions can be used to pull data related to ESXi artifacts. + +You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). +Then you may import them into your TypeScript code. + +For example: +```typescript +import { getVibs } from "./artemis-api/mod"; + +function main() { + + const results = getVibs(); + console.log(JSON.stringify(results)); + return results; +} + +main(); +``` + +### getVibs(alt_path?: string) -> Vib[] | EsxiError + +Get info of installed VIBs on the system. You may provide an optional alternative +glob to the directory containing VIB xml files. + + +| Param | Type | Description | +| -------- | ------ | ------------------------------- | +| alt_path | string | Optional glob to VIB xml files | + + +### sysLogEsxi(alt_path?: string) -> Syslog[] | EsxiError + +Get ESXi syslog entries. You may provide an optional alternative +glob to the syslog.log file. + + +| Param | Type | Description | +| -------- | ------ | --------------------------------- | +| alt_path | string | Optional glob to syslog.log file | + + +### shellLogHistory(alt_path?: string) -> ShellHistory[] | EsxiError + +Get ESXi shell.log entries. You may provide an optional alternative +glob to the shell.log file. + + +| Param | Type | Description | +| -------- | ------ | -------------------------------- | +| alt_path | string | Optional glob to shell.log file | + + +### esxiAccounts(alt_path?: string) -> Users[] | EsxiError + +Get ESXi user accounts. You may provide an optional alternative +path to the file /etc/passwd. + + +| Param | Type | Description | +| -------- | ------ | -----------------------=---------- | +| alt_path | string | Optional path to /etc/passwd file | \ No newline at end of file diff --git a/artemis-docs/docs/API/Artifacts/freebsd.md b/artemis-docs/docs/API/Artifacts/freebsd.md index 75b69774..d4f00e61 100644 --- a/artemis-docs/docs/API/Artifacts/freebsd.md +++ b/artemis-docs/docs/API/Artifacts/freebsd.md @@ -24,7 +24,7 @@ function main() { main(); ``` -### getPkgs(offset, limit, path) -> Pkg[] | LinuxError +### getPkgs(offset, limit, path) -> Pkg[] | FreebsdError Get list of installed pkgs on the system. You may provide an optional alternative full path to the local.sqlite file. diff --git a/artemis-docs/docs/API/Artifacts/macos.md b/artemis-docs/docs/API/Artifacts/macos.md index b562be3a..d50df76a 100644 --- a/artemis-docs/docs/API/Artifacts/macos.md +++ b/artemis-docs/docs/API/Artifacts/macos.md @@ -1,914 +1,922 @@ ---- -description: Interact with macOS Artifacts ---- - -# macOS - -These functions can be used to pull data related to macOS artifacts. - -You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). -Then you may import them into your TypeScript code. - -For example: -```typescript -import { listApps } from "./artemis-api/mod"; - -function main() { - const results = listApps(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -### getUsers(path) -> Users[] | MacosError - -Return all local users on macOS system. Can provide an optional alternative path -to directory containing users. Otherwise will use default path on system -/var/db/dslocal/nodes/Default/users - -| Param | Type | Description | -| ----- | ------ | -------------------------------------------- | -| path | string | Optional alternative path to users directory | - -### getGroup(path) -> Groups[] | MacosError - -Return all local groups on macOS system. Can provide an optional alternative path -to directory containing groups. Otherwise will use default path on system -/var/db/dslocal/nodes/Default/groups - -| Param | Type | Description | -| ----- | ------ | --------------------------------------------- | -| path | string | Optional alternative path to groups directory | - -### parseAlias(data) -> Alias | MacosError - -Parse macOS [alias](https://en.wikipedia.org/wiki/Alias_(Mac_OS)) data. Alias -files are a legacy shortcut format. May be encountered in plist files such as -the firewall plist file. - -| Param | Type | Description | -| ----- | ---------- | --------------- | -| data | Uint8Array | Raw alias bytes | - -### getEmond(path) -> Emond[] | MacosError - -Get all [Emond](../../Artifacts/macOS%20Artifacts/emond.md) rules on macOS. FYI -Emond was removed on Ventura. Can provide an optional alternative path to -directory containing emond rules. Otherwise will parse emond config on system to -try to find rules - -| Param | Type | Description | -| ----- | ------ | ---------------------------------------- | -| path | String | Optional alternative path to emond rules | - -### getExecpolicy(path) -> ExecPolicy[] | MacosError - -Parse the ExecPolicy sqlite database on macOS. Can provide an optional -alternative path to ExecPolicy database. Otherwise will parse default database -on system at /var/db/SystemPolicyConfiguration/ExecPolicy - -| Param | Type | Description | -| ----- | ------ | ------------------------------------------------ | -| path | String | Optional alternative path to ExecPolicy database | - -### firewallStatus(alt_path) -> Firewall | MacosError - -Return firewall information and status on macOS. Can provide an optional path to -com.apple.alf.plist, otherwise will use /Library/Preferences/com.apple.alf.plist - -| Param | Type | Description | -| -------- | ------ | ----------------------------------------------------- | -| alt_path | String | Alternative full path to the com.apple.alf.plist file | - -### getFsevents(path) -> Fsevents[] | MacosError - -Parse macOS FsEvents from provided file. - -| Param | Type | Description | -| ----- | ------ | ------------------------------ | -| path | String | Full path to the FsEvents file | - -### getLaunchdDaemons() -> Launchd[] | MacosError - -Return all Launch daemons on macOS - -### getLaunchdAgents() -> Launchd[] | MacosError - -Return all Launch agents on macOS - -### getLoginitems(path) -> LoginItems[] | MacosError - -Return all LoginItems on macOS. Can provide an optional alternative path to a -LoginItem file (.btm). Otherwise will parse default default locations for -LoginItems - -| Param | Type | Description | -| ----- | ------ | ------------------------------------------- | -| path | String | Optional alternative path to LoginItem file | - -### getMacho(path) -> MachoInfo[] | MacosError - -Parse a macho file and return metadata about the binary. - -| Param | Type | Description | -| ----- | ------ | -------------------- | -| path | string | Path to macho binary | - -### getPlist(path or Uint8Array) -> Record<string, unknown> | Uint8Array | Record<string, unknown>[] | MacosError - -Parse a plist file. Supports parsing a provide plist file path or the raw bytes -of plist data. Sometimes a plist file may contain another base64 encoded plist. -This function can parse the raw plist bytes. - -| Param | Type | Description | -| ------------------ | -------------------- | ------------------------------------- | -| path or Uint8Array | string or Uint8Array | Path to plist file or raw plist bytes | - -### passwordPolicy(alt_path) -> PasswordPolicy[] | MacosError - -Get password policies on macOS. Will parse plist file at -/var/db/dslocal/nodes/Default/config/shadowhash.plist. You may also provide an -optional alternative path to the shadowhash.plist file. - -| Param | Type | Description | -| -------- | ------ | -------------------------------------------------- | -| alt_path | String | Optional alternative path to shadowhash.plist file | - -### getUnifiedLog(path, archive_path) -> UnifiedLog[] | MacosError - -Parse a single UnifiedLog file (.tracev3) on macOS. Typically found at: - -- /private/var/db/diagnostics/Persist -- /private/var/db/diagnostics/Signpost -- /private/var/db/diagnostics/HighVolume -- /private/var/db/diagnostics/Special - -You may also specify an optional logarchive style directory containing the -Unified Log metadata (UUID directories, timesync, and dsc directory). Otherwise -artemis will parse their default locations. - -| Param | Type | Description | -| ------------ | ------ | ----------------------------------------------------------------------------- | -| path | string | Path to .tracev3 file | -| archive_path | string | Optional path to a logarchive style directory containing Unified Log metadata | - -### parseRequirementBlob(data) -> SingleRequirement | MacosError - -Parse the Requirement Blob from raw codesigning bytes. This part of Apple's -CodeSigning framework. This data can be found in macho binaries and also plist -files. - -| Param | Type | Description | -| ----- | ---------- | ------------------------------------------ | -| data | Uint8Array | Raw bytes associated with requirement blob | - -### listApps() -> Applications[] | MacosError - -Return a simple Application listing. Searches user installed Apps, System Apps, -default Homebrew paths: - -- /usr/local/Cellar -- /opt/homebrew/Cellar - -Use scanApps() if you want to scan the entire filesystem for Apps - -### scanApps() -> Applications[] | MacosError - -Scans the entire filesystem under /System/ and tries to parse all Applications. - -Includes embedded Apps, Frameworks, and any file that ends with -%/Contents/Info.plist - -Use listApps() if you a simpler Application listing - -### dockTiles() -> Applications[] | MacosError - -Scans the entire filesystem under /System looking for Applications that use -DockTile persistence. See https://theevilbit.github.io/beyond/beyond_0032/ for -details on Dock Tile PlugIns - -Includes embedded Apps, Frameworks, and any file that ends with -%/Contents/Info.plist - -### getPackages(glob_path) -> HomebrewReceipt[] - -Get Homebrew packages on the system. Does **not** include Casks.\ -Use getHomebrewInfo() to get all packages and Casks. - -By default this function will search for all packages at: - -- /opt/homebrew/Cellar -- /usr/local/Cellar - -| Param | Type | Description | -| --------- | ------ | ------------------------------------- | -| glob_path | string | Optional alternative glob path to use | - -### getCasks(glob_path) -> HomebrewFormula[] - -Get Homebrew Casks on the system. Does **not** include packages.\ -Use getHomebrewInfo() to get all packages and Casks. - -By default this function will search for all packages at: - -- /opt/homebrew/Caskroom -- /usr/local/Caskroom - -| Param | Type | Description | -| --------- | ------ | ------------------------------------- | -| glob_path | string | Optional alternative glob path to use | - -### getHomebrewInfo() -> HomebrewData - -Get Homebrew packages and Casks on the system. Searches for Homebrew data at: - -- /opt/homebrew -- /usr/local - -### wifiNetworks() -> Wifi[] - -Get list of joined Wifi networks on macOS. Requires root access. - -By default it will try to parse WiFi networks at -/Library/Preferences/com.apple.wifi.known-networks.plist. - -You may also provide an optional alternative path to -com.apple.wifi.known-networks.plist. - -| Param | Type | Description | -| -------- | ------ | --------------------------------------------------------------------- | -| alt_path | String | Optional alternative path to com.apple.wifi.known-networks.plist file | - -### getSudoLogs() -> UnifiedLog[] - -Parse the UnifiedLogs and extract entries related to sudo activity. - -### parseBom(path) -> Bom - -Parse Bill of Materials (BOM) files. BOM files are created whenever the macOS -Installer is used to install an application.\ -BOM files track what files were created by the Installer. It is commonly used to -ensure files are removed when the application is uninstalled. This function will -also try to parse the plist receipt associated with the BOM file (if found in -same directory). - -BOM files are located at /var/db/receipts/*.bom - -| Param | Type | Description | -| ----- | ------ | ---------------- | -| path | string | Path to BOM file | - -### systemExtensions(alt_path) -> SystemExtension[] - -Get list of macOS System Extensions. By default artemis will try to extract -installed extensions at /Library/SystemExtensions/db.plist. - -However, you may also provide an optional alternative path to db.plist. - -| Param | Type | Description | -| -------- | ------ | ------------------------------------------ | -| alt_path | String | Optional alternative path to db.plist file | - -### queryTccDb(alt_db) -> TccValues[] | MacosError - -Query all TCC.db files on the system. TCC.db contains granted permissions for -applications.\ -An optional path to the TCC.db can be provided. Otherwise will parse all user -and System TCC.db files. - -| Param | Type | Description | -| ------ | ------ | ---------------------------- | -| alt_db | string | Optional path to TCC.db file | - -### Parsing macOS Spotlight -#### setupSpotlightParser(glob_path) -> StoreMeta | MacosError - -Collect and setup the required data needed to parse the macOS Spotlight -database.\ -This function must be called before a user can parse the Spotlight database -using the API. - -The glob_path should point to the directory containing the Spotlight database -files.\ -The primary Spotlight database can be found at: -/System/Volumes/Data/.Spotlight-V100/Store-V\*/\*/\*\ -Would return something like: -/System/Volumes/Data/.Spotlight-V100/Store-V3/123-445566-778-12384/* - -| Param | Type | Description | -| --------- | ------ | ---------------------------------------------------------------- | -| glob_path | string | Glob path to a directory containing the Spotlight Database files | - -#### getSpotlight(meta, store_file, offset) -> StoreMeta | MacosError - -Parse the macOS Spotlight database. The database can potentially return a large -amount of data (5+GBs).\ -To prevent excessive memory usage, this function will parse the database in -blocks (chunks). - -It will parse **10** blocks at a time before returning the results. The -`StoreMeta` value obtained from setupSpotlightParser, contains the **TOTAL** -amount of blocks in the Spotlight database! You must loop through the blocks and -track what block offset the parser should start at! - -If you want to the parser to start at the beginning of the Spotlight database, -provide an offset of zero (0). Once the parser returns data, your next offset -will now be ten (10) because it parsed **10** blocks starting at zero (0-9). - -:::info - -Spotlight organizes data in "blocks". Think of blocks as containers that contain 1 or more Spotlight entries. -Parsing 10 blocks does **not** mean you are only getting 10 entries. - -Each block will typically contain ~50 to ~300 entries! -By getting 10 blocks you will be getting between ~500 to ~3000 entries! - -::: - -Finally, you must provide the full path to the Spotlight database file -(store.db). This is typically found in in the directory provided to -`setupSpotlightParser`\ -(ex: -/System/Volumes/Data/.Spotlight-V100/Store-V3/123-445566-778-12384/store.db) - -In order to parse Spotlight data you must have **root** permissions - -| Param | Type | Description | -| ---------- | --------- | ----------------------------------------------------- | -| meta | StoreMeta | Spotlight metadata obtained from setupSpotlightParser | -| store_file | string | Full path to the store.db file | -| offset | number | Offset to start parsing the Spotlight database | - -An example script is below that will show all Spotlight entries that contain the string "Downloads" or ".pkg". -```typescript -import { getSpotlight, setupSpotlightParser } from "./artemis-api/mod"; -import { MacosError } from "./artemis-api/src/macos/errors"; -import { glob } from "./artemis-api/src/filesystem/files"; -import { FileError } from "./artemis-api/src/filesystem/errors"; - -function main() { - // Glob to our primary Spotlight database - const glob_path = "/System/Volumes/Data/.Spotlight-V100/Store-V*/*/*"; - const values = glob(glob_path); - if (values instanceof FileError) { - console.error(values); - return; - } - - let store = ""; - // We need the full path to the store.db - for (const value of values) { - if (value.filename == "store.db") { - store = value.full_path; - break; - } - } - - if (store === "") { - console.error("Failed to find store.db"); - return; - } - - // Get some initial metadata that we will need later - const meta = setupSpotlightParser("/System/Volumes/Data/.Spotlight-V100/Store-V*/*/*"); - if (meta instanceof MacosError) { - console.error(meta); - return; - } - - let offset = 0; - // To avoid high memory usage we can only parse 10 blocks of Spotlight data at time - // We can determine how many total blocks there are by viewing the meta results from setupSpotlightParser - // So we loop until our offset is greater than the meta.blocks value - while (offset < meta.blocks.length) { - // Parse 10 blocks - const results = getSpotlight(meta, store, offset); - if (results instanceof MacosError) { - console.error(results); - break; - } - - offset += 10; - - console.log(`Got ${results.length} entries!`); - for (const entry of results) { - console.log(`Inode: ${entry.inode}`); - const downloads = JSON.stringify(entry.values); - if (downloads.includes("Downloads") || downloads.includes(".pkg")) { - console.log(downloads); - } - } - - } - -} - -main(); -``` - -Example output you may see: -```json -Inode: 21365561 -{ - "_kMDItemContentModificationDateWeekday": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 3 - ] - }, - "_kMDItemContentCreationDateWeekOfMonth": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 4 - ] - }, - "_kMDItemContentModificationDateWeekOfYear": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 21 - ] - }, - "_kMDItemContentModificationDateHour": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 20 - ] - }, - "kMDItemDateAdded": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:46:33.000Z" - ] - }, - "_kMDItemContentModificationDateWeekOfMonth": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 4 - ] - }, - "_kMDItemContentCreationDateDay": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 20 - ] - }, - "_kMDItemContentCreationDateMonth": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 5 - ] - }, - "kMDItemContentCreationDate": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:45:35.000Z" - ] - }, - "_kMDItemContentCreationDateWeekdayOrdinal": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 3 - ] - }, - "_kMDItemContentModificationDateYear": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 2025 - ] - }, - "kMDItemDocumentIdentifier": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "_kMDItemContentModificationDateWeekdayOrdinal": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 3 - ] - }, - "kMDItemInterestingDate_Ranking": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:00:00.000Z" - ] - }, - "_kMDItemCreationDate": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:45:35.000Z" - ] - }, - "_kMDItemFromImporter": { - "attribute": "AttrBool", - "value": true - }, - "kMDItemKind": { - "attribute": "AttrList", - "value": [ - "INSTALLER_FLAT_PACKAGE\u0016\u0002", - "INSTALLER_FLAT_PACKAGE\u0016\u0002Base", - "حزمة المثبّت المسطحة\u0016\u0002ar", - "Paquet simple de l’instal·lador\u0016\u0002ca", - "Balíček instalátoru (flat)\u0016\u0002cs", - "Flad installeringspakke\u0016\u0002da", - "Einfaches Installationspaket\u0016\u0002de", - "Επίπεδο πακέτο του προγράμματος εγκατάστασης\u0016\u0002el", - "Installer flat package\u0016\u0002en", - "Installer flat package\u0016\u0002en_AU", - "Installer flat package\u0016\u0002en_GB", - "Paquete plano del instalador\u0016\u0002es", - "Paquete raso del Instalador\u0016\u0002es_419", - "Asentajan litteä paketti\u0016\u0002fi", - "Paquet brut du programme d’installation\u0016\u0002fr", - "Paquet brut du programme d’installation\u0016\u0002fr_CA", - "חבילת התקנה שטוחה\u0016\u0002he", - "इंस्टॉलर फ़्लैट पैकेज\u0016\u0002hi", - "Obični paket instalacijskog programa\u0016\u0002hr", - "Telepítő lapos csomag\u0016\u0002hu", - "Paket datar Penginstal\u0016\u0002id", - "Pacchetto flat Installer\u0016\u0002it", - "インストーラ・フラット・パッケージ\u0016\u0002ja", - "설치 프로그램 플랫 패키지\u0016\u0002ko", - "Pakej rata pemasang\u0016\u0002ms", - "Installatieprogramma-pakket (plat)\u0016\u0002nl", - "Installerer for flat pakke\u0016\u0002no", - "jednorodny pakiet instalacyjny\u0016\u0002pl", - "Pacote simples do Instalador\u0016\u0002pt", - "Pacote simples do Instalador\u0016\u0002pt_PT", - "Pachet plat Program de instalare\u0016\u0002ro", - "Простой установочный пакет\u0016\u0002ru", - "Paušálny inštalačný balík\u0016\u0002sk", - "Enostaven paket namestitvenega programa\u0016\u0002sl", - "Platt installationspaket\u0016\u0002sv", - "ชุดโปรแกรมตัวติดตั้งแบบ Flat\u0016\u0002th", - "Düz yapılı Yükleyici paketi\u0016\u0002tr", - "Звичайний пакет Інсталятора\u0016\u0002uk", - "Gói trình cài đặt phẳng\u0016\u0002vi", - "安装器flat软件包\u0016\u0002zh_CN", - "安裝程式單層套件\u0016\u0002zh_HK", - "安裝程式單層套件\u0016\u0002zh_TW" - ] - }, - "_kMDItemGroupId": { - "attribute": "AttrVariableSizeInt", - "value": 18 - }, - "_kMDItemContentCreationDateHour": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 20 - ] - }, - "_kMDItemOwnerGroupID": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 20 - ] - }, - "_kMDItemTypeCode": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "_kMDItemContentCreationDateYear": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 2025 - ] - }, - "kMDItemPhysicalSize": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 25919488 - ] - }, - "_kMDItemPrescanCandidate": { - "attribute": "AttrBool", - "value": true - }, - "_kMDItemIsExtensionHidden": { - "attribute": "AttrBool", - "value": false - }, - "kMDItemDisplayName": { - "attribute": "AttrString", - "value": "osquery-5.17.0.pkg\u0016\u0002" - }, - "kMDItemContentModificationDate": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:45:48.000Z" - ] - }, - "_kMDItemDisplayNameWithExtensions": { - "attribute": "AttrString", - "value": "osquery-5.17.0.pkg\u0016\u0002" - }, - "_kMDItemCreatorCode": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "kMDItemContentTypeTree": { - "attribute": "AttrList", - "value": [ - "com.apple.installer-package-archive", - "public.data", - "public.item", - "public.archive" - ] - }, - "_kMDItemContentChangeDate": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:45:48.000Z" - ] - }, - "kMDItemContentCreationDate_Ranking": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:00:00.000Z" - ] - }, - "_kMDItemInterestingDate": { - "attribute": "AttrDate", - "value": [ - "2025-05-21T00:45:48.000Z" - ] - }, - "_kMDItemContentModificationDateDay": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 20 - ] - }, - "_kMDItemContentCreationDateWeekOfYear": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 21 - ] - }, - "_kMDItemFinderFlags": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "kMDItemLogicalSize": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 25005632 - ] - }, - "_kMDItemOwnerUserID": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 501 - ] - }, - "_kMDItemFileName": { - "attribute": "AttrString", - "value": "osquery-5.17.0.pkg" - }, - "_kMDItemTextContentIndexExists": { - "attribute": "AttrBool", - "value": false - }, - "kMDItemContentType": { - "attribute": "AttrList", - "value": "com.apple.installer-package-archive" - }, - "_kMDItemContentCreationDateWeekday": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 3 - ] - }, - "_kMDItemFinderLabel": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "_kMDItemContentModificationDateMonth": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 5 - ] - } -} -``` - - -### getXprotectDefinitions(alt_path) -> XprotectEntries[] | MacosError - -Grab Xprotect definitions on macOS. By default artemis will check for -Xprotect.plist files at: - -- /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Resources/Xprotect.plist -- /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Xprotect.plist - -You may also provide an optional alternative path to the Xprotect.plist file. - -| Param | Type | Description | -| -------- | ------ | ------------------------------------ | -| alt_path | string | Optional path to Xprotect.plist file | - -### luluRules(alt_path) -> LuluRules | MacosError - -Grab LuLu rules on macOS. By default artemis will check for rule.plist file at: - -- /Library/Objective-See/LuLu/rules.plist - -You may also provide an optional alternative path to the rules.plist file. - -| Param | Type | Description | -| -------- | ------ | --------------------------------- | -| alt_file | string | Optional path to rules.plist file | - -### munkiApplicationUsage(db) -> MunkiApplicationUsage[] | MacosError - -Grab application usage tracked by Munki on macOS. By default artemis will check -for application_usage.sqlite file at: - -- /Library/Managed Installs/application_usage.sqlite - -You may also provide an optional alternative path to the -application_usage.sqlite file. - -| Param | Type | Description | -| ----- | ------ | ---------------------------------------------- | -| db | string | Optional path to application_usage.sqlite file | - -### quarantineEvents(alt_file) -> MacosQuarantine[] | MacosError - -Grab quarantine events tracked by macOS. By default artemis will check for -quarantine events for all users file at: - -- /Users/*/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 - -You may also provide an optional alternative path to the -com.apple.LaunchServices.QuarantineEventsV2 file. - -| Param | Type | Description | -| -------- | ------ | ----------------------------------------------------------------- | -| alt_file | string | Optional path to com.apple.LaunchServices.QuarantineEventsV2 file | - -### parseBiome(app_focus_only, alt_file) -> Biome[] - -Parse a Biome files and try to extract data. By default artemis will only parse -App.InFocus files located at: - -- /Users/\*/Library/Biome/streams/\*/\*/local/\* -- /Users/\*/Library/Biome/streams/\*/\*/local/tombstone/\* -- /private/var/db/biome/streams/\*/\*/local/\* -- /private/var/db/biome/streams/\*/\*/local/tombstone/\* - -| Param | Type | Description | -| -------------- | ------- | ------------------------------------------------- | -| app_focus_only | boolean | Only parse App.InFocus files. Default is **true** | -| alt_file | string | Optional path to an alternative Biome file | - -### gatekeeperEntries(db) -> GatekeeperEntries[] | MacosError - -Grab Gatekeeper entries on macOS. By default artemis will parse the sqlite -database at: - -- /var/db/SystemPolicy - -You may also provide an optional alternative path to the SystemPolicy file. - -| Param | Type | Description | -| ----- | ------ | ---------------------------------- | -| db | string | Optional path to SystemPolicy file | - -### logonsMacos(path, archive_path) -> LogonMacos[] | MacosError - -Extract Logon entries from UnifiedLog files (.tracev3) on macOS. Typically found -at: - -- /private/var/db/diagnostics/Special - -You may also specify an optional logarchive style directory containing the -Unified Log metadata (UUID directories, timesync, and dsc directory). Otherwise -artemis will parse their default locations. - -| Param | Type | Description | -| ------------ | ------ | ----------------------------------------------------------------------------- | -| path | string | Path to .tracev3 file | -| archive_path | string | Optional path to a logarchive style directory containing Unified Log metadata | - -### parseCookies(path) -> Cookie[] | MacosError - -Parse binary Safri cookie at provided path. - -| Param | Type | Description | -| ----- | ------ | -------------------------- | -| path | string | Path to binary cookie file | - -### Safari Browser Class - -A basic TypeScript class to extract data from the Safari browser. You may optionally enable Unfold URL parsing (default is disabled) and provide an alternative glob to the base Safari directory. - -Sample TypeScript code: -```typescript -import { Safari, PlatformType } from "./artemis-api/mod"; - -function main() { - const enable_unfold = true; - const client = new Safari(PlatformType.Darwin, enable_unfold); - - const start = 0; - const limit = 300; - const history_data = client.history(start, limit); - console.log(JSON.stringify(history_data)); -} - -main(); -``` - -#### history(offset, limit) -> SafariHistory[] - -Return Safari history for all users. Safari history exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying history. -You may provide a starting offset and limit when querying history. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### downloads() -> SafariDownloads[] - -Return Safari file downloads for all users. Safari file downloads exists in a plist file. - -#### favicons(offset, limit) -> SafariFavicons[] - -Return Safari favicons for all users. Safari favicons exists in a sqlite database. -Artemis will bypass locked sqlite databases when querying histfaviconsory. -You may provide a starting offset and limit when querying favicons. -By default artemis will get the first 100 entries for all users. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------- | -| offset | number | Starting offset when querying the sqlite database | -| limit | number | Max number of rows to return per user | - -#### bookmarks() -> SafariBookmark[] - -Return Safari bookmarks for all users. - -#### cookies() -> Cookie[] - -Return Safari cookies for all users. - -#### extensions() -> SafariExtensions[] - -Return Safari extensions for all users. - -#### retrospect(output) -> void - -A powerful feature that will timeline all Safari browser artifacts. This functionality is similar to [Hindsight](https://github.com/obsidianforensics/hindsight). - -You will need to provide an Output object specifying how you want to output the results. You may only output as JSON or JSONL. **JSONL** is strongly recommended. This function returns no data, instead it outputs the results based on your Output object. - -Sample code below: -```typescript -import { Safari, PlatformType } from "./artemis-api/mod"; -import { Output } from "./artemis-api/src/system/output"; - -function main() { - const client = new Safari(PlatformType.Darwin); - const out: Output = { - name: "safari_timeline", - directory: "./tmp", - format: Format.JSONL, - compress: false, - // We can set this to false because the TypeScript/JavaScript API will timeline for us instead of using the Rust code - timeline: false, - endpoint_id: "abc", - collection_id: 0, - output: OutputType.LOCAL, - } - - // No data is returned. Our results and errors will appear at `./tmp/safari_timeline` - client.retrospect(out); - -} - -main(); -``` - -### parseSystemStats(alt_file) -> SystemStats[] | MacosError - -Parse a stats files and try to extract data. - -| Param | Type | Description | -| -------------- | ------- | ------------------------------------------------- | -| alt_file | string | Optional path to an alternative stats file | \ No newline at end of file +--- +description: Interact with macOS Artifacts +--- + +# macOS + +These functions can be used to pull data related to macOS artifacts. + +You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). +Then you may import them into your TypeScript code. + +For example: +```typescript +import { listApps } from "./artemis-api/mod"; + +function main() { + const results = listApps(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +### getUsers(path) -> Users[] | MacosError + +Return all local users on macOS system. Can provide an optional alternative path +to directory containing users. Otherwise will use default path on system +/var/db/dslocal/nodes/Default/users + +| Param | Type | Description | +| ----- | ------ | -------------------------------------------- | +| path | string | Optional alternative path to users directory | + +### getGroup(path) -> Groups[] | MacosError + +Return all local groups on macOS system. Can provide an optional alternative path +to directory containing groups. Otherwise will use default path on system +/var/db/dslocal/nodes/Default/groups + +| Param | Type | Description | +| ----- | ------ | --------------------------------------------- | +| path | string | Optional alternative path to groups directory | + +### parseAlias(data) -> Alias | MacosError + +Parse macOS [alias](https://en.wikipedia.org/wiki/Alias_(Mac_OS)) data. Alias +files are a legacy shortcut format. May be encountered in plist files such as +the firewall plist file. + +| Param | Type | Description | +| ----- | ---------- | --------------- | +| data | Uint8Array | Raw alias bytes | + +### getEmond(path) -> Emond[] | MacosError + +Get all [Emond](../../Artifacts/macOS%20Artifacts/emond.md) rules on macOS. FYI +Emond was removed on Ventura. Can provide an optional alternative path to +directory containing emond rules. Otherwise will parse emond config on system to +try to find rules + +| Param | Type | Description | +| ----- | ------ | ---------------------------------------- | +| path | String | Optional alternative path to emond rules | + +### getExecpolicy(path) -> ExecPolicy[] | MacosError + +Parse the ExecPolicy sqlite database on macOS. Can provide an optional +alternative path to ExecPolicy database. Otherwise will parse default database +on system at /var/db/SystemPolicyConfiguration/ExecPolicy + +| Param | Type | Description | +| ----- | ------ | ------------------------------------------------ | +| path | String | Optional alternative path to ExecPolicy database | + +### firewallStatus(alt_path) -> Firewall | MacosError + +Return firewall information and status on macOS. Can provide an optional path to +com.apple.alf.plist, otherwise will use /Library/Preferences/com.apple.alf.plist + +| Param | Type | Description | +| -------- | ------ | ----------------------------------------------------- | +| alt_path | String | Alternative full path to the com.apple.alf.plist file | + +### getFsevents(path) -> Fsevents[] | MacosError + +Parse macOS FsEvents from provided file. + +| Param | Type | Description | +| ----- | ------ | ------------------------------ | +| path | String | Full path to the FsEvents file | + +### getLaunchdDaemons() -> Launchd[] | MacosError + +Return all Launch daemons on macOS + +### getLaunchdAgents() -> Launchd[] | MacosError + +Return all Launch agents on macOS + +### getLoginitems(path) -> LoginItems[] | MacosError + +Return all LoginItems on macOS. Can provide an optional alternative path to a +LoginItem file (.btm). Otherwise will parse default default locations for +LoginItems + +| Param | Type | Description | +| ----- | ------ | ------------------------------------------- | +| path | String | Optional alternative path to LoginItem file | + +### getMacho(path) -> MachoInfo[] | MacosError + +Parse a macho file and return metadata about the binary. + +| Param | Type | Description | +| ----- | ------ | -------------------- | +| path | string | Path to macho binary | + +### getPlist(path or Uint8Array) -> Record<string, unknown> | Uint8Array | Record<string, unknown>[] | MacosError + +Parse a plist file. Supports parsing a provide plist file path or the raw bytes +of plist data. Sometimes a plist file may contain another base64 encoded plist. +This function can parse the raw plist bytes. + +| Param | Type | Description | +| ------------------ | -------------------- | ------------------------------------- | +| path or Uint8Array | string or Uint8Array | Path to plist file or raw plist bytes | + +### passwordPolicy(alt_path) -> PasswordPolicy[] | MacosError + +Get password policies on macOS. Will parse plist file at +/var/db/dslocal/nodes/Default/config/shadowhash.plist. You may also provide an +optional alternative path to the shadowhash.plist file. + +| Param | Type | Description | +| -------- | ------ | -------------------------------------------------- | +| alt_path | String | Optional alternative path to shadowhash.plist file | + +### getUnifiedLog(path, archive_path) -> UnifiedLog[] | MacosError + +Parse a single UnifiedLog file (.tracev3) on macOS. Typically found at: + +- /private/var/db/diagnostics/Persist +- /private/var/db/diagnostics/Signpost +- /private/var/db/diagnostics/HighVolume +- /private/var/db/diagnostics/Special + +You may also specify an optional logarchive style directory containing the +Unified Log metadata (UUID directories, timesync, and dsc directory). Otherwise +artemis will parse their default locations. + +| Param | Type | Description | +| ------------ | ------ | ----------------------------------------------------------------------------- | +| path | string | Path to .tracev3 file | +| archive_path | string | Optional path to a logarchive style directory containing Unified Log metadata | + +### parseRequirementBlob(data) -> SingleRequirement | MacosError + +Parse the Requirement Blob from raw codesigning bytes. This part of Apple's +CodeSigning framework. This data can be found in macho binaries and also plist +files. + +| Param | Type | Description | +| ----- | ---------- | ------------------------------------------ | +| data | Uint8Array | Raw bytes associated with requirement blob | + +### listApps() -> Applications[] | MacosError + +Return a simple Application listing. Searches user installed Apps, System Apps, +default Homebrew paths: + +- /usr/local/Cellar +- /opt/homebrew/Cellar + +Use scanApps() if you want to scan the entire filesystem for Apps + +### scanApps() -> Applications[] | MacosError + +Scans the entire filesystem under /System/ and tries to parse all Applications. + +Includes embedded Apps, Frameworks, and any file that ends with +%/Contents/Info.plist + +Use listApps() if you a simpler Application listing + +### dockTiles() -> Applications[] | MacosError + +Scans the entire filesystem under /System looking for Applications that use +DockTile persistence. See https://theevilbit.github.io/beyond/beyond_0032/ for +details on Dock Tile PlugIns + +Includes embedded Apps, Frameworks, and any file that ends with +%/Contents/Info.plist + +### getPackages(glob_path) -> HomebrewReceipt[] + +Get Homebrew packages on the system. Does **not** include Casks.\ +Use getHomebrewInfo() to get all packages and Casks. + +By default this function will search for all packages at: + +- /opt/homebrew/Cellar +- /usr/local/Cellar + +| Param | Type | Description | +| --------- | ------ | ------------------------------------- | +| glob_path | string | Optional alternative glob path to use | + +### getCasks(glob_path) -> HomebrewFormula[] + +Get Homebrew Casks on the system. Does **not** include packages.\ +Use getHomebrewInfo() to get all packages and Casks. + +By default this function will search for all packages at: + +- /opt/homebrew/Caskroom +- /usr/local/Caskroom + +| Param | Type | Description | +| --------- | ------ | ------------------------------------- | +| glob_path | string | Optional alternative glob path to use | + +### getHomebrewInfo() -> HomebrewData + +Get Homebrew packages and Casks on the system. Searches for Homebrew data at: + +- /opt/homebrew +- /usr/local + +### wifiNetworks() -> Wifi[] + +Get list of joined Wifi networks on macOS. Requires root access. + +By default it will try to parse WiFi networks at +/Library/Preferences/com.apple.wifi.known-networks.plist. + +You may also provide an optional alternative path to +com.apple.wifi.known-networks.plist. + +| Param | Type | Description | +| -------- | ------ | --------------------------------------------------------------------- | +| alt_path | String | Optional alternative path to com.apple.wifi.known-networks.plist file | + +### getSudoLogs() -> UnifiedLog[] + +Parse the UnifiedLogs and extract entries related to sudo activity. + +### parseBom(path) -> Bom + +Parse Bill of Materials (BOM) files. BOM files are created whenever the macOS +Installer is used to install an application.\ +BOM files track what files were created by the Installer. It is commonly used to +ensure files are removed when the application is uninstalled. This function will +also try to parse the plist receipt associated with the BOM file (if found in +same directory). + +BOM files are located at /var/db/receipts/*.bom + +| Param | Type | Description | +| ----- | ------ | ---------------- | +| path | string | Path to BOM file | + +### systemExtensions(alt_path) -> SystemExtension[] + +Get list of macOS System Extensions. By default artemis will try to extract +installed extensions at /Library/SystemExtensions/db.plist. + +However, you may also provide an optional alternative path to db.plist. + +| Param | Type | Description | +| -------- | ------ | ------------------------------------------ | +| alt_path | String | Optional alternative path to db.plist file | + +### queryTccDb(alt_db) -> TccValues[] | MacosError + +Query all TCC.db files on the system. TCC.db contains granted permissions for +applications.\ +An optional path to the TCC.db can be provided. Otherwise will parse all user +and System TCC.db files. + +| Param | Type | Description | +| ------ | ------ | ---------------------------- | +| alt_db | string | Optional path to TCC.db file | + +### Parsing macOS Spotlight +#### setupSpotlightParser(glob_path) -> StoreMeta | MacosError + +Collect and setup the required data needed to parse the macOS Spotlight +database.\ +This function must be called before a user can parse the Spotlight database +using the API. + +The glob_path should point to the directory containing the Spotlight database +files.\ +The primary Spotlight database can be found at: +/System/Volumes/Data/.Spotlight-V100/Store-V\*/\*/\*\ +Would return something like: +/System/Volumes/Data/.Spotlight-V100/Store-V3/123-445566-778-12384/* + +| Param | Type | Description | +| --------- | ------ | ---------------------------------------------------------------- | +| glob_path | string | Glob path to a directory containing the Spotlight Database files | + +#### getSpotlight(meta, store_file, offset) -> StoreMeta | MacosError + +Parse the macOS Spotlight database. The database can potentially return a large +amount of data (5+GBs).\ +To prevent excessive memory usage, this function will parse the database in +blocks (chunks). + +It will parse **10** blocks at a time before returning the results. The +`StoreMeta` value obtained from setupSpotlightParser, contains the **TOTAL** +amount of blocks in the Spotlight database! You must loop through the blocks and +track what block offset the parser should start at! + +If you want to the parser to start at the beginning of the Spotlight database, +provide an offset of zero (0). Once the parser returns data, your next offset +will now be ten (10) because it parsed **10** blocks starting at zero (0-9). + +:::info + +Spotlight organizes data in "blocks". Think of blocks as containers that contain 1 or more Spotlight entries. +Parsing 10 blocks does **not** mean you are only getting 10 entries. + +Each block will typically contain ~50 to ~300 entries! +By getting 10 blocks you will be getting between ~500 to ~3000 entries! + +::: + +Finally, you must provide the full path to the Spotlight database file +(store.db). This is typically found in in the directory provided to +`setupSpotlightParser`\ +(ex: +/System/Volumes/Data/.Spotlight-V100/Store-V3/123-445566-778-12384/store.db) + +In order to parse Spotlight data you must have **root** permissions + +| Param | Type | Description | +| ---------- | --------- | ----------------------------------------------------- | +| meta | StoreMeta | Spotlight metadata obtained from setupSpotlightParser | +| store_file | string | Full path to the store.db file | +| offset | number | Offset to start parsing the Spotlight database | + +An example script is below that will show all Spotlight entries that contain the string "Downloads" or ".pkg". +```typescript +import { getSpotlight, setupSpotlightParser } from "./artemis-api/mod"; +import { MacosError } from "./artemis-api/src/macos/errors"; +import { glob } from "./artemis-api/src/filesystem/files"; +import { FileError } from "./artemis-api/src/filesystem/errors"; + +function main() { + // Glob to our primary Spotlight database + const glob_path = "/System/Volumes/Data/.Spotlight-V100/Store-V*/*/*"; + const values = glob(glob_path); + if (values instanceof FileError) { + console.error(values); + return; + } + + let store = ""; + // We need the full path to the store.db + for (const value of values) { + if (value.filename == "store.db") { + store = value.full_path; + break; + } + } + + if (store === "") { + console.error("Failed to find store.db"); + return; + } + + // Get some initial metadata that we will need later + const meta = setupSpotlightParser("/System/Volumes/Data/.Spotlight-V100/Store-V*/*/*"); + if (meta instanceof MacosError) { + console.error(meta); + return; + } + + let offset = 0; + // To avoid high memory usage we can only parse 10 blocks of Spotlight data at time + // We can determine how many total blocks there are by viewing the meta results from setupSpotlightParser + // So we loop until our offset is greater than the meta.blocks value + while (offset < meta.blocks.length) { + // Parse 10 blocks + const results = getSpotlight(meta, store, offset); + if (results instanceof MacosError) { + console.error(results); + break; + } + + offset += 10; + + console.log(`Got ${results.length} entries!`); + for (const entry of results) { + console.log(`Inode: ${entry.inode}`); + const downloads = JSON.stringify(entry.values); + if (downloads.includes("Downloads") || downloads.includes(".pkg")) { + console.log(downloads); + } + } + + } + +} + +main(); +``` + +Example output you may see: +```json +Inode: 21365561 +{ + "_kMDItemContentModificationDateWeekday": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 3 + ] + }, + "_kMDItemContentCreationDateWeekOfMonth": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 4 + ] + }, + "_kMDItemContentModificationDateWeekOfYear": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 21 + ] + }, + "_kMDItemContentModificationDateHour": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 20 + ] + }, + "kMDItemDateAdded": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:46:33.000Z" + ] + }, + "_kMDItemContentModificationDateWeekOfMonth": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 4 + ] + }, + "_kMDItemContentCreationDateDay": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 20 + ] + }, + "_kMDItemContentCreationDateMonth": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 5 + ] + }, + "kMDItemContentCreationDate": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:45:35.000Z" + ] + }, + "_kMDItemContentCreationDateWeekdayOrdinal": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 3 + ] + }, + "_kMDItemContentModificationDateYear": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 2025 + ] + }, + "kMDItemDocumentIdentifier": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "_kMDItemContentModificationDateWeekdayOrdinal": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 3 + ] + }, + "kMDItemInterestingDate_Ranking": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:00:00.000Z" + ] + }, + "_kMDItemCreationDate": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:45:35.000Z" + ] + }, + "_kMDItemFromImporter": { + "attribute": "AttrBool", + "value": true + }, + "kMDItemKind": { + "attribute": "AttrList", + "value": [ + "INSTALLER_FLAT_PACKAGE\u0016\u0002", + "INSTALLER_FLAT_PACKAGE\u0016\u0002Base", + "حزمة المثبّت المسطحة\u0016\u0002ar", + "Paquet simple de l’instal·lador\u0016\u0002ca", + "Balíček instalátoru (flat)\u0016\u0002cs", + "Flad installeringspakke\u0016\u0002da", + "Einfaches Installationspaket\u0016\u0002de", + "Επίπεδο πακέτο του προγράμματος εγκατάστασης\u0016\u0002el", + "Installer flat package\u0016\u0002en", + "Installer flat package\u0016\u0002en_AU", + "Installer flat package\u0016\u0002en_GB", + "Paquete plano del instalador\u0016\u0002es", + "Paquete raso del Instalador\u0016\u0002es_419", + "Asentajan litteä paketti\u0016\u0002fi", + "Paquet brut du programme d’installation\u0016\u0002fr", + "Paquet brut du programme d’installation\u0016\u0002fr_CA", + "חבילת התקנה שטוחה\u0016\u0002he", + "इंस्टॉलर फ़्लैट पैकेज\u0016\u0002hi", + "Obični paket instalacijskog programa\u0016\u0002hr", + "Telepítő lapos csomag\u0016\u0002hu", + "Paket datar Penginstal\u0016\u0002id", + "Pacchetto flat Installer\u0016\u0002it", + "インストーラ・フラット・パッケージ\u0016\u0002ja", + "설치 프로그램 플랫 패키지\u0016\u0002ko", + "Pakej rata pemasang\u0016\u0002ms", + "Installatieprogramma-pakket (plat)\u0016\u0002nl", + "Installerer for flat pakke\u0016\u0002no", + "jednorodny pakiet instalacyjny\u0016\u0002pl", + "Pacote simples do Instalador\u0016\u0002pt", + "Pacote simples do Instalador\u0016\u0002pt_PT", + "Pachet plat Program de instalare\u0016\u0002ro", + "Простой установочный пакет\u0016\u0002ru", + "Paušálny inštalačný balík\u0016\u0002sk", + "Enostaven paket namestitvenega programa\u0016\u0002sl", + "Platt installationspaket\u0016\u0002sv", + "ชุดโปรแกรมตัวติดตั้งแบบ Flat\u0016\u0002th", + "Düz yapılı Yükleyici paketi\u0016\u0002tr", + "Звичайний пакет Інсталятора\u0016\u0002uk", + "Gói trình cài đặt phẳng\u0016\u0002vi", + "安装器flat软件包\u0016\u0002zh_CN", + "安裝程式單層套件\u0016\u0002zh_HK", + "安裝程式單層套件\u0016\u0002zh_TW" + ] + }, + "_kMDItemGroupId": { + "attribute": "AttrVariableSizeInt", + "value": 18 + }, + "_kMDItemContentCreationDateHour": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 20 + ] + }, + "_kMDItemOwnerGroupID": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 20 + ] + }, + "_kMDItemTypeCode": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "_kMDItemContentCreationDateYear": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 2025 + ] + }, + "kMDItemPhysicalSize": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 25919488 + ] + }, + "_kMDItemPrescanCandidate": { + "attribute": "AttrBool", + "value": true + }, + "_kMDItemIsExtensionHidden": { + "attribute": "AttrBool", + "value": false + }, + "kMDItemDisplayName": { + "attribute": "AttrString", + "value": "osquery-5.17.0.pkg\u0016\u0002" + }, + "kMDItemContentModificationDate": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:45:48.000Z" + ] + }, + "_kMDItemDisplayNameWithExtensions": { + "attribute": "AttrString", + "value": "osquery-5.17.0.pkg\u0016\u0002" + }, + "_kMDItemCreatorCode": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "kMDItemContentTypeTree": { + "attribute": "AttrList", + "value": [ + "com.apple.installer-package-archive", + "public.data", + "public.item", + "public.archive" + ] + }, + "_kMDItemContentChangeDate": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:45:48.000Z" + ] + }, + "kMDItemContentCreationDate_Ranking": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:00:00.000Z" + ] + }, + "_kMDItemInterestingDate": { + "attribute": "AttrDate", + "value": [ + "2025-05-21T00:45:48.000Z" + ] + }, + "_kMDItemContentModificationDateDay": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 20 + ] + }, + "_kMDItemContentCreationDateWeekOfYear": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 21 + ] + }, + "_kMDItemFinderFlags": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "kMDItemLogicalSize": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 25005632 + ] + }, + "_kMDItemOwnerUserID": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 501 + ] + }, + "_kMDItemFileName": { + "attribute": "AttrString", + "value": "osquery-5.17.0.pkg" + }, + "_kMDItemTextContentIndexExists": { + "attribute": "AttrBool", + "value": false + }, + "kMDItemContentType": { + "attribute": "AttrList", + "value": "com.apple.installer-package-archive" + }, + "_kMDItemContentCreationDateWeekday": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 3 + ] + }, + "_kMDItemFinderLabel": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "_kMDItemContentModificationDateMonth": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 5 + ] + } +} +``` + + +### getXprotectDefinitions(alt_path) -> XprotectEntries[] | MacosError + +Grab Xprotect definitions on macOS. By default artemis will check for +Xprotect.plist files at: + +- /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Resources/Xprotect.plist +- /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/Xprotect.plist + +You may also provide an optional alternative path to the Xprotect.plist file. + +| Param | Type | Description | +| -------- | ------ | ------------------------------------ | +| alt_path | string | Optional path to Xprotect.plist file | + +### luluRules(alt_path) -> LuluRules | MacosError + +Grab LuLu rules on macOS. By default artemis will check for rule.plist file at: + +- /Library/Objective-See/LuLu/rules.plist + +You may also provide an optional alternative path to the rules.plist file. + +| Param | Type | Description | +| -------- | ------ | --------------------------------- | +| alt_file | string | Optional path to rules.plist file | + +### munkiApplicationUsage(db) -> MunkiApplicationUsage[] | MacosError + +Grab application usage tracked by Munki on macOS. By default artemis will check +for application_usage.sqlite file at: + +- /Library/Managed Installs/application_usage.sqlite + +You may also provide an optional alternative path to the +application_usage.sqlite file. + +| Param | Type | Description | +| ----- | ------ | ---------------------------------------------- | +| db | string | Optional path to application_usage.sqlite file | + +### quarantineEvents(alt_file) -> MacosQuarantine[] | MacosError + +Grab quarantine events tracked by macOS. By default artemis will check for +quarantine events for all users file at: + +- /Users/*/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 + +You may also provide an optional alternative path to the +com.apple.LaunchServices.QuarantineEventsV2 file. + +| Param | Type | Description | +| -------- | ------ | ----------------------------------------------------------------- | +| alt_file | string | Optional path to com.apple.LaunchServices.QuarantineEventsV2 file | + +### parseBiome(app_focus_only, alt_file) -> Biome[] + +Parse a Biome files and try to extract data. By default artemis will only parse +App.InFocus files located at: + +- /Users/\*/Library/Biome/streams/\*/\*/local/\* +- /Users/\*/Library/Biome/streams/\*/\*/local/tombstone/\* +- /private/var/db/biome/streams/\*/\*/local/\* +- /private/var/db/biome/streams/\*/\*/local/tombstone/\* + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------- | +| app_focus_only | boolean | Only parse App.InFocus files. Default is **true** | +| alt_file | string | Optional path to an alternative Biome file | + +### gatekeeperEntries(db) -> GatekeeperEntries[] | MacosError + +Grab Gatekeeper entries on macOS. By default artemis will parse the sqlite +database at: + +- /var/db/SystemPolicy + +You may also provide an optional alternative path to the SystemPolicy file. + +| Param | Type | Description | +| ----- | ------ | ---------------------------------- | +| db | string | Optional path to SystemPolicy file | + +### logonsMacos(path, archive_path) -> LogonMacos[] | MacosError + +Extract Logon entries from UnifiedLog files (.tracev3) on macOS. Typically found +at: + +- /private/var/db/diagnostics/Special + +You may also specify an optional logarchive style directory containing the +Unified Log metadata (UUID directories, timesync, and dsc directory). Otherwise +artemis will parse their default locations. + +| Param | Type | Description | +| ------------ | ------ | ----------------------------------------------------------------------------- | +| path | string | Path to .tracev3 file | +| archive_path | string | Optional path to a logarchive style directory containing Unified Log metadata | + +### parseCookies(path) -> Cookie[] | MacosError + +Parse binary Safri cookie at provided path. + +| Param | Type | Description | +| ----- | ------ | -------------------------- | +| path | string | Path to binary cookie file | + +### Safari Browser Class + +A basic TypeScript class to extract data from the Safari browser. You may optionally enable Unfold URL parsing (default is disabled) and provide an alternative glob to the base Safari directory. + +Sample TypeScript code: +```typescript +import { Safari, PlatformType } from "./artemis-api/mod"; + +function main() { + const enable_unfold = true; + const client = new Safari(PlatformType.Darwin, enable_unfold); + + const start = 0; + const limit = 300; + const history_data = client.history(start, limit); + console.log(JSON.stringify(history_data)); +} + +main(); +``` + +#### history(offset, limit) -> SafariHistory[] + +Return Safari history for all users. Safari history exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying history. +You may provide a starting offset and limit when querying history. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### downloads() -> SafariDownloads[] + +Return Safari file downloads for all users. Safari file downloads exists in a plist file. + +#### favicons(offset, limit) -> SafariFavicons[] + +Return Safari favicons for all users. Safari favicons exists in a sqlite database. +Artemis will bypass locked sqlite databases when querying histfaviconsory. +You may provide a starting offset and limit when querying favicons. +By default artemis will get the first 100 entries for all users. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------- | +| offset | number | Starting offset when querying the sqlite database | +| limit | number | Max number of rows to return per user | + +#### bookmarks() -> SafariBookmark[] + +Return Safari bookmarks for all users. + +#### cookies() -> Cookie[] + +Return Safari cookies for all users. + +#### extensions() -> SafariExtensions[] + +Return Safari extensions for all users. + +#### retrospect(output) -> void + +A powerful feature that will timeline all Safari browser artifacts. This functionality is similar to [Hindsight](https://github.com/obsidianforensics/hindsight). + +You will need to provide an Output object specifying how you want to output the results. You may only output as JSON or JSONL. **JSONL** is strongly recommended. This function returns no data, instead it outputs the results based on your Output object. + +Sample code below: +```typescript +import { Safari, PlatformType } from "./artemis-api/mod"; +import { Output } from "./artemis-api/src/system/output"; + +function main() { + const client = new Safari(PlatformType.Darwin); + const out: Output = { + name: "safari_timeline", + directory: "./tmp", + format: Format.JSONL, + compress: false, + // We can set this to false because the TypeScript/JavaScript API will timeline for us instead of using the Rust code + timeline: false, + endpoint_id: "abc", + collection_id: 0, + output: OutputType.LOCAL, + } + + // No data is returned. Our results and errors will appear at `./tmp/safari_timeline` + client.retrospect(out); + +} + +main(); +``` + +### parseSystemStats(alt_file) -> SystemStats[] | MacosError + +Parse a stats files and try to extract data. + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------- | +| alt_file | string | Optional path to an alternative stats file | + +### recentFilesMacos(alt_file) -> RecentFiles[] | MacosError + +Parse a sharedfilelist (SFL) file and try to extract data. + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------- | +| alt_file | string | Optional path to an alternative SFL file | \ No newline at end of file diff --git a/artemis-docs/docs/API/Artifacts/unix.md b/artemis-docs/docs/API/Artifacts/unix.md index 3dd2e6cc..8fb36723 100644 --- a/artemis-docs/docs/API/Artifacts/unix.md +++ b/artemis-docs/docs/API/Artifacts/unix.md @@ -4,35 +4,50 @@ description: Interact with Unix Artifacts # Unix -These functions can be used to pull data related to Unix artifacts. They are -supported on both Linux and macOS. +These functions can be used to pull data related to Unix artifacts. They are primarily +supported on both Linux, macOS, and FreeBSD. You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). Then you may import them into your TypeScript code. For example: ```typescript -import { getBashHistory } from "./artemis-api/mod"; +import { getBashHistory, PlatformType } from "./artemis-api/mod"; function main() { - const results = getBashHistory(); + const results = getBashHistory(PlatformType.Linux); return results; } main(); ``` -### getBashHistory() -> BashHistory[] | UnixError +### getBashHistory(platform: PlatformType.Linux | PlatformType.Darwin, alt_file?: string) -> BashHistory[] | UnixError Get bash history for all users. -### getZshHistory() -> ZshHistory[] | UnixError +| Param | Type | Description | +| -------- | ------------ | ----------------------------------------- | +| platform | PlatformType | Either Linux or macOS platform | +| alt_file | string | Optional alternative path to bash history | + +### getZshHistory(platform: PlatformType.Linux | PlatformType.Darwin, alt_file?: string) -> ZshHistory[] | UnixError Get zsh history for all users. -### getPythonHistory() -> PythonHistory[] | UnixError +| Param | Type | Description | +| -------- | ------------ | ----------------------------------------- | +| platform | PlatformType | Either Linux or macOS platform | +| alt_file | string | Optional alternative path to zsh history | + +### getAshHistory(platform: PlatformType.Linux | PlatformType.Darwin, alt_file?: string) -> AshHistory[] | UnixError + +Get ash history for all users. -Get python history for all users. +| Param | Type | Description | +| -------- | ------------ | ----------------------------------------- | +| platform | PlatformType | Either Linux or macOS platform | +| alt_file | string | Optional alternative path to ash history | ### getCron() -> Cron[] | UnixError diff --git a/artemis-docs/docs/API/Artifacts/windows.md b/artemis-docs/docs/API/Artifacts/windows.md index 20e21d66..523f046f 100644 --- a/artemis-docs/docs/API/Artifacts/windows.md +++ b/artemis-docs/docs/API/Artifacts/windows.md @@ -1,707 +1,753 @@ ---- -description: Interact with Windows Artifacts ---- - -# Windows - -These functions can be used to pull data related to Windows artifacts. - -You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). -Then you may import them into your TypeScript code. - -For example: -```typescript -import { assembleScriptblocks } from "./artemis-api/mod"; - -function main() { - const powershell_scriptblocks = assembleScriptblocks(); - if(powershell_scriptblocks instanceof WindowsError) { - return; - } - - console.log(JSON.stringify(powershell_scriptblocks)); -} - -main(); -``` - -### getAmcache(path) -> Amcache[] | WindowsError - -Parse Amcache Registry file on the system drive. You may provide an optional alternative path to the Amcache.hve file - -| Param | Type | Description | -| ----- | ------- | ------------------------------------- | -| path | string | Optional path to an Amcache file | - -### getBits(carve, path) -> BitsInfo[] | WindowsError - -Parse Windows [BITS](../../Artifacts/Windows%20Artfacts/bits.md) data. Supports -carving deleted entries. You may also provide an optional alternative path to the BITS database file. - -| Param | Type | Description | -| ----- | ------- | ------------------------------------- | -| carve | boolean | Attempt to carve deleted BITS entries | -| path | string | Optional path to a BITS file | - - -### getEventlogs(path, offset, limit, include_templates, template_file) -> EventLogRecord[] |EventLogMessage[] | WindowsError - -Parse Windows EventLog file at provided path. If you want to include template -strings and you are on a non-Windows platform, a `template_file` file is -required. - -| Param | Type | Description | -| ----------------- | ------- | --------------------------------------------------------------- | -| path | string | Path to Windows EventLog file | -| offset | number | How many records to skip | -| limit | number | Max number of records to return | -| include_templates | boolean | Whether to include template strings in output. Default is false | -| template_file | string | Path to a JSON template file | - -### getJumplists(path) -> Jumplists[] | WindowsError - -Get all [JumpLists](../../Artifacts/Windows%20Artfacts/jumplists.md) for all -users at default system drive. You may also provide an optional alternative path to a Jumplist file. - -| Param | Type | Description | -| ----- | ------ | ------------------------------ | -| path | string | Optional path to Jumplist file | - -### readRawFile(path) -> Uint8Array | WindowsError - -Read a file at provided path by parsing the NTFS. You can read locked files with -this function. - -| Param | Type | Description | -| ----- | ------ | ----------------- | -| path | string | Path to file read | - -### readAdsData(path, ads_name) -> Uint8Array | WindowsError - -Read an Alternative Data Stream at provided file path. - -| Param | Type | Description | -| -------- | ------ | ----------------- | -| path | string | Path to file read | -| ads_name | string | ADS data to read | - -### getPe(path) -> PeInfo | WindowsError - -Parse PE file at provided path. - -| Param | Type | Description | -| ----- | ------ | --------------- | -| path | string | Path to PE file | - -### getPrefetch() -> Prefetch[] | WindowsError - -Parse all Prefetch files at default system drive. You may also provide an optional alternative path to a directory containing Prefetch files. - -| Param | Type | Description. | -| ----- | ------ | ----------------------------------- | -| path | string | Optional path to Prefetch directory | - -### getRecycleBin() -> RecycleBin[] | WindowsError - -Parse all RecycleBin files default system drive. - -| Param | Type | Description | -| ----- | ------ | -------------------------------- | -| path | string | Optional path to RecycleBin file | - -### getRegistry(path) -> Registry[] | WindowsError - -Parse Registry file at provided path. - -| Param | Type | Description | -| ----- | ------ | ---------------------- | -| path | string | Path to Registry file. | - -### getSearch(path, page_limit) -> SearchEntry[] | WindowsError - -Parse Windows [Search](../../Artifacts/Windows%20Artfacts/search.md) database at -provided path. - -You can provide an optional page_limit (default is 50). Will influence memory -usage, a higher number means higher memory usage but faster parsing. - -| Param | Type | Description | -| ---------- | ------ | ---------------------------------------------------------- | -| path | string | Path to Windows Search database. | -| page_limit | number | Set the number of pages to use when parsing. Default is 50 | - -### getServices() -> Services[] | WindowsError - -Parse Windows Services at default system drive. You may also provide an optional alternative path to the SYSTEM Registry file. - -| Param | Type | Description | -| ----- | ------ | --------------------------------------------- | -| path | string | Optional path to Windows SYSTEM Registry file | - - -### getShellbags(resolve_guids, path) -> Shellbags[] | WindowsError - -Parse Windows Shellbags at default system drive. You may enable GUID resolution (this feature only works on Windows). You may also provide an optional alternative path to the Shellbags Registry file. - -| Param | Type | Description | -| ------------- | ------ | ------------------------------------------------------- | -| resolve_guids | boolean| Enable GUID resolution. Only available on Windows | -| path | string | Optional path to either NTUSER.DAT or UsrClass.dat file | - -### getShimcache(path) -> Shimcache[] | WindowsError - -Parse Windows Shimcache at default system drive. You may also provide an optional alternative path to the SYSTEM Registry file. - - -| Param | Type | Description | -| ----- | ------ | ----------------------------------------- | -| path | string | Optional path to the SYSTEM Registry file | - - -### getShimdb(path) -> Shimdb[] | WindowsError - -Parse Windows ShimDB files at default system drive. You may also provide an optional path to a ShimDB file. - -| Param | Type | Description | -| ----- | ------ | -------------------------------------- | -| path | string | Optional path to a Windows ShimDB file | - - -### getLnkFile(path) -> Shortcut | WindowsError - -Parse Windows Shortcut file at provided path. - -| Param | Type | Description | -| ----- | ------ | ----------------------------- | -| path | string | Path to Windows Shortcut file | - -### getSrumApplicationInfo(path) -> ApplicationInfo[] | WindowsError - -Parse Application info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumApplicationTimeline(path) -> ApplicationTimeline[] | WindowsError - -Parse Application Timeline info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumApplicationVfu(path) -> AppVfu[] | WindowsError - -Parse Application VFU info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumEnergyInfo(path) -> EnergyInfo[] | WindowsError - -Parse Energy info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumEnergyUsage(path) -> EnergyUsage[] | WindowsError - -Parse Energy usage from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumNetworkInfo(path) -> NetworkInfo[] | WindowsError - -Parse Network info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumNetworkConnectivity(path) -> NetworkConnectivityInfo[] | WindowsError - -Parse Network connectivity info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getSrumNotifications(path) -> NotificationInfo[] | WindowsError - -Parse notification info from Windows -[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). - -| Param | Type | Description | -| ----- | ------ | ------------------------- | -| path | string | Path to Windows SRUM file | - -### getTasks(path) -> TaskXml | WindowsError - -Parse Windows Schedule Tasks at default system drive. You may also provide an optional path to a Schedule Task file. - -| Param | Type | Description | -| ----- | ------ | --------------------------------------------------------------------- | -| path | string | Path to Windows Schedule Task XML file | - -### getUserassist(resolve, path) -> UserAssist[] | WindowsError - -Parse Windows Userassist entries at default system drive for all users. You may enable GUID resolution lookups. You may also provide an optional path to the NTUSER.dat file. - -| Param | Type | Description | -| ------- | ------- | ------------------------------------------------- | -| resolve | boolean | Enable GUID resolution. Only available on Windows | -| path | string | Full path to NTUSER.DAT file | - -### getUsersWin(path) -> UserInfo[] | WindowsError - -Get local Windows User accounts from SAM Registry file. Uses default system drive -letter. You may also provide an optional path to the SAM Registry file. - -| Param | Type | Description | -| ----- | ------ | ---------------------------------- | -| path | string | Optional path to SAM Registry file | - -### getUsnjrnl(path, drive, mft) -> UsnJrnl[] | WindowsError - -Parses Windows UsnJrnl data. Uses default system drive letter. You may also provide optional alternative paths for: -- UsnJrnl file -- Drive letter -- MFT for path resolutions - -| Param | Type | Description | -| ----- | ------ | ---------------------------------------------- | -| drive | string | Optional drive letter to get Windows UsnJrnl | -| path | string | Optional path to UsnJrnl file | -| mft | string | Optional path to MFT file for path resolutions | - -### logonsWindows(path, limit) -> Logons[] | WindowsError - -Parse the Windows Security.evtx and extract Logon and Logoff events. - -| Param | Type | Description | -| ----- | ------ | --------------------------------------------------------------------------------- | -| path | string | Path to Windows Security.evtx file | -| limit | number | How may EventLog entries to parse when streaming the evtx file. Default is 10,000 | - -### lookupSecurityKey(path, offset) -> SecurityKey | WindowsError - -Parse Security Key data from Registry at provided Security Key offset. The -offset must be a postive number greater than 0. You can use getRegistry(path) to -pull a list of keys which contain Security Key offset data. - -It is not recommended to bulk lookup Security Key info due the amount of data. -Security Keys contain information about Registry key permissions and ACLs. Its -not super useful. - -| Param | Type | Description | -| ------ | ------ | ----------------------------- | -| path | string | Path to Windows Registry file | -| offset | number | Offset to Security Key | - -### ESE Database Class - -A basic class to help interact and extract data from ESE databases - -#### catalogInfo() -> Catalog[] | WindowsError - -Dump the Catalog metadata associated with an ESE database. Returns an array of -Catalog entries or WindowsError - -#### tableInfo(catalog, table_name) -> TableInfo - -Extract table metadata from parsed Catalog entries based on provided table name - -| Param | Type | Description | -| ---------- | --------- | ------------------------ | -| catalog | Catalog[] | Array of Catalog entries | -| table_name | string | Name of table to extract | - -#### getPages(first_page) -> number[] | WindowsError - -Get an array of all pages associated with a table starting at the first page -provided. First page can be found in the TableInfo object. - -| Param | Type | Description | -| ---------- | ------ | --------------------- | -| first_page | number | First page of a table | - -#### getRows(pages, info) -> Record<string, EseTable[][]> | WindowsError - -Get rows associated with provided TableInfo object and number of pages. A -returns a `Record` or WindowsError. - -The table name is the Record string key. - -[EseTable](../../Artifacts/Windows%20Artfacts/ese.md) is an array of rows and -columns representing ESE data. - -| Param | Type | Description | -| ----- | --------- | ---------------- | -| pages | number[] | Array of pages | -| info | TableInfo | TableInfo object | - -#### getFilteredRows(pages, info, column_name, column_data) -> Record<string, EseTable[][]> | WindowsError - -Get rows and filter based on provided column_name and column_data. This function -can be useful if you want to get data from a table thats shares data with -another table. For example, if you call getRows() to get data associated with -TableA and now you want to get data from TableB and both tables share a unique -key. - -Its _a little_ similar to "select \* from tableB where columnX = Y" where Y is a -unique key - -| Param | Type | Description | -| ----------- | ----------------------------- | ------------------------------------------------------------------------------ | -| pages | number[] | Array of pages | -| info | TableInfo | TableInfo object | -| column_name | string | Column name that you want to filter on | -| column_data | Record<string, boolean> | HashMap of column values to filter on. Only the key is used to filter the data | - -#### dumpTableColumns(pages, info, column_names) -> Record<string, EseTable[][]> | WindowsError - -Get rows based on specific columns names. This function is the same as getRows() -except it will only return column names that included in column_names. - -| Param | Type | Description | -| ----------- | --------- | -------------------------------------- | -| pages | number[] | Array of pages | -| info | TableInfo | TableInfo object | -| column_name | string[] | Array of column names to get data from | - -### getChocolateyInfo(alt_base) -> ChocolateyInfo[] | WindowsError - -Return a list of installed Chocolatey packages. Will use the ChocolateyInstall -ENV value by default (C:\ProgramData\chocolatey). - -An optional alternative base path can also be provided - -| Param | Type | Description | -| -------- | ------ | --------------------------------- | -| alt_base | string | Optional base path for Chocolatey | - -### Updates class - -A simple class to help dump the contents of the Windows DataStore.edb database. -This class extends the EseDatabase class. - -#### updateHistory(pages) -> UpdateHistory[] | WindowsError - -Return a list of Windows Updates by parsing the Windows DataStore.edb database. - -| Param | Type | Description | -| ----- | -------- | ------------------------------- | -| pages | number[] | Array of pages to get data from | - -### powershellHistory(platform, alt_path) -> History[] | History | WindowsError - -Return PowerShell history entries for all users. Uses the system drive by -default. - -This artifact also supports PowerShell history on macOS or Linux -An optional alternative path to ConsoleHost_history.txt can also be provided -instead. - -| Param | Type | Description | -| -------- | ------------ | ------------------------------------------------------ | -| platform | PlatformType | Platform type to parse history for. Default is Windows | -| alt_path | string | Optional full path to ConsoleHost_history.txt | - -### parseMru(ntuser_path) -> Mru[] | WindowsError - -Parse common Most Recently Used (MRU) locations in the Registry. Currently -parses: OpenSave, LastVisited, and RecentDocs MRU keys - -| Param | Type | Description | -| ----------- | ------ | ---------------------------- | -| ntuser_path | string | Full path to NTUSER.DAT file | - -### getShellItem(data) -> JsShellItem | WindowsError - -Parse raw bytes that contain a ShellItem. Returns a JsShellItem that contains -ShellItem and any remaining bytes. This function can be used to parse multiple -shellitems. - -| Param | Type | Description | -| ----- | ---------- | ---------------------- | -| data | Uint8Array | Raw bytes of shellitem | - -### UserAccessLogging class - -A simple class to help extract data from the Windows User Access Log database. -This class extends the EseDatabase class - -#### getRoleIds(pages) -> RoleIds[] | WindowsError - -Return an array of RoleIds associated with UAL database. This function expects -the UserAccessLogging class to be initialized with the SystemIdentity.mdb -database otherwise it will return no results. - -| Param | Type | Description | -| ----- | -------- | ------------------------------- | -| pages | number[] | Array of pages to get data from | - -#### getUserAccessLog(pages, roles_ual, role_page_chunk) -> UserAccessLog[] | WindowsError - -Parse the User Access Log (UAL) database on Windows Servers. This database -contains logon information for users on the system.\ -It is **not** related to M365 UAL (Unified Audit Logging)! - -This function expects the UserAccessLogging class to be initialized with the -Current.mdb or `{GUID}.mdb` database otherwise it will return no results. - -You may provide an optional UserAccessLogging associated with SystemIdentity.mdb -to perform RoleID lookups. Otherwise this table will parse the Current.mdb or -`{GUID}.mdb` database. You may also customize the number of pages that should be -used when doing RoleID lookups, by default 30 pages will used. - -| Param | Type | Description | -| --------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | -| pages | number[] | Array of pages to get data from | -| roles_ual | UserAccessLogging | Optional UserAccessLogging object that was initialized with the file SystemIdentity.mdb. Can be used to perform RoleID lookups | -| role_page_chunk | number | Number of pages that should be submitted when doing RoleID lookups. By default 30 page chunks will be used to do lookup | - -### userAccessLog(alt_dir) -> UserAccessLog[] | WindowsError - -Parse the User Access Log (UAL) database on Windows Servers. This database -contains logon information for users on the system.\ -It is **not** related to M365 UAL (Unified Audit Logging)! - -By default it will parse the databases at %SYSTEMROOT%\System32\LogFiles\Sum. -However, you may provided an optional alternative path if you want. - -| Param | Type | Description | -| ------- | ------ | ------------------------------------------------------ | -| alt_dir | string | Alternative directory containing the UAL log databases | - -### getWmiPersist() -> WmiPersist[] | WindowsError - -Parse the WMI Repository and extract persistence information. - -### getWmiPersistPath(path) -> WmiPersist[] | WindowsError - -Parse the WMI Repository and extract persistence information at provided path. -The directory must contain: - -- MAPPING\*.MAP -- OBJECTS.DATA -- INDEX.BTR - -| Param | Type | Description | -| ----- | ------ | ---------------------- | -| path | string | Path to WMI Repository | - -### listUsbDevices(alt_file) -> UsbDevices[] | WindowsError - -Parse SYSTEM Registry to get list of USB devices that have been connected - -| Param | Type | Description | -| -------- | ------ | -------------------------------------------- | -| alt_file | string | Alternative path to the SYSTEM Registry file | - -### serviceInstalls(path) -> ServiceInstalls[] | WindowsError - -Parse Windows System.evtx file to extract Service Install events. - -| Param | Type | Description | -| ----- | ------ | ----------------------------------- | -| path | string | Option path to the System.evtx file | - -### Outlook Class - -A basic class to help interact and extract data from OST files. - -#### rootFolder() -> FolderInfo | WindowsError - -Returns the root folder in an OST file. Can be used to start walking through the -OST file - -#### readFolder(folder) -> FolderInfo | WindowsError - -Reads the provided folder ID. Returns the same object as `rootFolder()` -function. - -| Param | Type | Description | -| ------ | ------ | ----------- | -| folder | number | Folder ID | - -#### readMessages(table, offset, limit) -> MessageDetails[] | WindowsError - -Read messages in a folder. You can specify which message to start at with the -offset and how many the limit. Returns an array of read messages or an error. - -An offset of 0 means, start with the first message. By default artemis will -return only 50 messages. - -| Param | Type | Description | -| ------ | --------- | -------------------------------------------------------------------------- | -| table | TableInfo | Table structure associated with the folder. Obtained by readFolder() | -| offset | number | First message to read. A value of 0 means read the first message | -| limit | number | Optional limit to provide to the function. By default 50 messages are read | - -#### readAttachment(block_id, descriptor_id) -> Attachment | WindowsError - -Read and extract and attach from provided block and descriptor IDs. The IDs can -be obtained from the readMessages function. If there are no IDs in the -MessageDetails object then the message has no attachment - -| Param | Type | Description | -| ------------- | ------ | ---------------------------------------- | -| block_id | number | Block ID associated with attachment | -| descriptor_id | number | Descriptor ID associated with attachment | - -#### parseWordWheel(path) -> WordWheelEntry[] | WindowsError - -Reads the provided glob path and parses all NTUSER.DAT files looking for -WordWheel entries. - -| Param | Type | Description | -| ----- | ------ | -------------------------- | -| path | string | Glob to NTUSER.DAT file(s) | - -#### assembleScriptblocks(path) -> Scriptblock[] | WindowsError - -Parses the Windows Microsoft-Windows-PowerShell%4Operational.evtx file and reassembles PowerShell Scriptblocks. -You may provided an optional alternative path to Microsoft-Windows-PowerShell%4Operational.evtx. - -| Param | Type | Description | -| ----- | ------ | --------------------------------------------------------------------------- | -| path | string | Optional alternative path to Microsoft-Windows-PowerShell%4Operational.evtx | - -#### firwallRules(path) -> FirewallRules[] | WindowsError - -Extract Windows Firewall rules from the SYSTEM Registry file. By default artemis will use the SYSTEM Registry on the system drive. -You may provide an optional alternative SYSTEM file. - -| Param | Type | Description | -| ----- | ------ | ------------------------------------------------- | -| path | string | Optional alternative path to System Registry file | - -#### processTreeEventLogs(path) -> EventLogProcessTree[] | WindowsError - -Parses the Windows Security.evtx file and attempts to construct process trees for 4688 events. -You may provided an optional alternative path to Security.evtx. - -| Param | Type | Description | -| ----- | ------ | ------------------------------------------ | -| path | string | Optional alternative path to Security.evtx | - -#### wifiNetworksWindows(path) -> Wifi[] | WindowsError - -Parses the Windows SOFTWARE Registry file and attempts extract WiFi networks connected to. -You may provided an optional alternative path to the SOFTWARE Registry file. - -| Param | Type | Description | -| ----- | ------ | ------------------------------------------ | -| path | string | Optional alternative path to Security.evtx | - -### ADCertificates Class - -A basic class to help interact and extract data from AD Certificates ESE databases. - -Sample TypeScript code: -```typescript -import { ADCertificates } from "./artemis-api/mod"; -import { WindowsError } from "./artemis-api/src/windows/errors"; - -function main() { - // You may also provide an optional alternative path to the ESE database - const client = new ADCertificates(); - const catalog = client.catalogInfo(); - if (catalog instanceof WindowsError) { - return; - } - const first_page = client.tableInfo(catalog, "Certificates").table_page; - const cert_pages = client.getPages(first_page); - if (cert_pages instanceof WindowsError) { - return; - } - // Depending on size of EDB file you should customize the number of pages you use - // The larger the pages array the more memory required - // 100 pages should be a reasonable default - const certs = client.getCertificates(cert_pages, client.tableInfo(catalog, "Certificates")) - console.log(JSON.stringify(certs)); -} - -main(); -``` - -#### getCertificates(pages, info) -> Certificates[] | WindowsError - -Get list of certificates from the database. - -| Param | Type | Description | -| ----- | --------- | --------------------------------------------------------- | -| pages | number[] | Number of pages to parse from the ESE database | -| info | TableInfo | Table info object that can be obtained with the ESE class | - - -#### getRequests(pages, info) -> Requests[] | WindowsError - -Get list of requests from the database. - -| Param | Type | Description | -| ----- | --------- | --------------------------------------------------------- | -| pages | number[] | Number of pages to parse from the ESE database | -| info | TableInfo | Table info object that can be obtained with the ESE class | - -#### requestAttributes(pages, info) -> RequestAttributes[] | WindowsError - -Get list of requests attributes from the database. - -| Param | Type | Description | -| ----- | --------- | --------------------------------------------------------- | -| pages | number[] | Number of pages to parse from the ESE database | -| info | TableInfo | Table info object that can be obtained with the ESE class | - - -### getRunKeys(path) -> RegistryRunKey[] - -Parse Windows Registry files and extract Run key entries. You may provide an optional path to a Registry file. By default all user NTUSER.DAT and SOFTWARE files are parsed - -| Param | Type | Description | -| ------- | ------- | -------------------------------- | -| path | string | Optional path to a Registry file | - - -### rdpLogons(path) -> RegistryRunKey[] - -Parse Windows RDP logons. You may provide an optional path to a Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx file. By default artemis will check the system drive volume for the Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx. - -Typically this will be C:\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx - -| Param | Type | Description | -| ------- | ------- | ---------------------------------------------------------------------------------------------- | -| path | string | Optional path to Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx file | - -### parsePca(alt_dir) -> ProgramCompatibilityAssist[] | WindowsError - -Parse Windows Program Compatibility Assistant (PCA) files. You may provide an optional alternative glob to a folder that contains the PCA files - -| Param | Type | Description | -| ---------- | ------- | -------------------------------------------- | -| alt_dir | string | Optional glob to folder containing PCA files | - - -### defenderQuarantineEventLog(path, limit) -> EventLogDefenderQuarantine[] - -Parse Windows Defender Quarantine events. You may provide an optional path to a Microsoft-Windows-Windows Defender%4Operational.evtx file. By default artemis will check the system drive volume for the Microsoft-Windows-Windows Defender%4Operational.evtx. - -Typically this will be C:\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-Windows Defender%4Operational.evtx - -| Param | Type | Description | -| ------- | ------- | -------------------------------------------------------------------------- | -| path | string | Optional path to Microsoft-Windows-Windows Defender%4Operational.evtx file | -| limit | number | Optional limit to set when streaming the EventLogs | \ No newline at end of file +--- +description: Interact with Windows Artifacts +--- + +# Windows + +These functions can be used to pull data related to Windows artifacts. + +You can access these functions by using git to clone the API [TypeScript bindings](https://github.com/puffyCid/artemis-api). +Then you may import them into your TypeScript code. + +For example: +```typescript +import { assembleScriptblocks } from "./artemis-api/mod"; + +function main() { + const powershell_scriptblocks = assembleScriptblocks(); + if(powershell_scriptblocks instanceof WindowsError) { + return; + } + + console.log(JSON.stringify(powershell_scriptblocks)); +} + +main(); +``` + +### getAmcache(path) -> Amcache[] | WindowsError + +Parse Amcache Registry file on the system drive. You may provide an optional alternative path to the Amcache.hve file + +| Param | Type | Description | +| ----- | ------- | ------------------------------------- | +| path | string | Optional path to an Amcache file | + +### getBits(carve, path) -> BitsInfo[] | WindowsError + +Parse Windows [BITS](../../Artifacts/Windows%20Artfacts/bits.md) data. Supports +carving deleted entries. You may also provide an optional alternative path to the BITS database file. + +| Param | Type | Description | +| ----- | ------- | ------------------------------------- | +| carve | boolean | Attempt to carve deleted BITS entries | +| path | string | Optional path to a BITS file | + + +### getEventlogs(path, offset, limit, include_templates, template_file) -> EventLogRecord[] |EventLogMessage[] | WindowsError + +Parse Windows EventLog file at provided path. If you want to include template +strings and you are on a non-Windows platform, a `template_file` file is +required. + +| Param | Type | Description | +| ----------------- | ------- | --------------------------------------------------------------- | +| path | string | Path to Windows EventLog file | +| offset | number | How many records to skip | +| limit | number | Max number of records to return | +| include_templates | boolean | Whether to include template strings in output. Default is false | +| template_file | string | Path to a JSON template file | + +### getJumplists(path) -> Jumplists[] | WindowsError + +Get all [JumpLists](../../Artifacts/Windows%20Artfacts/jumplists.md) for all +users at default system drive. You may also provide an optional alternative path to a Jumplist file. + +| Param | Type | Description | +| ----- | ------ | ------------------------------ | +| path | string | Optional path to Jumplist file | + +### readRawFile(path) -> Uint8Array | WindowsError + +Read a file at provided path by parsing the NTFS. You can read locked files with +this function. + +| Param | Type | Description | +| ----- | ------ | ----------------- | +| path | string | Path to file read | + +### readAdsData(path, ads_name) -> Uint8Array | WindowsError + +Read an Alternative Data Stream at provided file path. + +| Param | Type | Description | +| -------- | ------ | ----------------- | +| path | string | Path to file read | +| ads_name | string | ADS data to read | + +### getPe(path) -> PeInfo | WindowsError + +Parse PE file at provided path. + +| Param | Type | Description | +| ----- | ------ | --------------- | +| path | string | Path to PE file | + +### getPrefetch() -> Prefetch[] | WindowsError + +Parse all Prefetch files at default system drive. You may also provide an optional alternative path to a directory containing Prefetch files. + +| Param | Type | Description. | +| ----- | ------ | ----------------------------------- | +| path | string | Optional path to Prefetch directory | + +### getRecycleBin() -> RecycleBin[] | WindowsError + +Parse all RecycleBin files default system drive. + +| Param | Type | Description | +| ----- | ------ | -------------------------------- | +| path | string | Optional path to RecycleBin file | + +### getRegistry(path) -> Registry[] | WindowsError + +Parse Registry file at provided path. + +| Param | Type | Description | +| ----- | ------ | ---------------------- | +| path | string | Path to Registry file. | + +### getSearch(path, page_limit) -> SearchEntry[] | WindowsError + +Parse Windows [Search](../../Artifacts/Windows%20Artfacts/search.md) database at +provided path. + +You can provide an optional page_limit (default is 50). Will influence memory +usage, a higher number means higher memory usage but faster parsing. + +| Param | Type | Description | +| ---------- | ------ | ---------------------------------------------------------- | +| path | string | Path to Windows Search database. | +| page_limit | number | Set the number of pages to use when parsing. Default is 50 | + +### getServices() -> Services[] | WindowsError + +Parse Windows Services at default system drive. You may also provide an optional alternative path to the SYSTEM Registry file. + +| Param | Type | Description | +| ----- | ------ | --------------------------------------------- | +| path | string | Optional path to Windows SYSTEM Registry file | + + +### getShellbags(resolve_guids, path) -> Shellbags[] | WindowsError + +Parse Windows Shellbags at default system drive. You may enable GUID resolution (this feature only works on Windows). You may also provide an optional alternative path to the Shellbags Registry file. + +| Param | Type | Description | +| ------------- | ------ | ------------------------------------------------------- | +| resolve_guids | boolean| Enable GUID resolution. Only available on Windows | +| path | string | Optional path to either NTUSER.DAT or UsrClass.dat file | + +### getShimcache(path) -> Shimcache[] | WindowsError + +Parse Windows Shimcache at default system drive. You may also provide an optional alternative path to the SYSTEM Registry file. + + +| Param | Type | Description | +| ----- | ------ | ----------------------------------------- | +| path | string | Optional path to the SYSTEM Registry file | + + +### getShimdb(path) -> Shimdb[] | WindowsError + +Parse Windows ShimDB files at default system drive. You may also provide an optional path to a ShimDB file. + +| Param | Type | Description | +| ----- | ------ | -------------------------------------- | +| path | string | Optional path to a Windows ShimDB file | + + +### getLnkFile(path) -> Shortcut | WindowsError + +Parse Windows Shortcut file at provided path. + +| Param | Type | Description | +| ----- | ------ | ----------------------------- | +| path | string | Path to Windows Shortcut file | + +### getSrumApplicationInfo(path) -> ApplicationInfo[] | WindowsError + +Parse Application info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumApplicationTimeline(path) -> ApplicationTimeline[] | WindowsError + +Parse Application Timeline info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumApplicationVfu(path) -> AppVfu[] | WindowsError + +Parse Application VFU info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumEnergyInfo(path) -> EnergyInfo[] | WindowsError + +Parse Energy info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumEnergyUsage(path) -> EnergyUsage[] | WindowsError + +Parse Energy usage from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumNetworkInfo(path) -> NetworkInfo[] | WindowsError + +Parse Network info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumNetworkConnectivity(path) -> NetworkConnectivityInfo[] | WindowsError + +Parse Network connectivity info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getSrumNotifications(path) -> NotificationInfo[] | WindowsError + +Parse notification info from Windows +[SRUM](../../Artifacts/Windows%20Artfacts/srum.md). + +| Param | Type | Description | +| ----- | ------ | ------------------------- | +| path | string | Path to Windows SRUM file | + +### getTasks(path) -> TaskXml | WindowsError + +Parse Windows Schedule Tasks at default system drive. You may also provide an optional path to a Schedule Task file. + +| Param | Type | Description | +| ----- | ------ | --------------------------------------------------------------------- | +| path | string | Path to Windows Schedule Task XML file | + +### getUserassist(resolve, path) -> UserAssist[] | WindowsError + +Parse Windows Userassist entries at default system drive for all users. You may enable GUID resolution lookups. You may also provide an optional path to the NTUSER.dat file. + +| Param | Type | Description | +| ------- | ------- | ------------------------------------------------- | +| resolve | boolean | Enable GUID resolution. Only available on Windows | +| path | string | Full path to NTUSER.DAT file | + +### getUsersWin(path) -> UserInfo[] | WindowsError + +Get local Windows User accounts from SAM Registry file. Uses default system drive +letter. You may also provide an optional path to the SAM Registry file. + +| Param | Type | Description | +| ----- | ------ | ---------------------------------- | +| path | string | Optional path to SAM Registry file | + +### getUsnjrnl(path, drive, mft) -> UsnJrnl[] | WindowsError + +Parses Windows UsnJrnl data. Uses default system drive letter. You may also provide optional alternative paths for: +- UsnJrnl file +- Drive letter +- MFT for path resolutions + +| Param | Type | Description | +| ----- | ------ | ---------------------------------------------- | +| drive | string | Optional drive letter to get Windows UsnJrnl | +| path | string | Optional path to UsnJrnl file | +| mft | string | Optional path to MFT file for path resolutions | + +### logonsWindows(path, limit) -> Logons[] | WindowsError + +Parse the Windows Security.evtx and extract Logon and Logoff events. + +| Param | Type | Description | +| ----- | ------ | --------------------------------------------------------------------------------- | +| path | string | Path to Windows Security.evtx file | +| limit | number | How may EventLog entries to parse when streaming the evtx file. Default is 10,000 | + +### lookupSecurityKey(path, offset) -> SecurityKey | WindowsError + +Parse Security Key data from Registry at provided Security Key offset. The +offset must be a postive number greater than 0. You can use getRegistry(path) to +pull a list of keys which contain Security Key offset data. + +It is not recommended to bulk lookup Security Key info due the amount of data. +Security Keys contain information about Registry key permissions and ACLs. Its +not super useful. + +| Param | Type | Description | +| ------ | ------ | ----------------------------- | +| path | string | Path to Windows Registry file | +| offset | number | Offset to Security Key | + +### ESE Database Class + +A basic class to help interact and extract data from ESE databases + +#### catalogInfo() -> Catalog[] | WindowsError + +Dump the Catalog metadata associated with an ESE database. Returns an array of +Catalog entries or WindowsError + +#### tableInfo(catalog, table_name) -> TableInfo + +Extract table metadata from parsed Catalog entries based on provided table name + +| Param | Type | Description | +| ---------- | --------- | ------------------------ | +| catalog | Catalog[] | Array of Catalog entries | +| table_name | string | Name of table to extract | + +#### getPages(first_page) -> number[] | WindowsError + +Get an array of all pages associated with a table starting at the first page +provided. First page can be found in the TableInfo object. + +| Param | Type | Description | +| ---------- | ------ | --------------------- | +| first_page | number | First page of a table | + +#### getRows(pages, info) -> Record<string, EseTable[][]> | WindowsError + +Get rows associated with provided TableInfo object and number of pages. A +returns a `Record` or WindowsError. + +The table name is the Record string key. + +[EseTable](../../Artifacts/Windows%20Artfacts/ese.md) is an array of rows and +columns representing ESE data. + +| Param | Type | Description | +| ----- | --------- | ---------------- | +| pages | number[] | Array of pages | +| info | TableInfo | TableInfo object | + +#### getFilteredRows(pages, info, column_name, column_data) -> Record<string, EseTable[][]> | WindowsError + +Get rows and filter based on provided column_name and column_data. This function +can be useful if you want to get data from a table thats shares data with +another table. For example, if you call getRows() to get data associated with +TableA and now you want to get data from TableB and both tables share a unique +key. + +Its _a little_ similar to "select \* from tableB where columnX = Y" where Y is a +unique key + +| Param | Type | Description | +| ----------- | ----------------------------- | ------------------------------------------------------------------------------ | +| pages | number[] | Array of pages | +| info | TableInfo | TableInfo object | +| column_name | string | Column name that you want to filter on | +| column_data | Record<string, boolean> | HashMap of column values to filter on. Only the key is used to filter the data | + +#### dumpTableColumns(pages, info, column_names) -> Record<string, EseTable[][]> | WindowsError + +Get rows based on specific columns names. This function is the same as getRows() +except it will only return column names that included in column_names. + +| Param | Type | Description | +| ----------- | --------- | -------------------------------------- | +| pages | number[] | Array of pages | +| info | TableInfo | TableInfo object | +| column_name | string[] | Array of column names to get data from | + +### getChocolateyInfo(alt_base) -> ChocolateyInfo[] | WindowsError + +Return a list of installed Chocolatey packages. Will use the ChocolateyInstall +ENV value by default (C:\ProgramData\chocolatey). + +An optional alternative base path can also be provided + +| Param | Type | Description | +| -------- | ------ | --------------------------------- | +| alt_base | string | Optional base path for Chocolatey | + +### Updates class + +A simple class to help dump the contents of the Windows DataStore.edb database. +This class extends the EseDatabase class. + +#### updateHistory(pages) -> UpdateHistory[] | WindowsError + +Return a list of Windows Updates by parsing the Windows DataStore.edb database. + +| Param | Type | Description | +| ----- | -------- | ------------------------------- | +| pages | number[] | Array of pages to get data from | + +### powershellHistory(platform, alt_path) -> History[] | History | WindowsError + +Return PowerShell history entries for all users. Uses the system drive by +default. + +This artifact also supports PowerShell history on macOS or Linux +An optional alternative path to ConsoleHost_history.txt can also be provided +instead. + +| Param | Type | Description | +| -------- | ------------ | ------------------------------------------------------ | +| platform | PlatformType | Platform type to parse history for. Default is Windows | +| alt_path | string | Optional full path to ConsoleHost_history.txt | + +### parseMru(ntuser_path) -> Mru[] | WindowsError + +Parse common Most Recently Used (MRU) locations in the Registry. Currently +parses: OpenSave, LastVisited, and RecentDocs MRU keys + +| Param | Type | Description | +| ----------- | ------ | ---------------------------- | +| ntuser_path | string | Full path to NTUSER.DAT file | + +### getShellItem(data) -> JsShellItem | WindowsError + +Parse raw bytes that contain a ShellItem. Returns a JsShellItem that contains +ShellItem and any remaining bytes. This function can be used to parse multiple +shellitems. + +| Param | Type | Description | +| ----- | ---------- | ---------------------- | +| data | Uint8Array | Raw bytes of shellitem | + +### UserAccessLogging class + +A simple class to help extract data from the Windows User Access Log database. +This class extends the EseDatabase class + +#### getRoleIds(pages) -> RoleIds[] | WindowsError + +Return an array of RoleIds associated with UAL database. This function expects +the UserAccessLogging class to be initialized with the SystemIdentity.mdb +database otherwise it will return no results. + +| Param | Type | Description | +| ----- | -------- | ------------------------------- | +| pages | number[] | Array of pages to get data from | + +#### getUserAccessLog(pages, roles_ual, role_page_chunk) -> UserAccessLog[] | WindowsError + +Parse the User Access Log (UAL) database on Windows Servers. This database +contains logon information for users on the system.\ +It is **not** related to M365 UAL (Unified Audit Logging)! + +This function expects the UserAccessLogging class to be initialized with the +Current.mdb or `{GUID}.mdb` database otherwise it will return no results. + +You may provide an optional UserAccessLogging associated with SystemIdentity.mdb +to perform RoleID lookups. Otherwise this table will parse the Current.mdb or +`{GUID}.mdb` database. You may also customize the number of pages that should be +used when doing RoleID lookups, by default 30 pages will used. + +| Param | Type | Description | +| --------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| pages | number[] | Array of pages to get data from | +| roles_ual | UserAccessLogging | Optional UserAccessLogging object that was initialized with the file SystemIdentity.mdb. Can be used to perform RoleID lookups | +| role_page_chunk | number | Number of pages that should be submitted when doing RoleID lookups. By default 30 page chunks will be used to do lookup | + +### userAccessLog(alt_dir) -> UserAccessLog[] | WindowsError + +Parse the User Access Log (UAL) database on Windows Servers. This database +contains logon information for users on the system.\ +It is **not** related to M365 UAL (Unified Audit Logging)! + +By default it will parse the databases at %SYSTEMROOT%\System32\LogFiles\Sum. +However, you may provided an optional alternative path if you want. + +| Param | Type | Description | +| ------- | ------ | ------------------------------------------------------ | +| alt_dir | string | Alternative directory containing the UAL log databases | + +### getWmiPersist() -> WmiPersist[] | WindowsError + +Parse the WMI Repository and extract persistence information. + +### getWmiPersistPath(path) -> WmiPersist[] | WindowsError + +Parse the WMI Repository and extract persistence information at provided path. +The directory must contain: + +- MAPPING\*.MAP +- OBJECTS.DATA +- INDEX.BTR + +| Param | Type | Description | +| ----- | ------ | ---------------------- | +| path | string | Path to WMI Repository | + +### listUsbDevices(alt_file) -> UsbDevices[] | WindowsError + +Parse SYSTEM Registry to get list of USB devices that have been connected + +| Param | Type | Description | +| -------- | ------ | -------------------------------------------- | +| alt_file | string | Alternative path to the SYSTEM Registry file | + +### serviceInstalls(path) -> ServiceInstalls[] | WindowsError + +Parse Windows System.evtx file to extract Service Install events. + +| Param | Type | Description | +| ----- | ------ | ----------------------------------- | +| path | string | Option path to the System.evtx file | + +### Outlook Class + +A basic class to help interact and extract data from OST files. + +#### rootFolder() -> FolderInfo | WindowsError + +Returns the root folder in an OST file. Can be used to start walking through the +OST file + +#### readFolder(folder) -> FolderInfo | WindowsError + +Reads the provided folder ID. Returns the same object as `rootFolder()` +function. + +| Param | Type | Description | +| ------ | ------ | ----------- | +| folder | number | Folder ID | + +#### readMessages(table, offset, limit) -> MessageDetails[] | WindowsError + +Read messages in a folder. You can specify which message to start at with the +offset and how many the limit. Returns an array of read messages or an error. + +An offset of 0 means, start with the first message. By default artemis will +return only 50 messages. + +| Param | Type | Description | +| ------ | --------- | -------------------------------------------------------------------------- | +| table | TableInfo | Table structure associated with the folder. Obtained by readFolder() | +| offset | number | First message to read. A value of 0 means read the first message | +| limit | number | Optional limit to provide to the function. By default 50 messages are read | + +#### readAttachment(block_id, descriptor_id) -> Attachment | WindowsError + +Read and extract and attach from provided block and descriptor IDs. The IDs can +be obtained from the readMessages function. If there are no IDs in the +MessageDetails object then the message has no attachment + +| Param | Type | Description | +| ------------- | ------ | ---------------------------------------- | +| block_id | number | Block ID associated with attachment | +| descriptor_id | number | Descriptor ID associated with attachment | + +#### parseWordWheel(path) -> WordWheelEntry[] | WindowsError + +Reads the provided glob path and parses all NTUSER.DAT files looking for +WordWheel entries. + +| Param | Type | Description | +| ----- | ------ | -------------------------- | +| path | string | Glob to NTUSER.DAT file(s) | + +#### assembleScriptblocks(path) -> Scriptblock[] | WindowsError + +Parses the Windows Microsoft-Windows-PowerShell%4Operational.evtx file and reassembles PowerShell Scriptblocks. +You may provided an optional alternative path to Microsoft-Windows-PowerShell%4Operational.evtx. + +| Param | Type | Description | +| ----- | ------ | --------------------------------------------------------------------------- | +| path | string | Optional alternative path to Microsoft-Windows-PowerShell%4Operational.evtx | + +#### firwallRules(path) -> FirewallRules[] | WindowsError + +Extract Windows Firewall rules from the SYSTEM Registry file. By default artemis will use the SYSTEM Registry on the system drive. +You may provide an optional alternative SYSTEM file. + +| Param | Type | Description | +| ----- | ------ | ------------------------------------------------- | +| path | string | Optional alternative path to System Registry file | + +#### processTreeEventLogs(path) -> EventLogProcessTree[] | WindowsError + +Parses the Windows Security.evtx file and attempts to construct process trees for 4688 events. +You may provided an optional alternative path to Security.evtx. + +| Param | Type | Description | +| ----- | ------ | ------------------------------------------ | +| path | string | Optional alternative path to Security.evtx | + +#### wifiNetworksWindows(path) -> Wifi[] | WindowsError + +Parses the Windows SOFTWARE Registry file and attempts extract WiFi networks connected to. +You may provided an optional alternative path to the SOFTWARE Registry file. + +| Param | Type | Description | +| ----- | ------ | ------------------------------------------ | +| path | string | Optional alternative path to Security.evtx | + +### ADCertificates Class + +A basic class to help interact and extract data from AD Certificates ESE databases. + +Sample TypeScript code: +```typescript +import { ADCertificates } from "./artemis-api/mod"; +import { WindowsError } from "./artemis-api/src/windows/errors"; + +function main() { + // You may also provide an optional alternative path to the ESE database + const client = new ADCertificates(); + const catalog = client.catalogInfo(); + if (catalog instanceof WindowsError) { + return; + } + const first_page = client.tableInfo(catalog, "Certificates").table_page; + const cert_pages = client.getPages(first_page); + if (cert_pages instanceof WindowsError) { + return; + } + // Depending on size of EDB file you should customize the number of pages you use + // The larger the pages array the more memory required + // 100 pages should be a reasonable default + const certs = client.getCertificates(cert_pages, client.tableInfo(catalog, "Certificates")) + console.log(JSON.stringify(certs)); +} + +main(); +``` + +#### getCertificates(pages, info) -> Certificates[] | WindowsError + +Get list of certificates from the database. + +| Param | Type | Description | +| ----- | --------- | --------------------------------------------------------- | +| pages | number[] | Number of pages to parse from the ESE database | +| info | TableInfo | Table info object that can be obtained with the ESE class | + + +#### getRequests(pages, info) -> Requests[] | WindowsError + +Get list of requests from the database. + +| Param | Type | Description | +| ----- | --------- | --------------------------------------------------------- | +| pages | number[] | Number of pages to parse from the ESE database | +| info | TableInfo | Table info object that can be obtained with the ESE class | + +#### requestAttributes(pages, info) -> RequestAttributes[] | WindowsError + +Get list of requests attributes from the database. + +| Param | Type | Description | +| ----- | --------- | --------------------------------------------------------- | +| pages | number[] | Number of pages to parse from the ESE database | +| info | TableInfo | Table info object that can be obtained with the ESE class | + + +### getRunKeys(path) -> RegistryRunKey[] + +Parse Windows Registry files and extract Run key entries. You may provide an optional path to a Registry file. By default all user NTUSER.DAT and SOFTWARE files are parsed + +| Param | Type | Description | +| ------- | ------- | -------------------------------- | +| path | string | Optional path to a Registry file | + + +### rdpLogons(path) -> RegistryRunKey[] + +Parse Windows RDP logons. You may provide an optional path to a Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx file. By default artemis will check the system drive volume for the Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx. + +Typically this will be C:\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx + +| Param | Type | Description | +| ------- | ------- | ---------------------------------------------------------------------------------------------- | +| path | string | Optional path to Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx file | + +### parsePca(alt_dir) -> ProgramCompatibilityAssist[] | WindowsError + +Parse Windows Program Compatibility Assistant (PCA) files. You may provide an optional alternative glob to a folder that contains the PCA files + +| Param | Type | Description | +| ---------- | ------- | -------------------------------------------- | +| alt_dir | string | Optional glob to folder containing PCA files | + + +### defenderQuarantineEventLog(path, limit) -> EventLogDefenderQuarantine[] | WindowsError + +Parse Windows Defender Quarantine events. You may provide an optional path to a Microsoft-Windows-Windows Defender%4Operational.evtx file. By default artemis will check the system drive volume for the Microsoft-Windows-Windows Defender%4Operational.evtx. + +Typically this will be C:\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-Windows Defender%4Operational.evtx + +| Param | Type | Description | +| ------- | ------- | -------------------------------------------------------------------------- | +| path | string | Optional path to Microsoft-Windows-Windows Defender%4Operational.evtx file | +| limit | number | Optional limit to set when streaming the EventLogs | + +### msiInstalled(path, limit) -> MsiInstalled[] + +Parse Windows MSI Installer events. You may provide an optional path to a Application.evtx file. By default artemis will check the system drive volume for the Application.evtx. + +Typically this will be C:\\Windows\\System32\\winevt\\Logs\\Application.evtx + +| Param | Type | Description | +| ------- | ------- | -------------------------------------------------- | +| path | string | Optional path to Application.evtx file | +| limit | number | Optional limit to set when streaming the EventLogs | + +### bitsEvents(alt_file, limit) -> BitsEvent[] | WindowsError + +Parse a Windows BITS EventLog file and try to extract data. + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------------------------ | +| alt_file | string | Optional path to an Windows BITS EventLog file | +| limit | number | Optional EventLog limit to use when iterating through the EventLog | + +### veloCommands(alt_file, limit) -> BitsEvent[] | WindowsError + +Extract Velociraptor commands from the Windows Application.evtx EventLog file. + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------------------------------------ | +| alt_file | string | Optional path to an Windows Application.evtx EventLog file | +| limit | number | Optional EventLog limit to use when iterating through the EventLog | + +### crashEvents(alt_file, limit) -> CrashEvent[] | WindowsError + +Parse a Windows Crash EventLog file and try to extract data. + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------------------------ | +| alt_file | string | Optional path to an Windows EventLog file | +| limit | number | Optional EventLog limit to use when iterating through the EventLog | + +### extractAppCrashes(alt_path) -> AppCrash[] | WindowsError + +Parse a Windows Application Crash Report.wer files and try to extract data. + +| Param | Type | Description | +| -------------- | ------- | ------------------------------------------------------------------------------- | +| alt_path | string | Optional glob to directory containing Report.wer files | diff --git a/artemis-docs/docs/API/Helper/filesystem.md b/artemis-docs/docs/API/Helper/filesystem.md index 12cb6bd9..b5a6c6b0 100644 --- a/artemis-docs/docs/API/Helper/filesystem.md +++ b/artemis-docs/docs/API/Helper/filesystem.md @@ -1,81 +1,125 @@ ---- -description: Interacting with the Filesystem ---- - -# Filesystem APIs - -The artemis API contains several functions that can be used to interact with the -filesystem. - -### stat(path) -> FileInfo | FileError - -Return basic metadata about a file or directory - -| Param | Type | Description | -| ----- | ------ | --------------------------------------- | -| path | string | File or directory to get metadata about | - -### hash(path, md5, sha1, sha256) -> Hashes | FileError - -Return hashes for a single file - -| Param | Type | Description | -| ------ | ------- | --------------------- | -| path | string | File to hash | -| md5 | boolean | Enable MD5 hashing | -| sha1 | boolean | Enable SHA1 hashing | -| sha256 | boolean | Enable SHA256 hashing | - -### readTextFile(path) -> string | FileError - -Read a text file. Currently only files less than 2GB in size can be read - -| Param | Type | Description | -| ----- | ------ | ----------------- | -| path | string | Text file to read | - -### readFile(path) -> Uint8Array | FileError - -Read a file using regular OS APIs. Currently only files less than 2GB in size -can be read - -| Param | Type | Description | -| ----- | ------ | ------------ | -| path | string | File to read | - -### glob(pattern) -> GlobInfo[] | FileError - -Parse glob patterns based on Rust [glob](https://docs.rs/glob/latest/glob/) -support - -| Param | Type | Description | -| ------- | ------ | ---------------------------------------------------------------------------------------- | -| pattern | string | Glob pattern to parse. Ex: C:\\* to get all files and directories at Windows C directory | - -### readDir(path) -> Promise<FileInfo[]> | FileError - -Read a provided directory and get list of files. This function is async! - -| Param | Type | Description | -| ----- | ------ | ----------------- | -| path | string | Directory to read | - -### acquireFile(path, output) -> boolean | FileError - -Acquire a local file using OS APIs. Supports copying the file to local location -or uploading to cloud. - -| Param | Type | Description | -| ------ | ------ | ----------------- | -| path | string | Directory to read | -| output | Output | Output object | - -### readLines(path, offset, limit) -> string[] | FileError - -Read lines from a text file. You may provide an offset to specific line and limit the number of lines to read. - -| Param | Type | Description | -| ------ | ------- | --------------------------------------------------------------------- | -| path | string | Text file to read | -| offset | number | Line to start reading file at. Must be positive number. Default is 0. | -| limit | number | How many lines to read. Must be positive number. Default is 100. | \ No newline at end of file +--- +description: Interacting with the Filesystem +--- + +# Filesystem APIs + +The artemis API contains several functions that can be used to interact with the +filesystem. + +### stat(path) -> FileInfo | FileError + +Return basic metadata about a file or directory + +| Param | Type | Description | +| ----- | ------ | --------------------------------------- | +| path | string | File or directory to get metadata about | + +### hash(path, md5, sha1, sha256) -> Hashes | FileError + +Return hashes for a single file + +| Param | Type | Description | +| ------ | ------- | --------------------- | +| path | string | File to hash | +| md5 | boolean | Enable MD5 hashing | +| sha1 | boolean | Enable SHA1 hashing | +| sha256 | boolean | Enable SHA256 hashing | + +### readTextFile(path) -> string | FileError + +Read a text file. Currently only files less than 2GB in size can be read + +| Param | Type | Description | +| ----- | ------ | ----------------- | +| path | string | Text file to read | + +### readFile(path) -> Uint8Array | FileError + +Read a file using regular OS APIs. Currently only files less than 2GB in size +can be read + +| Param | Type | Description | +| ----- | ------ | ------------ | +| path | string | File to read | + +### glob(pattern) -> GlobInfo[] | FileError + +Parse glob patterns based on Rust [glob](https://docs.rs/glob/latest/glob/) +support + +| Param | Type | Description | +| ------- | ------ | ---------------------------------------------------------------------------------------- | +| pattern | string | Glob pattern to parse. Ex: C:\\* to get all files and directories at Windows C directory | + +### readDir(path) -> Promise<FileInfo[]> | FileError + +Read a provided directory and get list of files. This function is async! + +| Param | Type | Description | +| ----- | ------ | ----------------- | +| path | string | Directory to read | + +### acquireFile(path, output) -> boolean | FileError + +Acquire a local file using OS APIs. Supports copying the file to local location +or uploading to cloud. + +| Param | Type | Description | +| ------ | ------ | ----------------- | +| path | string | Directory to read | +| output | Output | Output object | + +### readLines(path, offset, limit) -> string[] | FileError + +Read lines from a text file. You may provide an offset to specific line and limit the number of lines to read. + +| Param | Type | Description | +| ------ | ------- | --------------------------------------------------------------------- | +| path | string | Text file to read | +| offset | number | Line to start reading file at. Must be positive number. Default is 0. | +| limit | number | How many lines to read. Must be positive number. Default is 100. | + + +### BufReader class + +A basic TypeScript class that lets you open and and read parts of a file. + +Sample TypeScript code: +```typescript +import { FileError } from "./artemis-api/src/filesystem/errors"; +import { BufReader } from "./artemis-api/src/filesystem/reader"; + +function main() { + const reader = new BufReader("C:\\Windows\\explorer.exe"); + const bytes = reader.readBytes(0, 25); + if(bytes instanceof FileError) { + return; + } + const array = Array.from(bytes); + if(array.length !== 25) { + throw "bad length"; + } + + console.log(`I used the Rust BufReader to read the first 25 bytes of explorer.exe! ${array}`); + const middle = reader.readBytes(1000, 50); + if(middle instanceof FileError) { + return; + } + + console.log(`I used the Rust BufReader to read 50 bytes of explorer.exe at offset 1000! I did not read the entire file into memory! ${array}`); + + return Array.from(bytes); +} + +main(); +``` + +#### readBytes(offset, bytes) -> Uint8Array | FileError + +Read bytes from a file at provided offset + +| Param | Type | Description | +| ------- | ------ | ------------------------------------- | +| offset | number | Starting offset when reading the file | +| bytes | number | How many bytes to read | \ No newline at end of file diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/_category_.json b/artemis-docs/docs/Artifacts/Application Artifacts/_category_.json index 6646636a..4bf8ae22 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/_category_.json +++ b/artemis-docs/docs/Artifacts/Application Artifacts/_category_.json @@ -1,6 +1,6 @@ { "label": "Application Artifacts", - "position": 8, + "position": 9, "link": { "type": "generated-index", "description": "Forensic artifacts for specific applications" diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/anydesk.md b/artemis-docs/docs/Artifacts/Application Artifacts/anydesk.md index b9659171..8bc3a1ed 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/anydesk.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/anydesk.md @@ -1,76 +1,76 @@ ---- -description: AnyDesk Remote Access Tool -keywords: - - remote access tool - - remote monitoring and management ---- - -# AnyDesk - -AnyDesk is a popular remote access tool to connect to remote systems. -Artemis supports parsing several files related to AnyDesk. - -- Trace log files -- User config -- System config - -Other parsers: - -- Any program that can read a text file - -# References - -- [RATs Review](https://www.synacktiv.com/publications/legitimate-rats-a-comprehensive-forensic-analysis-of-the-usual-suspects#anydesk) -- [Suspicious AnyDesk Use](https://www.cybertriage.com/blog/dfir-next-steps-suspicious-anydesk-use/) - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect AnyDesk data - -```typescript -import { AnyDesk, PlatformType } from "../artemis-api/mod"; - -function main() { - console.log('Running AnyDesk tests....'); - const results = new AnyDesk(PlatformType.Linux, "./test_data/anydesk"); - const used_alt_dir = true; - const hits = results.traceFiles(used_alt_dir); - if (hits.length !== 2872) { - throw `Got ${hits.length} rows. Expected 2872`; - } - - console.log('All AnyDesk tests passed! 🥳💃🕺'); -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -/** - * Object representing a Trace log entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface TraceEntry { - message: string; - datetime: string; - timestamp_desc: "Trace Entry"; - artifact: "AnyDesk Trace Log"; - data_type: "applications:anydesk:trace:entry"; - path: string; - level: string; - entry_timestamp: string; - component: string; - code_function: string; - pid: number; - ppid: number; - subfunction: string; - log_message: string; - account: string; - version: string; - id: string; -} -``` +--- +description: AnyDesk Remote Access Tool +keywords: + - remote access tool + - remote monitoring and management +--- + +# AnyDesk + +AnyDesk is a popular remote access tool to connect to remote systems. +Artemis supports parsing several files related to AnyDesk. + +- Trace log files +- User config +- System config + +Other parsers: + +- Any program that can read a text file + +# References + +- [RATs Review](https://www.synacktiv.com/publications/legitimate-rats-a-comprehensive-forensic-analysis-of-the-usual-suspects#anydesk) +- [Suspicious AnyDesk Use](https://www.cybertriage.com/blog/dfir-next-steps-suspicious-anydesk-use/) + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect AnyDesk data + +```typescript +import { AnyDesk, PlatformType } from "../artemis-api/mod"; + +function main() { + console.log('Running AnyDesk tests....'); + const results = new AnyDesk(PlatformType.Linux, "./test_data/anydesk"); + const used_alt_dir = true; + const hits = results.traceFiles(used_alt_dir); + if (hits.length !== 2872) { + throw `Got ${hits.length} rows. Expected 2872`; + } + + console.log('All AnyDesk tests passed! 🥳💃🕺'); +} + +main(); +``` + +## Output Structure + +Array of TraceEntry + +```typescript +/** + * Object representing a Trace log entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface TraceEntry { + message: string; + datetime: string; + timestamp_desc: "Trace Entry"; + artifact: "AnyDesk Trace Log"; + data_type: "applications:anydesk:trace:entry"; + path: string; + level: string; + entry_timestamp: string; + component: string; + code_function: string; + pid: number; + ppid: number; + subfunction: string; + log_message: string; + account: string; + version: string; + id: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/brave.md b/artemis-docs/docs/Artifacts/Application Artifacts/brave.md index 5c44971d..fb6c5318 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/brave.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/brave.md @@ -1,366 +1,367 @@ ---- -description: Brave browser -keywords: - - browser ---- - -# Brave - -Brave is an open source web browser created and maintained by Brave Software. - -Artemis supports parsing the list of artifacts below: - -- History -- Downloads -- Cookies -- Autofill -- Bookmarks -- Login Data -- Extensions -- Preferences -- Detect Incidental Party State (DIPS) -- Local Storage -- Browser sessions -- Favicons -- Shortcuts -- Retrospect - A powerful capability that timelines all artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) - - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Brave data. -Since Brave is based on Chromium, many of the Brave artifacts will be identical to Chromium - - -## Sample API Script - -```typescript -import { Brave } from "./artemis-api/mod"; -import { PlatformType } from "./artemis-api/src/system/systeminfo"; - -function main() { - const client = new Brave(PlatformType.Darwin); - console.log(JSON.stringify(client.cookies())); -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -/** - * An interface representing the Chromium SQLITE tables: `urls` and `visits` - */ -export interface ChromiumHistory { - /**Row ID value */ - id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**Page visit count */ - visit_count: number; - /**Typed count value */ - typed_count: number; - /**Last visit time*/ - last_visit_time: string; - /**Hidden value */ - hidden: number; - /**Visits ID value */ - visits_id: number; - /**From visit value */ - from_visit: number; - /**Transition value */ - transition: number; - /**Segment ID value */ - segment_id: number; - /**Visit duration value */ - visit_duration: number; - /**Opener visit value */ - opener_visit: number; - unfold: Url | undefined; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: string; - browser: BrowserType; -} - -/** - * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` - */ -export interface ChromiumDownloads { - /**Row ID */ - id: number; - /**GUID for download */ - guid: string; - /**Path to download */ - current_path: string; - /**Target path to download */ - target_path: string; - /**Download start time */ - start_time: string; - /**Bytes downloaded */ - received_bytes: number; - /**Total bytes downloaded */ - total_bytes: number; - /**State value */ - state: number; - /**Danger type value */ - danger_type: number; - /**Interrupt reason value */ - interrupt_reason: number; - /**Raw byte hash value */ - hash: number[]; - /**Download end time */ - end_time: string; - /**Opened value */ - opened: number; - /**Last access time */ - last_access_time: string; - /**Transient value */ - transient: number; - /**Referer URL */ - referrer: string; - /**Download source URL */ - site_url: string; - /**Tab URL */ - tab_url: string; - /**Tab referrer URL */ - tab_referrer_url: string; - /**HTTP method used */ - http_method: string; - /**By ext ID value */ - by_ext_id: string; - /**By ext name value */ - by_ext_name: string; - /**Etag value */ - etag: string; - /**Last modified time */ - last_modified: string; - /**MIME type value */ - mime_type: string; - /**Original mime type value */ - original_mime_type: string; - /**Downloads URL chain ID value */ - downloads_url_chain_id: number; - /**Chain index value */ - chain_index: number; - /**URL for download */ - url: string; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumCookies { - creation: string; - host_key: string; - top_frame_site_key: string; - name: string; - value: string; - /**This value is currently Base64 encoded */ - encrypted_value: string; - path: string; - expires: string; - is_secure: boolean; - is_httponly: boolean; - last_access: string; - has_expires: boolean; - is_persistent: boolean; - priority: number; - samesite: number; - source_scheme: number; - source_port: number; - is_same_party: number; - last_update: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumAutofill { - name?: string; - value?: string; - value_lower?: string; - date_created: string; - date_last_used: string; - /**Default is 1 */ - count: number; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Autofill Created"; - artifact: "Website Autofill"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumBookmarks { - date_added: string; - date_last_used: string; - guid: string; - id: number; - name: string; - type: string; - url: string; - meta_info: Record; - bookmark_type: BookmarkType; - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Bookmark Added"; - artifact: "Browser Bookmark"; - data_type: string; - browser: BrowserType; -} - -export enum BookmarkType { - Bar = "Bookmark Bar", - Sync = "Synced", - Other = "Other", - Unknown = "Unknown", -} - -export interface ChromiumLogins { - origin_url: string; - action_url?: string; - username_element?: string; - username_value?: string; - password_element?: string; - password_value?: string; - submit_element?: string; - signon_realm: string; - date_created: string; - blacklisted_by_user: number; - scheme: number; - password_type?: number; - times_used?: number; - form_data?: string; - display_name?: string; - icon_url?: string; - federation_url?: string; - skip_zero_click?: number; - generation_upload_status?: number; - possible_username_pairs?: string; - id: number; - date_last_used: string; - moving_blocked_for?: string; - date_password_modified: string; - sender_email?: string; - sender_name?: string; - date_received?: string; - sharing_notification_display: number; - keychain_identifier?: string; - sender_profile_image_url?: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Last Login"; - artifact: "Website Login"; - data_type: string; - browser: BrowserType; -} - -/** - * Detect Incidental Party State (DIPS) collects metrics on websites - */ -export interface ChromiumDips { - site: string; - first_site_storage?: string | null; - last_site_storage?: string | null; - first_user_interaction?: string | null; - last_user_interaction?: string | null; - first_stateful_bounce?: string | null; - last_stateful_bounce?: string | null; - first_bounce?: string | null; - last_bounce?: string | null; - first_web_authn_assertion: string | null; - last_web_authn_assertion: string | null; - /**Path to DIPS database */ - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "First Interaction"; - artifact: "Browser DIPS"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumProfiles { - full_path: string; - version: string; - browser: BrowserType; -} - -export enum ChromiumCookieType { - Unknown = "Unknown", - Http = "HTTP", - Script = "Script", - Other = "Other", -} - -/** - * Object representing a Local Storage LevelDb entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface ChromiumLocalStorage extends LevelDbEntry { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; - artifact: "Level Database"; - data_type: "applications:leveldb:entry"; -} - -export interface ChromiumSession { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Active"; - artifact: "Browser Session"; - data_type: string; - session_id: string; - last_active: string; - url: string; - title: string; - session_type: SessionType; - path: string; -} - -export enum SessionType { - Session = "Session", - Tab = "Tab", -} -``` +--- +description: Brave browser +keywords: + - browser +--- + +# Brave + +Brave is an open source web browser created and maintained by Brave Software. + +Artemis supports parsing the list of artifacts below: + +- History +- Downloads +- Cookies +- Autofill +- Bookmarks +- Login Data +- Extensions +- Preferences +- Detect Incidental Party State (DIPS) +- Local Storage +- Browser sessions +- Favicons +- Shortcuts +- Cache +- Retrospect - A powerful capability that timelines all browser artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) + + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Brave data. +Since Brave is based on Chromium, many of the Brave artifacts will be identical to Chromium + + +## Sample API Script + +```typescript +import { Brave } from "./artemis-api/mod"; +import { PlatformType } from "./artemis-api/src/system/systeminfo"; + +function main() { + const client = new Brave(PlatformType.Darwin); + console.log(JSON.stringify(client.cookies())); +} + +main(); +``` + +## Output Structure + +Dependent on browser artifact user wants to parse. + +```typescript +/** + * An interface representing the Chromium SQLITE tables: `urls` and `visits` + */ +export interface ChromiumHistory { + /**Row ID value */ + id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**Page visit count */ + visit_count: number; + /**Typed count value */ + typed_count: number; + /**Last visit time*/ + last_visit_time: string; + /**Hidden value */ + hidden: number; + /**Visits ID value */ + visits_id: number; + /**From visit value */ + from_visit: number; + /**Transition value */ + transition: number; + /**Segment ID value */ + segment_id: number; + /**Visit duration value */ + visit_duration: number; + /**Opener visit value */ + opener_visit: number; + unfold: Url | undefined; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: string; + browser: BrowserType; +} + +/** + * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` + */ +export interface ChromiumDownloads { + /**Row ID */ + id: number; + /**GUID for download */ + guid: string; + /**Path to download */ + current_path: string; + /**Target path to download */ + target_path: string; + /**Download start time */ + start_time: string; + /**Bytes downloaded */ + received_bytes: number; + /**Total bytes downloaded */ + total_bytes: number; + /**State value */ + state: number; + /**Danger type value */ + danger_type: number; + /**Interrupt reason value */ + interrupt_reason: number; + /**Raw byte hash value */ + hash: number[]; + /**Download end time */ + end_time: string; + /**Opened value */ + opened: number; + /**Last access time */ + last_access_time: string; + /**Transient value */ + transient: number; + /**Referrer URL */ + referrer: string; + /**Download source URL */ + site_url: string; + /**Tab URL */ + tab_url: string; + /**Tab referrer URL */ + tab_referrer_url: string; + /**HTTP method used */ + http_method: string; + /**By ext ID value */ + by_ext_id: string; + /**By ext name value */ + by_ext_name: string; + /**Etag value */ + etag: string; + /**Last modified time */ + last_modified: string; + /**MIME type value */ + mime_type: string; + /**Original mime type value */ + original_mime_type: string; + /**Downloads URL chain ID value */ + downloads_url_chain_id: number; + /**Chain index value */ + chain_index: number; + /**URL for download */ + url: string; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumCookies { + creation: string; + host_key: string; + top_frame_site_key: string; + name: string; + value: string; + /**This value is currently Base64 encoded */ + encrypted_value: string; + path: string; + expires: string; + is_secure: boolean; + is_httponly: boolean; + last_access: string; + has_expires: boolean; + is_persistent: boolean; + priority: number; + samesite: number; + source_scheme: number; + source_port: number; + is_same_party: number; + last_update: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumAutofill { + name?: string; + value?: string; + value_lower?: string; + date_created: string; + date_last_used: string; + /**Default is 1 */ + count: number; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Autofill Created"; + artifact: "Website Autofill"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumBookmarks { + date_added: string; + date_last_used: string; + guid: string; + id: number; + name: string; + type: string; + url: string; + meta_info: Record; + bookmark_type: BookmarkType; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Bookmark Added"; + artifact: "Browser Bookmark"; + data_type: string; + browser: BrowserType; +} + +export enum BookmarkType { + Bar = "Bookmark Bar", + Sync = "Synced", + Other = "Other", + Unknown = "Unknown", +} + +export interface ChromiumLogins { + origin_url: string; + action_url?: string; + username_element?: string; + username_value?: string; + password_element?: string; + password_value?: string; + submit_element?: string; + signon_realm: string; + date_created: string; + blacklisted_by_user: number; + scheme: number; + password_type?: number; + times_used?: number; + form_data?: string; + display_name?: string; + icon_url?: string; + federation_url?: string; + skip_zero_click?: number; + generation_upload_status?: number; + possible_username_pairs?: string; + id: number; + date_last_used: string; + moving_blocked_for?: string; + date_password_modified: string; + sender_email?: string; + sender_name?: string; + date_received?: string; + sharing_notification_display: number; + keychain_identifier?: string; + sender_profile_image_url?: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Last Login"; + artifact: "Website Login"; + data_type: string; + browser: BrowserType; +} + +/** + * Detect Incidental Party State (DIPS) collects metrics on websites + */ +export interface ChromiumDips { + site: string; + first_site_storage?: string | null; + last_site_storage?: string | null; + first_user_interaction?: string | null; + last_user_interaction?: string | null; + first_stateful_bounce?: string | null; + last_stateful_bounce?: string | null; + first_bounce?: string | null; + last_bounce?: string | null; + first_web_authn_assertion: string | null; + last_web_authn_assertion: string | null; + /**Path to DIPS database */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "First Interaction"; + artifact: "Browser DIPS"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumProfiles { + full_path: string; + version: string; + browser: BrowserType; +} + +export enum ChromiumCookieType { + Unknown = "Unknown", + Http = "HTTP", + Script = "Script", + Other = "Other", +} + +/** + * Object representing a Local Storage LevelDb entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface ChromiumLocalStorage extends LevelDbEntry { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; + artifact: "Level Database"; + data_type: "applications:leveldb:entry"; +} + +export interface ChromiumSession { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Active"; + artifact: "Browser Session"; + data_type: string; + session_id: string; + last_active: string; + url: string; + title: string; + session_type: SessionType; + evidence: string; +} + +export enum SessionType { + Session = "Session", + Tab = "Tab", +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/chrome.md b/artemis-docs/docs/Artifacts/Application Artifacts/chrome.md index 8b26fdf6..1817dd6e 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/chrome.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/chrome.md @@ -1,373 +1,374 @@ ---- -description: The most popular browser -keywords: - - browser - - google ---- - -# Chrome - -Chrome is a popular web browser created and maintained by Google. - -Artemis supports parsing the list of artifacts below: - -- History -- Downloads -- Cookies -- Autofill -- Bookmarks -- Login Data -- Extensions -- Preferences -- Detect Incidental Party State (DIPS) -- Local Storage -- Browser sessions -- Favicons -- Shortcuts -- Retrospect - A powerful capability that timelines all artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) - - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Chrome data. -Since Chrome is based on Chromium, many of the Chrome artifacts will be identical to Chromium - - -## Sample API Script - -```typescript -import { Chrome } from "./artemis-api/mod"; -import { PlatformType } from "./artemis-api/src/system/systeminfo"; - -function main() { - const client = new Chrome(PlatformType.Darwin); - console.log(JSON.stringify(client.cookies())); -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -/** - * An interface representing the Chromium SQLITE tables: `urls` and `visits` - */ -export interface ChromiumHistory { - /**Row ID value */ - id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**Page visit count */ - visit_count: number; - /**Typed count value */ - typed_count: number; - /**Last visit time*/ - last_visit_time: string; - /**Hidden value */ - hidden: number; - /**Visits ID value */ - visits_id: number; - /**From visit value */ - from_visit: number; - /**Transition value */ - transition: number; - /**Segment ID value */ - segment_id: number; - /**Visit duration value */ - visit_duration: number; - /**Opener visit value */ - opener_visit: number; - unfold: Url | undefined; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: string; - browser: BrowserType; -} - -/** - * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` - */ -export interface ChromiumDownloads { - /**Row ID */ - id: number; - /**GUID for download */ - guid: string; - /**Path to download */ - current_path: string; - /**Target path to download */ - target_path: string; - /**Download start time */ - start_time: string; - /**Bytes downloaded */ - received_bytes: number; - /**Total bytes downloaded */ - total_bytes: number; - /**State value */ - state: number; - /**Danger type value */ - danger_type: number; - /**Interrupt reason value */ - interrupt_reason: number; - /**Raw byte hash value */ - hash: number[]; - /**Download end time */ - end_time: string; - /**Opened value */ - opened: number; - /**Last access time */ - last_access_time: string; - /**Transient value */ - transient: number; - /**Referer URL */ - referrer: string; - /**Download source URL */ - site_url: string; - /**Tab URL */ - tab_url: string; - /**Tab referrer URL */ - tab_referrer_url: string; - /**HTTP method used */ - http_method: string; - /**By ext ID value */ - by_ext_id: string; - /**By ext name value */ - by_ext_name: string; - /**Etag value */ - etag: string; - /**Last modified time */ - last_modified: string; - /**MIME type value */ - mime_type: string; - /**Original mime type value */ - original_mime_type: string; - /**Downloads URL chain ID value */ - downloads_url_chain_id: number; - /**Chain index value */ - chain_index: number; - /**URL for download */ - url: string; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumCookies { - creation: string; - host_key: string; - top_frame_site_key: string; - name: string; - value: string; - /**This value is currently Base64 encoded */ - encrypted_value: string; - path: string; - expires: string; - is_secure: boolean; - is_httponly: boolean; - last_access: string; - has_expires: boolean; - is_persistent: boolean; - priority: number; - samesite: number; - source_scheme: number; - source_port: number; - is_same_party: number; - last_update: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumAutofill { - name?: string; - value?: string; - value_lower?: string; - date_created: string; - date_last_used: string; - /**Default is 1 */ - count: number; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Autofill Created"; - artifact: "Website Autofill"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumBookmarks { - date_added: string; - date_last_used: string; - guid: string; - id: number; - name: string; - type: string; - url: string; - meta_info: Record; - bookmark_type: BookmarkType; - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Bookmark Added"; - artifact: "Browser Bookmark"; - data_type: string; - browser: BrowserType; -} - -export enum BookmarkType { - Bar = "Bookmark Bar", - Sync = "Synced", - Other = "Other", - Unknown = "Unknown", -} - -export interface ChromiumLogins { - origin_url: string; - action_url?: string; - username_element?: string; - username_value?: string; - password_element?: string; - password_value?: string; - submit_element?: string; - signon_realm: string; - date_created: string; - blacklisted_by_user: number; - scheme: number; - password_type?: number; - times_used?: number; - form_data?: string; - display_name?: string; - icon_url?: string; - federation_url?: string; - skip_zero_click?: number; - generation_upload_status?: number; - possible_username_pairs?: string; - id: number; - date_last_used: string; - moving_blocked_for?: string; - date_password_modified: string; - sender_email?: string; - sender_name?: string; - date_received?: string; - sharing_notification_display: number; - keychain_identifier?: string; - sender_profile_image_url?: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Last Login"; - artifact: "Website Login"; - data_type: string; - browser: BrowserType; -} - -/** - * Detect Incidental Party State (DIPS) collects metrics on websites - */ -export interface ChromiumDips { - site: string; - first_site_storage?: string | null; - last_site_storage?: string | null; - first_user_interaction?: string | null; - last_user_interaction?: string | null; - first_stateful_bounce?: string | null; - last_stateful_bounce?: string | null; - first_bounce?: string | null; - last_bounce?: string | null; - first_web_authn_assertion: string | null; - last_web_authn_assertion: string | null; - /**Path to DIPS database */ - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "First Interaction"; - artifact: "Browser DIPS"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumProfiles { - full_path: string; - version: string; - browser: BrowserType; -} - -export enum BrowserType { - CHROME = "Google Chrome", - EDGE = "Microsoft Edge", - CHROMIUM = "Google Chromium" -} - -export enum ChromiumCookieType { - Unknown = "Unknown", - Http = "HTTP", - Script = "Script", - Other = "Other", -} - -/** - * Object representing a Local Storage LevelDb entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface ChromiumLocalStorage extends LevelDbEntry { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; - artifact: "Level Database"; - data_type: "applications:leveldb:entry"; -} - -export interface ChromiumSession { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Active"; - artifact: "Browser Session"; - data_type: string; - session_id: string; - last_active: string; - url: string; - title: string; - session_type: SessionType; - path: string; -} - -export enum SessionType { - Session = "Session", - Tab = "Tab", -} -``` +--- +description: The most popular browser +keywords: + - browser + - google +--- + +# Chrome + +Chrome is a popular web browser created and maintained by Google. + +Artemis supports parsing the list of artifacts below: + +- History +- Downloads +- Cookies +- Autofill +- Bookmarks +- Login Data +- Extensions +- Preferences +- Detect Incidental Party State (DIPS) +- Local Storage +- Browser sessions +- Favicons +- Shortcuts +- Cache +- Retrospect - A powerful capability that timelines all browser artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) + + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Chrome data. +Since Chrome is based on Chromium, many of the Chrome artifacts will be identical to Chromium + + +## Sample API Script + +```typescript +import { Chrome } from "./artemis-api/mod"; +import { PlatformType } from "./artemis-api/src/system/systeminfo"; + +function main() { + const client = new Chrome(PlatformType.Darwin); + console.log(JSON.stringify(client.cookies())); +} + +main(); +``` + +## Output Structure + +Dependent on browser artifact user wants to parse. + +```typescript +/** + * An interface representing the Chromium SQLITE tables: `urls` and `visits` + */ +export interface ChromiumHistory { + /**Row ID value */ + id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**Page visit count */ + visit_count: number; + /**Typed count value */ + typed_count: number; + /**Last visit time*/ + last_visit_time: string; + /**Hidden value */ + hidden: number; + /**Visits ID value */ + visits_id: number; + /**From visit value */ + from_visit: number; + /**Transition value */ + transition: number; + /**Segment ID value */ + segment_id: number; + /**Visit duration value */ + visit_duration: number; + /**Opener visit value */ + opener_visit: number; + unfold: Url | undefined; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: string; + browser: BrowserType; +} + +/** + * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` + */ +export interface ChromiumDownloads { + /**Row ID */ + id: number; + /**GUID for download */ + guid: string; + /**Path to download */ + current_path: string; + /**Target path to download */ + target_path: string; + /**Download start time */ + start_time: string; + /**Bytes downloaded */ + received_bytes: number; + /**Total bytes downloaded */ + total_bytes: number; + /**State value */ + state: number; + /**Danger type value */ + danger_type: number; + /**Interrupt reason value */ + interrupt_reason: number; + /**Raw byte hash value */ + hash: number[]; + /**Download end time */ + end_time: string; + /**Opened value */ + opened: number; + /**Last access time */ + last_access_time: string; + /**Transient value */ + transient: number; + /**Referrer URL */ + referrer: string; + /**Download source URL */ + site_url: string; + /**Tab URL */ + tab_url: string; + /**Tab referrer URL */ + tab_referrer_url: string; + /**HTTP method used */ + http_method: string; + /**By ext ID value */ + by_ext_id: string; + /**By ext name value */ + by_ext_name: string; + /**Etag value */ + etag: string; + /**Last modified time */ + last_modified: string; + /**MIME type value */ + mime_type: string; + /**Original mime type value */ + original_mime_type: string; + /**Downloads URL chain ID value */ + downloads_url_chain_id: number; + /**Chain index value */ + chain_index: number; + /**URL for download */ + url: string; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumCookies { + creation: string; + host_key: string; + top_frame_site_key: string; + name: string; + value: string; + /**This value is currently Base64 encoded */ + encrypted_value: string; + path: string; + expires: string; + is_secure: boolean; + is_httponly: boolean; + last_access: string; + has_expires: boolean; + is_persistent: boolean; + priority: number; + samesite: number; + source_scheme: number; + source_port: number; + is_same_party: number; + last_update: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumAutofill { + name?: string; + value?: string; + value_lower?: string; + date_created: string; + date_last_used: string; + /**Default is 1 */ + count: number; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Autofill Created"; + artifact: "Website Autofill"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumBookmarks { + date_added: string; + date_last_used: string; + guid: string; + id: number; + name: string; + type: string; + url: string; + meta_info: Record; + bookmark_type: BookmarkType; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Bookmark Added"; + artifact: "Browser Bookmark"; + data_type: string; + browser: BrowserType; +} + +export enum BookmarkType { + Bar = "Bookmark Bar", + Sync = "Synced", + Other = "Other", + Unknown = "Unknown", +} + +export interface ChromiumLogins { + origin_url: string; + action_url?: string; + username_element?: string; + username_value?: string; + password_element?: string; + password_value?: string; + submit_element?: string; + signon_realm: string; + date_created: string; + blacklisted_by_user: number; + scheme: number; + password_type?: number; + times_used?: number; + form_data?: string; + display_name?: string; + icon_url?: string; + federation_url?: string; + skip_zero_click?: number; + generation_upload_status?: number; + possible_username_pairs?: string; + id: number; + date_last_used: string; + moving_blocked_for?: string; + date_password_modified: string; + sender_email?: string; + sender_name?: string; + date_received?: string; + sharing_notification_display: number; + keychain_identifier?: string; + sender_profile_image_url?: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Last Login"; + artifact: "Website Login"; + data_type: string; + browser: BrowserType; +} + +/** + * Detect Incidental Party State (DIPS) collects metrics on websites + */ +export interface ChromiumDips { + site: string; + first_site_storage?: string | null; + last_site_storage?: string | null; + first_user_interaction?: string | null; + last_user_interaction?: string | null; + first_stateful_bounce?: string | null; + last_stateful_bounce?: string | null; + first_bounce?: string | null; + last_bounce?: string | null; + first_web_authn_assertion: string | null; + last_web_authn_assertion: string | null; + /**Path to DIPS database */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "First Interaction"; + artifact: "Browser DIPS"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumProfiles { + full_path: string; + version: string; + browser: BrowserType; +} + +export enum BrowserType { + CHROME = "Google Chrome", + EDGE = "Microsoft Edge", + CHROMIUM = "Google Chromium" +} + +export enum ChromiumCookieType { + Unknown = "Unknown", + Http = "HTTP", + Script = "Script", + Other = "Other", +} + +/** + * Object representing a Local Storage LevelDb entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface ChromiumLocalStorage extends LevelDbEntry { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; + artifact: "Level Database"; + data_type: "applications:leveldb:entry"; +} + +export interface ChromiumSession { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Active"; + artifact: "Browser Session"; + data_type: string; + session_id: string; + last_active: string; + url: string; + title: string; + session_type: SessionType; + evidence: string; +} + +export enum SessionType { + Session = "Session", + Tab = "Tab", +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/chromium.md b/artemis-docs/docs/Artifacts/Application Artifacts/chromium.md index 51a815a7..9a49a424 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/chromium.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/chromium.md @@ -1,380 +1,381 @@ ---- -description: Google's open source browser -keywords: - - browser - - google ---- - -# Chromium - -Chromium is a popular open source web browser created and maintained by Google. -The Chromium codebase also used for multiple other browsers such as: - -- Chrome -- Microsoft Edge -- Opera -- Brave - -Artemis supports parsing the list of artifacts below: - -- History -- Downloads -- Cookies -- Autofill -- Bookmarks -- Login Data -- Extensions -- Preferences -- Detect Incidental Party State (DIPS) -- Local Storage -- Browser sessions -- Favicons -- Shortcuts -- Retrospect - A powerful capability that timelines all artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) - - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Chromium data - - -## Sample API Script - -```typescript -import { Chromium } from "./artemis-api/mod"; -import { PlatformType } from "./artemis-api/src/system/systeminfo"; - -function main() { - const client = new Chromium(PlatformType.Darwin, false, BrowserType.Chromium); - console.log(JSON.stringify(client.cookies())); -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -/** - * An interface representing the Chromium SQLITE tables: `urls` and `visits` - */ -export interface ChromiumHistory { - /**Row ID value */ - id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**Page visit count */ - visit_count: number; - /**Typed count value */ - typed_count: number; - /**Last visit time*/ - last_visit_time: string; - /**Hidden value */ - hidden: number; - /**Visits ID value */ - visits_id: number; - /**From visit value */ - from_visit: number; - /**Transition value */ - transition: number; - /**Segment ID value */ - segment_id: number; - /**Visit duration value */ - visit_duration: number; - /**Opener visit value */ - opener_visit: number; - unfold: Url | undefined; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: string; - browser: BrowserType; -} - -/** - * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` - */ -export interface ChromiumDownloads { - /**Row ID */ - id: number; - /**GUID for download */ - guid: string; - /**Path to download */ - current_path: string; - /**Target path to download */ - target_path: string; - /**Download start time */ - start_time: string; - /**Bytes downloaded */ - received_bytes: number; - /**Total bytes downloaded */ - total_bytes: number; - /**State value */ - state: number; - /**Danger type value */ - danger_type: number; - /**Interrupt reason value */ - interrupt_reason: number; - /**Raw byte hash value */ - hash: number[]; - /**Download end time */ - end_time: string; - /**Opened value */ - opened: number; - /**Last access time */ - last_access_time: string; - /**Transient value */ - transient: number; - /**Referer URL */ - referrer: string; - /**Download source URL */ - site_url: string; - /**Tab URL */ - tab_url: string; - /**Tab referrer URL */ - tab_referrer_url: string; - /**HTTP method used */ - http_method: string; - /**By ext ID value */ - by_ext_id: string; - /**By ext name value */ - by_ext_name: string; - /**Etag value */ - etag: string; - /**Last modified time */ - last_modified: string; - /**MIME type value */ - mime_type: string; - /**Original mime type value */ - original_mime_type: string; - /**Downloads URL chain ID value */ - downloads_url_chain_id: number; - /**Chain index value */ - chain_index: number; - /**URL for download */ - url: string; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumCookies { - creation: string; - host_key: string; - top_frame_site_key: string; - name: string; - value: string; - /**This value is currently Base64 encoded */ - encrypted_value: string; - path: string; - expires: string; - is_secure: boolean; - is_httponly: boolean; - last_access: string; - has_expires: boolean; - is_persistent: boolean; - priority: number; - samesite: number; - source_scheme: number; - source_port: number; - is_same_party: number; - last_update: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumAutofill { - name?: string; - value?: string; - value_lower?: string; - date_created: string; - date_last_used: string; - /**Default is 1 */ - count: number; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Autofill Created"; - artifact: "Website Autofill"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumBookmarks { - date_added: string; - date_last_used: string; - guid: string; - id: number; - name: string; - type: string; - url: string; - meta_info: Record; - bookmark_type: BookmarkType; - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Bookmark Added"; - artifact: "Browser Bookmark"; - data_type: string; - browser: BrowserType; -} - -export enum BookmarkType { - Bar = "Bookmark Bar", - Sync = "Synced", - Other = "Other", - Unknown = "Unknown", -} - -export interface ChromiumLogins { - origin_url: string; - action_url?: string; - username_element?: string; - username_value?: string; - password_element?: string; - password_value?: string; - submit_element?: string; - signon_realm: string; - date_created: string; - blacklisted_by_user: number; - scheme: number; - password_type?: number; - times_used?: number; - form_data?: string; - display_name?: string; - icon_url?: string; - federation_url?: string; - skip_zero_click?: number; - generation_upload_status?: number; - possible_username_pairs?: string; - id: number; - date_last_used: string; - moving_blocked_for?: string; - date_password_modified: string; - sender_email?: string; - sender_name?: string; - date_received?: string; - sharing_notification_display: number; - keychain_identifier?: string; - sender_profile_image_url?: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Last Login"; - artifact: "Website Login"; - data_type: string; - browser: BrowserType; -} - -/** - * Detect Incidental Party State (DIPS) collects metrics on websites - */ -export interface ChromiumDips { - site: string; - first_site_storage?: string | null; - last_site_storage?: string | null; - first_user_interaction?: string | null; - last_user_interaction?: string | null; - first_stateful_bounce?: string | null; - last_stateful_bounce?: string | null; - first_bounce?: string | null; - last_bounce?: string | null; - first_web_authn_assertion: string | null; - last_web_authn_assertion: string | null; - /**Path to DIPS database */ - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "First Interaction"; - artifact: "Browser DIPS"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumProfiles { - full_path: string; - version: string; - browser: BrowserType; -} - -export enum BrowserType { - CHROME = "Google Chrome", - EDGE = "Microsoft Edge", - CHROMIUM = "Google Chromium", - COMET = "Perplexity Comet", - BRAVE = "Brave", -} - -export enum ChromiumCookieType { - Unknown = "Unknown", - Http = "HTTP", - Script = "Script", - Other = "Other", -} - -/** - * Object representing a Local Storage LevelDb entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface ChromiumLocalStorage extends LevelDbEntry { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; - artifact: "Level Database"; - data_type: "applications:leveldb:entry"; -} - -export interface ChromiumSession { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Active"; - artifact: "Browser Session"; - data_type: string; - session_id: string; - last_active: string; - url: string; - title: string; - session_type: SessionType; - path: string; -} - -export enum SessionType { - Session = "Session", - Tab = "Tab", -} -``` +--- +description: Google's open source browser +keywords: + - browser + - google +--- + +# Chromium + +Chromium is a popular open source web browser created and maintained by Google. +The Chromium codebase also used for multiple other browsers such as: + +- Chrome +- Microsoft Edge +- Opera +- Brave + +Artemis supports parsing the list of artifacts below: + +- History +- Downloads +- Cookies +- Autofill +- Bookmarks +- Login Data +- Extensions +- Preferences +- Detect Incidental Party State (DIPS) +- Local Storage +- Browser sessions +- Favicons +- Shortcuts +- Cache +- Retrospect - A powerful capability that timelines all browser artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) + + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Chromium data + + +## Sample API Script + +```typescript +import { Chromium } from "./artemis-api/mod"; +import { PlatformType } from "./artemis-api/src/system/systeminfo"; + +function main() { + const client = new Chromium(PlatformType.Darwin, false, BrowserType.Chromium); + console.log(JSON.stringify(client.cookies())); +} + +main(); +``` + +## Output Structure + +Dependent on browser artifact user wants to parse. + +```typescript +/** + * An interface representing the Chromium SQLITE tables: `urls` and `visits` + */ +export interface ChromiumHistory { + /**Row ID value */ + id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**Page visit count */ + visit_count: number; + /**Typed count value */ + typed_count: number; + /**Last visit time*/ + last_visit_time: string; + /**Hidden value */ + hidden: number; + /**Visits ID value */ + visits_id: number; + /**From visit value */ + from_visit: number; + /**Transition value */ + transition: number; + /**Segment ID value */ + segment_id: number; + /**Visit duration value */ + visit_duration: number; + /**Opener visit value */ + opener_visit: number; + unfold: Url | undefined; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: string; + browser: BrowserType; +} + +/** + * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` + */ +export interface ChromiumDownloads { + /**Row ID */ + id: number; + /**GUID for download */ + guid: string; + /**Path to download */ + current_path: string; + /**Target path to download */ + target_path: string; + /**Download start time */ + start_time: string; + /**Bytes downloaded */ + received_bytes: number; + /**Total bytes downloaded */ + total_bytes: number; + /**State value */ + state: number; + /**Danger type value */ + danger_type: number; + /**Interrupt reason value */ + interrupt_reason: number; + /**Raw byte hash value */ + hash: number[]; + /**Download end time */ + end_time: string; + /**Opened value */ + opened: number; + /**Last access time */ + last_access_time: string; + /**Transient value */ + transient: number; + /**Referrer URL */ + referrer: string; + /**Download source URL */ + site_url: string; + /**Tab URL */ + tab_url: string; + /**Tab referrer URL */ + tab_referrer_url: string; + /**HTTP method used */ + http_method: string; + /**By ext ID value */ + by_ext_id: string; + /**By ext name value */ + by_ext_name: string; + /**Etag value */ + etag: string; + /**Last modified time */ + last_modified: string; + /**MIME type value */ + mime_type: string; + /**Original mime type value */ + original_mime_type: string; + /**Downloads URL chain ID value */ + downloads_url_chain_id: number; + /**Chain index value */ + chain_index: number; + /**URL for download */ + url: string; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumCookies { + creation: string; + host_key: string; + top_frame_site_key: string; + name: string; + value: string; + /**This value is currently Base64 encoded */ + encrypted_value: string; + path: string; + expires: string; + is_secure: boolean; + is_httponly: boolean; + last_access: string; + has_expires: boolean; + is_persistent: boolean; + priority: number; + samesite: number; + source_scheme: number; + source_port: number; + is_same_party: number; + last_update: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumAutofill { + name?: string; + value?: string; + value_lower?: string; + date_created: string; + date_last_used: string; + /**Default is 1 */ + count: number; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Autofill Created"; + artifact: "Website Autofill"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumBookmarks { + date_added: string; + date_last_used: string; + guid: string; + id: number; + name: string; + type: string; + url: string; + meta_info: Record; + bookmark_type: BookmarkType; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Bookmark Added"; + artifact: "Browser Bookmark"; + data_type: string; + browser: BrowserType; +} + +export enum BookmarkType { + Bar = "Bookmark Bar", + Sync = "Synced", + Other = "Other", + Unknown = "Unknown", +} + +export interface ChromiumLogins { + origin_url: string; + action_url?: string; + username_element?: string; + username_value?: string; + password_element?: string; + password_value?: string; + submit_element?: string; + signon_realm: string; + date_created: string; + blacklisted_by_user: number; + scheme: number; + password_type?: number; + times_used?: number; + form_data?: string; + display_name?: string; + icon_url?: string; + federation_url?: string; + skip_zero_click?: number; + generation_upload_status?: number; + possible_username_pairs?: string; + id: number; + date_last_used: string; + moving_blocked_for?: string; + date_password_modified: string; + sender_email?: string; + sender_name?: string; + date_received?: string; + sharing_notification_display: number; + keychain_identifier?: string; + sender_profile_image_url?: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Last Login"; + artifact: "Website Login"; + data_type: string; + browser: BrowserType; +} + +/** + * Detect Incidental Party State (DIPS) collects metrics on websites + */ +export interface ChromiumDips { + site: string; + first_site_storage?: string | null; + last_site_storage?: string | null; + first_user_interaction?: string | null; + last_user_interaction?: string | null; + first_stateful_bounce?: string | null; + last_stateful_bounce?: string | null; + first_bounce?: string | null; + last_bounce?: string | null; + first_web_authn_assertion: string | null; + last_web_authn_assertion: string | null; + /**Path to DIPS database */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "First Interaction"; + artifact: "Browser DIPS"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumProfiles { + full_path: string; + version: string; + browser: BrowserType; +} + +export enum BrowserType { + CHROME = "Google Chrome", + EDGE = "Microsoft Edge", + CHROMIUM = "Google Chromium", + COMET = "Perplexity Comet", + BRAVE = "Brave", +} + +export enum ChromiumCookieType { + Unknown = "Unknown", + Http = "HTTP", + Script = "Script", + Other = "Other", +} + +/** + * Object representing a Local Storage LevelDb entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface ChromiumLocalStorage extends LevelDbEntry { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; + artifact: "Level Database"; + data_type: "applications:leveldb:entry"; +} + +export interface ChromiumSession { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Active"; + artifact: "Browser Session"; + data_type: string; + session_id: string; + last_active: string; + url: string; + title: string; + session_type: SessionType; + evidence: string; +} + +export enum SessionType { + Session = "Session", + Tab = "Tab", +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/comet.md b/artemis-docs/docs/Artifacts/Application Artifacts/comet.md index 03737afb..59e81a54 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/comet.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/comet.md @@ -1,365 +1,366 @@ ---- -description: Perplexity AI Browser -keywords: - - browser ---- - -# Comet - -Comet is a AI focus web browser created and maintained by Perplexity. - -Artemis supports parsing the list of artifacts below: - -- History -- Downloads -- Cookies -- Autofill -- Bookmarks -- Login Data -- Extensions -- Preferences -- Detect Incidental Party State (DIPS) -- Local Storage -- Browser sessions -- Favicons -- Shortcuts -- Retrospect - A powerful capability that timelines all artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) - - -## Collection -You have to use the artemis [api](../../API/overview.md) in order to collect Comet data. -Since Comet is based on Chromium, many of the Comet artifacts will be identical to Chromium - - -## Sample API Script - -```typescript -import { Comet } from "./artemis-api/mod"; -import { PlatformType } from "./artemis-api/src/system/systeminfo"; - -function main() { - const client = new Comet(PlatformType.Darwin); - console.log(JSON.stringify(client.cookies())); -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -/** - * An interface representing the Chromium SQLITE tables: `urls` and `visits` - */ -export interface ChromiumHistory { - /**Row ID value */ - id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**Page visit count */ - visit_count: number; - /**Typed count value */ - typed_count: number; - /**Last visit time*/ - last_visit_time: string; - /**Hidden value */ - hidden: number; - /**Visits ID value */ - visits_id: number; - /**From visit value */ - from_visit: number; - /**Transition value */ - transition: number; - /**Segment ID value */ - segment_id: number; - /**Visit duration value */ - visit_duration: number; - /**Opener visit value */ - opener_visit: number; - unfold: Url | undefined; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: string; - browser: BrowserType; -} - -/** - * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` - */ -export interface ChromiumDownloads { - /**Row ID */ - id: number; - /**GUID for download */ - guid: string; - /**Path to download */ - current_path: string; - /**Target path to download */ - target_path: string; - /**Download start time */ - start_time: string; - /**Bytes downloaded */ - received_bytes: number; - /**Total bytes downloaded */ - total_bytes: number; - /**State value */ - state: number; - /**Danger type value */ - danger_type: number; - /**Interrupt reason value */ - interrupt_reason: number; - /**Raw byte hash value */ - hash: number[]; - /**Download end time */ - end_time: string; - /**Opened value */ - opened: number; - /**Last access time */ - last_access_time: string; - /**Transient value */ - transient: number; - /**Referer URL */ - referrer: string; - /**Download source URL */ - site_url: string; - /**Tab URL */ - tab_url: string; - /**Tab referrer URL */ - tab_referrer_url: string; - /**HTTP method used */ - http_method: string; - /**By ext ID value */ - by_ext_id: string; - /**By ext name value */ - by_ext_name: string; - /**Etag value */ - etag: string; - /**Last modified time */ - last_modified: string; - /**MIME type value */ - mime_type: string; - /**Original mime type value */ - original_mime_type: string; - /**Downloads URL chain ID value */ - downloads_url_chain_id: number; - /**Chain index value */ - chain_index: number; - /**URL for download */ - url: string; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumCookies { - creation: string; - host_key: string; - top_frame_site_key: string; - name: string; - value: string; - /**This value is currently Base64 encoded */ - encrypted_value: string; - path: string; - expires: string; - is_secure: boolean; - is_httponly: boolean; - last_access: string; - has_expires: boolean; - is_persistent: boolean; - priority: number; - samesite: number; - source_scheme: number; - source_port: number; - is_same_party: number; - last_update: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumAutofill { - name?: string; - value?: string; - value_lower?: string; - date_created: string; - date_last_used: string; - /**Default is 1 */ - count: number; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Autofill Created"; - artifact: "Website Autofill"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumBookmarks { - date_added: string; - date_last_used: string; - guid: string; - id: number; - name: string; - type: string; - url: string; - meta_info: Record; - bookmark_type: BookmarkType; - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Bookmark Added"; - artifact: "Browser Bookmark"; - data_type: string; - browser: BrowserType; -} - -export enum BookmarkType { - Bar = "Bookmark Bar", - Sync = "Synced", - Other = "Other", - Unknown = "Unknown", -} - -export interface ChromiumLogins { - origin_url: string; - action_url?: string; - username_element?: string; - username_value?: string; - password_element?: string; - password_value?: string; - submit_element?: string; - signon_realm: string; - date_created: string; - blacklisted_by_user: number; - scheme: number; - password_type?: number; - times_used?: number; - form_data?: string; - display_name?: string; - icon_url?: string; - federation_url?: string; - skip_zero_click?: number; - generation_upload_status?: number; - possible_username_pairs?: string; - id: number; - date_last_used: string; - moving_blocked_for?: string; - date_password_modified: string; - sender_email?: string; - sender_name?: string; - date_received?: string; - sharing_notification_display: number; - keychain_identifier?: string; - sender_profile_image_url?: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Last Login"; - artifact: "Website Login"; - data_type: string; - browser: BrowserType; -} - -/** - * Detect Incidental Party State (DIPS) collects metrics on websites - */ -export interface ChromiumDips { - site: string; - first_site_storage?: string | null; - last_site_storage?: string | null; - first_user_interaction?: string | null; - last_user_interaction?: string | null; - first_stateful_bounce?: string | null; - last_stateful_bounce?: string | null; - first_bounce?: string | null; - last_bounce?: string | null; - first_web_authn_assertion: string | null; - last_web_authn_assertion: string | null; - /**Path to DIPS database */ - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "First Interaction"; - artifact: "Browser DIPS"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumProfiles { - full_path: string; - version: string; - browser: BrowserType; -} - -export enum ChromiumCookieType { - Unknown = "Unknown", - Http = "HTTP", - Script = "Script", - Other = "Other", -} - -/** - * Object representing a Local Storage LevelDb entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface ChromiumLocalStorage extends LevelDbEntry { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; - artifact: "Level Database"; - data_type: "applications:leveldb:entry"; -} - -export interface ChromiumSession { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Active"; - artifact: "Browser Session"; - data_type: string; - session_id: string; - last_active: string; - url: string; - title: string; - session_type: SessionType; - path: string; -} - -export enum SessionType { - Session = "Session", - Tab = "Tab", -} -``` +--- +description: Perplexity AI Browser +keywords: + - browser +--- + +# Comet + +Comet is a AI focus web browser created and maintained by Perplexity. + +Artemis supports parsing the list of artifacts below: + +- History +- Downloads +- Cookies +- Autofill +- Bookmarks +- Login Data +- Extensions +- Preferences +- Detect Incidental Party State (DIPS) +- Local Storage +- Browser sessions +- Favicons +- Shortcuts +- Cache +- Retrospect - A powerful capability that timelines all browser artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) + + +## Collection +You have to use the artemis [api](../../API/overview.md) in order to collect Comet data. +Since Comet is based on Chromium, many of the Comet artifacts will be identical to Chromium + + +## Sample API Script + +```typescript +import { Comet } from "./artemis-api/mod"; +import { PlatformType } from "./artemis-api/src/system/systeminfo"; + +function main() { + const client = new Comet(PlatformType.Darwin); + console.log(JSON.stringify(client.cookies())); +} + +main(); +``` + +## Output Structure + +Dependent on browser artifact user wants to parse. + +```typescript +/** + * An interface representing the Chromium SQLITE tables: `urls` and `visits` + */ +export interface ChromiumHistory { + /**Row ID value */ + id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**Page visit count */ + visit_count: number; + /**Typed count value */ + typed_count: number; + /**Last visit time*/ + last_visit_time: string; + /**Hidden value */ + hidden: number; + /**Visits ID value */ + visits_id: number; + /**From visit value */ + from_visit: number; + /**Transition value */ + transition: number; + /**Segment ID value */ + segment_id: number; + /**Visit duration value */ + visit_duration: number; + /**Opener visit value */ + opener_visit: number; + unfold: Url | undefined; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: string; + browser: BrowserType; +} + +/** + * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` + */ +export interface ChromiumDownloads { + /**Row ID */ + id: number; + /**GUID for download */ + guid: string; + /**Path to download */ + current_path: string; + /**Target path to download */ + target_path: string; + /**Download start time */ + start_time: string; + /**Bytes downloaded */ + received_bytes: number; + /**Total bytes downloaded */ + total_bytes: number; + /**State value */ + state: number; + /**Danger type value */ + danger_type: number; + /**Interrupt reason value */ + interrupt_reason: number; + /**Raw byte hash value */ + hash: number[]; + /**Download end time */ + end_time: string; + /**Opened value */ + opened: number; + /**Last access time */ + last_access_time: string; + /**Transient value */ + transient: number; + /**Referrer URL */ + referrer: string; + /**Download source URL */ + site_url: string; + /**Tab URL */ + tab_url: string; + /**Tab referrer URL */ + tab_referrer_url: string; + /**HTTP method used */ + http_method: string; + /**By ext ID value */ + by_ext_id: string; + /**By ext name value */ + by_ext_name: string; + /**Etag value */ + etag: string; + /**Last modified time */ + last_modified: string; + /**MIME type value */ + mime_type: string; + /**Original mime type value */ + original_mime_type: string; + /**Downloads URL chain ID value */ + downloads_url_chain_id: number; + /**Chain index value */ + chain_index: number; + /**URL for download */ + url: string; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumCookies { + creation: string; + host_key: string; + top_frame_site_key: string; + name: string; + value: string; + /**This value is currently Base64 encoded */ + encrypted_value: string; + path: string; + expires: string; + is_secure: boolean; + is_httponly: boolean; + last_access: string; + has_expires: boolean; + is_persistent: boolean; + priority: number; + samesite: number; + source_scheme: number; + source_port: number; + is_same_party: number; + last_update: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumAutofill { + name?: string; + value?: string; + value_lower?: string; + date_created: string; + date_last_used: string; + /**Default is 1 */ + count: number; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Autofill Created"; + artifact: "Website Autofill"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumBookmarks { + date_added: string; + date_last_used: string; + guid: string; + id: number; + name: string; + type: string; + url: string; + meta_info: Record; + bookmark_type: BookmarkType; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Bookmark Added"; + artifact: "Browser Bookmark"; + data_type: string; + browser: BrowserType; +} + +export enum BookmarkType { + Bar = "Bookmark Bar", + Sync = "Synced", + Other = "Other", + Unknown = "Unknown", +} + +export interface ChromiumLogins { + origin_url: string; + action_url?: string; + username_element?: string; + username_value?: string; + password_element?: string; + password_value?: string; + submit_element?: string; + signon_realm: string; + date_created: string; + blacklisted_by_user: number; + scheme: number; + password_type?: number; + times_used?: number; + form_data?: string; + display_name?: string; + icon_url?: string; + federation_url?: string; + skip_zero_click?: number; + generation_upload_status?: number; + possible_username_pairs?: string; + id: number; + date_last_used: string; + moving_blocked_for?: string; + date_password_modified: string; + sender_email?: string; + sender_name?: string; + date_received?: string; + sharing_notification_display: number; + keychain_identifier?: string; + sender_profile_image_url?: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Last Login"; + artifact: "Website Login"; + data_type: string; + browser: BrowserType; +} + +/** + * Detect Incidental Party State (DIPS) collects metrics on websites + */ +export interface ChromiumDips { + site: string; + first_site_storage?: string | null; + last_site_storage?: string | null; + first_user_interaction?: string | null; + last_user_interaction?: string | null; + first_stateful_bounce?: string | null; + last_stateful_bounce?: string | null; + first_bounce?: string | null; + last_bounce?: string | null; + first_web_authn_assertion: string | null; + last_web_authn_assertion: string | null; + /**Path to DIPS database */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "First Interaction"; + artifact: "Browser DIPS"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumProfiles { + full_path: string; + version: string; + browser: BrowserType; +} + +export enum ChromiumCookieType { + Unknown = "Unknown", + Http = "HTTP", + Script = "Script", + Other = "Other", +} + +/** + * Object representing a Local Storage LevelDb entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface ChromiumLocalStorage extends LevelDbEntry { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; + artifact: "Level Database"; + data_type: "applications:leveldb:entry"; +} + +export interface ChromiumSession { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Active"; + artifact: "Browser Session"; + data_type: string; + session_id: string; + last_active: string; + url: string; + title: string; + session_type: SessionType; + evidence: string; +} + +export enum SessionType { + Session = "Session", + Tab = "Tab", +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/edge.md b/artemis-docs/docs/Artifacts/Application Artifacts/edge.md index a0289f72..d872176b 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/edge.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/edge.md @@ -1,367 +1,368 @@ ---- -description: Microsoft Edge (Chromium based) -keywords: - - browser - - microsoft ---- - -# Edge - -Edge is a popular web browser created and maintained by Microsoft. - -Artemis supports parsing the list of artifacts below: - -- History -- Downloads -- Cookies -- Autofill -- Bookmarks -- Login Data -- Extensions -- Preferences -- Detect Incidental Party State (DIPS) -- Local Storage -- Browser sessions -- Favicons -- Shortcuts -- Retrospect - A powerful capability that timelines all artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) - - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Edge data. -Since Edge is based on Chromium, many of the Edge artifacts will be identical to Chromium - - -## Sample API Script - -```typescript -import { Edge } from "./artemis-api/mod"; -import { PlatformType } from "./artemis-api/src/system/systeminfo"; - -function main() { - const client = new Edge(PlatformType.Darwin); - console.log(JSON.stringify(client.cookies())); -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -/** - * An interface representing the Chromium SQLITE tables: `urls` and `visits` - */ -export interface ChromiumHistory { - /**Row ID value */ - id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**Page visit count */ - visit_count: number; - /**Typed count value */ - typed_count: number; - /**Last visit time*/ - last_visit_time: string; - /**Hidden value */ - hidden: number; - /**Visits ID value */ - visits_id: number; - /**From visit value */ - from_visit: number; - /**Transition value */ - transition: number; - /**Segment ID value */ - segment_id: number; - /**Visit duration value */ - visit_duration: number; - /**Opener visit value */ - opener_visit: number; - unfold: Url | undefined; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: string; - browser: BrowserType; -} - -/** - * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` - */ -export interface ChromiumDownloads { - /**Row ID */ - id: number; - /**GUID for download */ - guid: string; - /**Path to download */ - current_path: string; - /**Target path to download */ - target_path: string; - /**Download start time */ - start_time: string; - /**Bytes downloaded */ - received_bytes: number; - /**Total bytes downloaded */ - total_bytes: number; - /**State value */ - state: number; - /**Danger type value */ - danger_type: number; - /**Interrupt reason value */ - interrupt_reason: number; - /**Raw byte hash value */ - hash: number[]; - /**Download end time */ - end_time: string; - /**Opened value */ - opened: number; - /**Last access time */ - last_access_time: string; - /**Transient value */ - transient: number; - /**Referer URL */ - referrer: string; - /**Download source URL */ - site_url: string; - /**Tab URL */ - tab_url: string; - /**Tab referrer URL */ - tab_referrer_url: string; - /**HTTP method used */ - http_method: string; - /**By ext ID value */ - by_ext_id: string; - /**By ext name value */ - by_ext_name: string; - /**Etag value */ - etag: string; - /**Last modified time */ - last_modified: string; - /**MIME type value */ - mime_type: string; - /**Original mime type value */ - original_mime_type: string; - /**Downloads URL chain ID value */ - downloads_url_chain_id: number; - /**Chain index value */ - chain_index: number; - /**URL for download */ - url: string; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumCookies { - creation: string; - host_key: string; - top_frame_site_key: string; - name: string; - value: string; - /**This value is currently Base64 encoded */ - encrypted_value: string; - path: string; - expires: string; - is_secure: boolean; - is_httponly: boolean; - last_access: string; - has_expires: boolean; - is_persistent: boolean; - priority: number; - samesite: number; - source_scheme: number; - source_port: number; - is_same_party: number; - last_update: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumAutofill { - name?: string; - value?: string; - value_lower?: string; - date_created: string; - date_last_used: string; - /**Default is 1 */ - count: number; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Autofill Created"; - artifact: "Website Autofill"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumBookmarks { - date_added: string; - date_last_used: string; - guid: string; - id: number; - name: string; - type: string; - url: string; - meta_info: Record; - bookmark_type: BookmarkType; - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Bookmark Added"; - artifact: "Browser Bookmark"; - data_type: string; - browser: BrowserType; -} - -export enum BookmarkType { - Bar = "Bookmark Bar", - Sync = "Synced", - Other = "Other", - Unknown = "Unknown", -} - -export interface ChromiumLogins { - origin_url: string; - action_url?: string; - username_element?: string; - username_value?: string; - password_element?: string; - password_value?: string; - submit_element?: string; - signon_realm: string; - date_created: string; - blacklisted_by_user: number; - scheme: number; - password_type?: number; - times_used?: number; - form_data?: string; - display_name?: string; - icon_url?: string; - federation_url?: string; - skip_zero_click?: number; - generation_upload_status?: number; - possible_username_pairs?: string; - id: number; - date_last_used: string; - moving_blocked_for?: string; - date_password_modified: string; - sender_email?: string; - sender_name?: string; - date_received?: string; - sharing_notification_display: number; - keychain_identifier?: string; - sender_profile_image_url?: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Last Login"; - artifact: "Website Login"; - data_type: string; - browser: BrowserType; -} - -/** - * Detect Incidental Party State (DIPS) collects metrics on websites - */ -export interface ChromiumDips { - site: string; - first_site_storage?: string | null; - last_site_storage?: string | null; - first_user_interaction?: string | null; - last_user_interaction?: string | null; - first_stateful_bounce?: string | null; - last_stateful_bounce?: string | null; - first_bounce?: string | null; - last_bounce?: string | null; - first_web_authn_assertion: string | null; - last_web_authn_assertion: string | null; - /**Path to DIPS database */ - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "First Interaction"; - artifact: "Browser DIPS"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumProfiles { - full_path: string; - version: string; - browser: BrowserType; -} - -export enum ChromiumCookieType { - Unknown = "Unknown", - Http = "HTTP", - Script = "Script", - Other = "Other", -} - -/** - * Object representing a Local Storage LevelDb entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface ChromiumLocalStorage extends LevelDbEntry { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; - artifact: "Level Database"; - data_type: "applications:leveldb:entry"; -} - -export interface ChromiumSession { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Active"; - artifact: "Browser Session"; - data_type: string; - session_id: string; - last_active: string; - url: string; - title: string; - session_type: SessionType; - path: string; -} - -export enum SessionType { - Session = "Session", - Tab = "Tab", -} -``` +--- +description: Microsoft Edge (Chromium based) +keywords: + - browser + - microsoft +--- + +# Edge + +Edge is a popular web browser created and maintained by Microsoft. + +Artemis supports parsing the list of artifacts below: + +- History +- Downloads +- Cookies +- Autofill +- Bookmarks +- Login Data +- Extensions +- Preferences +- Detect Incidental Party State (DIPS) +- Local Storage +- Browser sessions +- Favicons +- Shortcuts +- Cache +- Retrospect - A powerful capability that timelines all browser artifacts. It is based on [Hindsight](https://github.com/obsidianforensics/hindsight) + + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Edge data. +Since Edge is based on Chromium, many of the Edge artifacts will be identical to Chromium + + +## Sample API Script + +```typescript +import { Edge } from "./artemis-api/mod"; +import { PlatformType } from "./artemis-api/src/system/systeminfo"; + +function main() { + const client = new Edge(PlatformType.Darwin); + console.log(JSON.stringify(client.cookies())); +} + +main(); +``` + +## Output Structure + +Dependent on browser artifact user wants to parse. + +```typescript +/** + * An interface representing the Chromium SQLITE tables: `urls` and `visits` + */ +export interface ChromiumHistory { + /**Row ID value */ + id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**Page visit count */ + visit_count: number; + /**Typed count value */ + typed_count: number; + /**Last visit time*/ + last_visit_time: string; + /**Hidden value */ + hidden: number; + /**Visits ID value */ + visits_id: number; + /**From visit value */ + from_visit: number; + /**Transition value */ + transition: number; + /**Segment ID value */ + segment_id: number; + /**Visit duration value */ + visit_duration: number; + /**Opener visit value */ + opener_visit: number; + unfold: Url | undefined; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: string; + browser: BrowserType; +} + +/** + * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` + */ +export interface ChromiumDownloads { + /**Row ID */ + id: number; + /**GUID for download */ + guid: string; + /**Path to download */ + current_path: string; + /**Target path to download */ + target_path: string; + /**Download start time */ + start_time: string; + /**Bytes downloaded */ + received_bytes: number; + /**Total bytes downloaded */ + total_bytes: number; + /**State value */ + state: number; + /**Danger type value */ + danger_type: number; + /**Interrupt reason value */ + interrupt_reason: number; + /**Raw byte hash value */ + hash: number[]; + /**Download end time */ + end_time: string; + /**Opened value */ + opened: number; + /**Last access time */ + last_access_time: string; + /**Transient value */ + transient: number; + /**Referrer URL */ + referrer: string; + /**Download source URL */ + site_url: string; + /**Tab URL */ + tab_url: string; + /**Tab referrer URL */ + tab_referrer_url: string; + /**HTTP method used */ + http_method: string; + /**By ext ID value */ + by_ext_id: string; + /**By ext name value */ + by_ext_name: string; + /**Etag value */ + etag: string; + /**Last modified time */ + last_modified: string; + /**MIME type value */ + mime_type: string; + /**Original mime type value */ + original_mime_type: string; + /**Downloads URL chain ID value */ + downloads_url_chain_id: number; + /**Chain index value */ + chain_index: number; + /**URL for download */ + url: string; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumCookies { + creation: string; + host_key: string; + top_frame_site_key: string; + name: string; + value: string; + /**This value is currently Base64 encoded */ + encrypted_value: string; + path: string; + expires: string; + is_secure: boolean; + is_httponly: boolean; + last_access: string; + has_expires: boolean; + is_persistent: boolean; + priority: number; + samesite: number; + source_scheme: number; + source_port: number; + is_same_party: number; + last_update: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumAutofill { + name?: string; + value?: string; + value_lower?: string; + date_created: string; + date_last_used: string; + /**Default is 1 */ + count: number; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Autofill Created"; + artifact: "Website Autofill"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumBookmarks { + date_added: string; + date_last_used: string; + guid: string; + id: number; + name: string; + type: string; + url: string; + meta_info: Record; + bookmark_type: BookmarkType; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Bookmark Added"; + artifact: "Browser Bookmark"; + data_type: string; + browser: BrowserType; +} + +export enum BookmarkType { + Bar = "Bookmark Bar", + Sync = "Synced", + Other = "Other", + Unknown = "Unknown", +} + +export interface ChromiumLogins { + origin_url: string; + action_url?: string; + username_element?: string; + username_value?: string; + password_element?: string; + password_value?: string; + submit_element?: string; + signon_realm: string; + date_created: string; + blacklisted_by_user: number; + scheme: number; + password_type?: number; + times_used?: number; + form_data?: string; + display_name?: string; + icon_url?: string; + federation_url?: string; + skip_zero_click?: number; + generation_upload_status?: number; + possible_username_pairs?: string; + id: number; + date_last_used: string; + moving_blocked_for?: string; + date_password_modified: string; + sender_email?: string; + sender_name?: string; + date_received?: string; + sharing_notification_display: number; + keychain_identifier?: string; + sender_profile_image_url?: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Last Login"; + artifact: "Website Login"; + data_type: string; + browser: BrowserType; +} + +/** + * Detect Incidental Party State (DIPS) collects metrics on websites + */ +export interface ChromiumDips { + site: string; + first_site_storage?: string | null; + last_site_storage?: string | null; + first_user_interaction?: string | null; + last_user_interaction?: string | null; + first_stateful_bounce?: string | null; + last_stateful_bounce?: string | null; + first_bounce?: string | null; + last_bounce?: string | null; + first_web_authn_assertion: string | null; + last_web_authn_assertion: string | null; + /**Path to DIPS database */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "First Interaction"; + artifact: "Browser DIPS"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumProfiles { + full_path: string; + version: string; + browser: BrowserType; +} + +export enum ChromiumCookieType { + Unknown = "Unknown", + Http = "HTTP", + Script = "Script", + Other = "Other", +} + +/** + * Object representing a Local Storage LevelDb entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface ChromiumLocalStorage extends LevelDbEntry { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; + artifact: "Level Database"; + data_type: "applications:leveldb:entry"; +} + +export interface ChromiumSession { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Active"; + artifact: "Browser Session"; + data_type: string; + session_id: string; + last_active: string; + url: string; + title: string; + session_type: SessionType; + evidence: string; +} + +export enum SessionType { + Session = "Session", + Tab = "Tab", +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/firefox.md b/artemis-docs/docs/Artifacts/Application Artifacts/firefox.md index 559b197c..0ce51443 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/firefox.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/firefox.md @@ -1,252 +1,252 @@ ---- -description: The Mozilla browser -keywords: - - browser - - mozilla ---- - -# Firefox - -Firefox is a popular open source web browser created and maintained by Mozilla. -Artemis supports parsing the following artifacts from Firefox. - -- History -- Downloads -- Cookies -- Addons -- Website storage -- Favicons -- Form history - -Other parsers: - -- Any program that read a SQLITE database - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect FireFox data - - -## Sample API Script - -```typescript -import { FireFox, PlatformType } from "./artemis-api/mod"; - -function main() { - const enable_unfold = true; - const fox = new FireFox(PlatformType.Linux, enable_unfold); - - const start = 0; - const limit = 300; - const history_data = fox.history(start, limit); - - const addons = fox.addons(); - return addons; -} - -main(); -``` - -## Output Structure - -Dependent on browser artifact user wants to parse. - -```typescript -import { Url } from "../http/unfold"; - -/** - * Firefox history is stored in a SQLITE file. - * `artemis` uses the `sqlite` crate to read the SQLITE file. It can even read the file if Firefox is running. - * - * References: - * - https://kb.mozillazine.org/Places.sqlite - */ - -/** - * An interface representing the Firefox SQLITE tables: `moz_places` and `moz_origins` - */ -export interface FirefoxHistory { - /**SQLITE row id */ - moz_places_id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**URL in reverse */ - rev_host: string; - /**Page visit count */ - visit_count: number; - /**Hidden value */ - hidden: number; - /**Typed value */ - typed: number; - /**Frequency value */ - frequency: number; - /**Last visit time */ - last_visit_date: string; - /**GUID for entry */ - guid: string; - /**Foreign count value */ - foreign_count: number; - /**Hash of URL */ - url_hash: number; - /**Page description */ - description: string; - /**Preview image URL value */ - preview_image_url: string; - /**Prefix value (ex: https://) */ - prefix: string; - /** Host value */ - host: string; - unfold: Url | undefined; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: "application:firefox:history:entry"; -} - -/** - * An interface representing the Firefox SQLITE tables: `moz_places`, `moz_origins`, `moz_annos`, `moz_anno_attributes` - */ -export interface FirefoxDownloads { - /**ID for SQLITE row */ - id: number; - /**ID to history entry */ - place_id: number; - /**ID to anno_attribute entry */ - anno_attribute_id: number; - /**Content value */ - content: string; - /**Flags value */ - flags: number; - /**Expiration value */ - expiration: number; - /**Download type value */ - download_type: number; - /**Date added */ - date_added: string; - /**Last modified */ - last_modified: string; - /**Downloaded file name */ - name: string; - /**SQLITE row id */ - moz_places_id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**URL in reverse */ - rev_host: string; - /**Page visit count */ - visit_count: number; - /**Hidden value */ - hidden: number; - /**Typed value */ - typed: number; - /**Frequency value */ - frequency: number; - /**Last visit time */ - last_visit_date: string; - /**GUID for entry */ - guid: string; - /**Foreign count value */ - foreign_count: number; - /**Hash of URL */ - url_hash: number; - /**Page description */ - description: string; - /**Preview image URL value */ - preview_image_url: string; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: "application:firefox:downloads:entry"; -} - -export interface FirefoxCookies { - id: number; - origin_attributes: string; - name: string; - value: string; - host: string; - path: string; - expiry: string; - last_accessed: string; - creation_time: string; - is_secure: boolean; - is_http_only: boolean; - in_browser_element: boolean; - same_site: boolean; - scheme_map: number; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: "application:firefox:cookies:entry"; -} - -export interface FirefoxFavicons { - icon_url: string; - expires: string; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "Favicon Expires"; - artifact: "URL Favicon"; - data_type: "application:firefox:favicons:entry"; -} - -export interface FirefoxProfiles { - full_path: string; - version: number; -} - -export interface FirefoxStorage { - repository: Respository; - suffix?: string; - group: string; - origin: string; - client_usages: string; - last_access: string; - accessed: number; - persisted: number; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "Website Storage Last Accessed"; - artifact: "Website Storage"; - data_type: "application:firefox:storage:entry"; -} - -export enum Respository { - Persistent = "Persistent", - Default = "Default", - Private = "Private", - Unknown = "Unknown", - Temporary = "Temporary", -} - -export interface FirefoxAddons { - installed: string; - updated: string; - active: boolean; - visible: boolean; - author: string; - version: string; - path: string; - db_path: string; - message: string; - datetime: string; - name: string; - description: string; - creator: string; - timestamp_desc: "Extension Installed"; - artifact: "Browser Extension"; - data_type: "application:firefox:extension:entry"; -} -``` +--- +description: The Mozilla browser +keywords: + - browser + - mozilla +--- + +# Firefox + +Firefox is a popular open source web browser created and maintained by Mozilla. +Artemis supports parsing the following artifacts from Firefox. + +- History +- Downloads +- Cookies +- Addons +- Website storage +- Favicons +- Form history + +Other parsers: + +- Any program that read a SQLITE database + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect FireFox data + + +## Sample API Script + +```typescript +import { FireFox, PlatformType } from "./artemis-api/mod"; + +function main() { + const enable_unfold = true; + const fox = new FireFox(PlatformType.Linux, enable_unfold); + + const start = 0; + const limit = 300; + const history_data = fox.history(start, limit); + + const addons = fox.addons(); + return addons; +} + +main(); +``` + +## Output Structure + +Dependent on browser artifact user wants to parse. + +```typescript +import { Url } from "../http/unfold"; + +/** + * Firefox history is stored in a SQLITE file. + * `artemis` uses the `sqlite` crate to read the SQLITE file. It can even read the file if Firefox is running. + * + * References: + * - https://kb.mozillazine.org/Places.sqlite + */ + +/** + * An interface representing the Firefox SQLITE tables: `moz_places` and `moz_origins` + */ +export interface FirefoxHistory { + /**SQLITE row id */ + moz_places_id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**URL in reverse */ + rev_host: string; + /**Page visit count */ + visit_count: number; + /**Hidden value */ + hidden: number; + /**Typed value */ + typed: number; + /**Frequency value */ + frequency: number; + /**Last visit time */ + last_visit_date: string; + /**GUID for entry */ + guid: string; + /**Foreign count value */ + foreign_count: number; + /**Hash of URL */ + url_hash: number; + /**Page description */ + description: string; + /**Preview image URL value */ + preview_image_url: string; + /**Prefix value (ex: https://) */ + prefix: string; + /** Host value */ + host: string; + unfold: Url | undefined; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: "application:firefox:history:entry"; +} + +/** + * An interface representing the Firefox SQLITE tables: `moz_places`, `moz_origins`, `moz_annos`, `moz_anno_attributes` + */ +export interface FirefoxDownloads { + /**ID for SQLITE row */ + id: number; + /**ID to history entry */ + place_id: number; + /**ID to anno_attribute entry */ + anno_attribute_id: number; + /**Content value */ + content: string; + /**Flags value */ + flags: number; + /**Expiration value */ + expiration: number; + /**Download type value */ + download_type: number; + /**Date added */ + date_added: string; + /**Last modified */ + last_modified: string; + /**Downloaded file name */ + name: string; + /**SQLITE row id */ + moz_places_id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**URL in reverse */ + rev_host: string; + /**Page visit count */ + visit_count: number; + /**Hidden value */ + hidden: number; + /**Typed value */ + typed: number; + /**Frequency value */ + frequency: number; + /**Last visit time */ + last_visit_date: string; + /**GUID for entry */ + guid: string; + /**Foreign count value */ + foreign_count: number; + /**Hash of URL */ + url_hash: number; + /**Page description */ + description: string; + /**Preview image URL value */ + preview_image_url: string; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: "application:firefox:downloads:entry"; +} + +export interface FirefoxCookies { + id: number; + origin_attributes: string; + name: string; + value: string; + host: string; + path: string; + expiry: string; + last_accessed: string; + creation_time: string; + is_secure: boolean; + is_http_only: boolean; + in_browser_element: boolean; + same_site: boolean; + scheme_map: number; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: "application:firefox:cookies:entry"; +} + +export interface FirefoxFavicons { + icon_url: string; + expires: string; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "Favicon Expires"; + artifact: "URL Favicon"; + data_type: "application:firefox:favicons:entry"; +} + +export interface FirefoxProfiles { + full_path: string; + version: number; +} + +export interface FirefoxStorage { + repository: Respository; + suffix?: string; + group: string; + origin: string; + client_usages: string; + last_access: string; + accessed: number; + persisted: number; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "Website Storage Last Accessed"; + artifact: "Website Storage"; + data_type: "application:firefox:storage:entry"; +} + +export enum Respository { + Persistent = "Persistent", + Default = "Default", + Private = "Private", + Unknown = "Unknown", + Temporary = "Temporary", +} + +export interface FirefoxAddons { + installed: string; + updated: string; + active: boolean; + visible: boolean; + author: string; + version: string; + path: string; + evidence: string; + message: string; + datetime: string; + name: string; + description: string; + creator: string; + timestamp_desc: "Extension Installed"; + artifact: "Browser Extension"; + data_type: "application:firefox:extension:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/leveldb.md b/artemis-docs/docs/Artifacts/Application Artifacts/leveldb.md index f70861a4..808dc58b 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/leveldb.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/leveldb.md @@ -1,64 +1,64 @@ ---- -description: Open source embedded NoSQL database -keywords: - - database ---- - -# LevelDb - -LevelDb is an open source embedded NoSQL database used by many popular applications. It is commonly used by Chromium based browsers or Electron applications. -Artemis supports parsing and extracting entries from LevelDb. - -# References - -- https://www.cclsolutionsgroup.com/post/hang-on-thats-not-sqlite-chrome-electron-and-leveldb -- https://github.com/libyal/dtformats/blob/main/documentation/LevelDB%20database%20format.asciidoc -- https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/content/browser/indexed_db/leveldb_coding_scheme.md - - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -SQLite information. - -## Sample API Script - -```typescript -import { LevelDb, PlatformType, } from "./artemis-api/mod"; - -function main() { - const info = new LevelDb("Path to leveldb directory", PlatformType.Linux); - console.log(JSON.stringify(info.tables())); -} - -main(); -``` - -## Output Structure - -An array of `LevelDbEntry` entries. - -```typescript -export interface LevelDbEntry { - sequence: number; - key_type: number; - value_type: ValueType; - value: string | number | boolean | unknown[] | Record; - shared_key: string; - origin: string; - key: string; - path: string; -} - -export enum ValueType { - String = "String", - Null = "Null", - Number = "Number", - Date = "Date", - Binary = "Binary", - Array = "Array", - Unknown = "Unknown", - Protobuf = "Protobuf", - Utf16 = "Utf16", -} +--- +description: Open source embedded NoSQL database +keywords: + - database +--- + +# LevelDb + +LevelDb is an open source embedded NoSQL database used by many popular applications. It is commonly used by Chromium based browsers or Electron applications. +Artemis supports parsing and extracting entries from LevelDb. + +# References + +- https://www.cclsolutionsgroup.com/post/hang-on-thats-not-sqlite-chrome-electron-and-leveldb +- https://github.com/libyal/dtformats/blob/main/documentation/LevelDB%20database%20format.asciidoc +- https://chromium.googlesource.com/chromium/src.git/+/62.0.3178.1/content/browser/indexed_db/leveldb_coding_scheme.md + + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +SQLite information. + +## Sample API Script + +```typescript +import { LevelDb, PlatformType, } from "./artemis-api/mod"; + +function main() { + const info = new LevelDb("Path to leveldb directory", PlatformType.Linux); + console.log(JSON.stringify(info.tables())); +} + +main(); +``` + +## Output Structure + +An array of `LevelDbEntry` entries. + +```typescript +export interface LevelDbEntry { + sequence: number; + key_type: number; + value_type: ValueType; + value: string | number | boolean | unknown[] | Record; + shared_key: string; + origin: string; + key: string; + evidence: string; +} + +export enum ValueType { + String = "String", + Null = "Null", + Number = "Number", + Date = "Date", + Binary = "Binary", + Array = "Array", + Unknown = "Unknown", + Protobuf = "Protobuf", + Utf16 = "Utf16", +} ``` \ No newline at end of file diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/libreoffice.md b/artemis-docs/docs/Artifacts/Application Artifacts/libreoffice.md index b1e27fd8..d78de8be 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/libreoffice.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/libreoffice.md @@ -53,7 +53,7 @@ export interface RecentFilesLibreOffice { /**Base64 encoded thumbnail of file */ thumbnail: string; /**Path to registrymodifications.xcu */ - source: string; + evidence: string; message: string; datetime: "1970-01-01T00:00:00.000Z"; timestamp_desc: "N/A"; diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/office.md b/artemis-docs/docs/Artifacts/Application Artifacts/office.md index 6dac03bb..8053573f 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/office.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/office.md @@ -1,49 +1,53 @@ ---- -description: Microsoft Office suite -keywords: - - office software - - plist - - registry ---- - -# Microsoft Office - -Microsoft Office software (Word, PowerPoint, Excel, etc) track recently open -files. Artemis supports parsing Office configurations associated with tracking -recently opened files. - -- macOS: - /Users/\*/Library/Containers/com.microsoft\*/Data/Library/Preferences/com.microsoft.\*.securebookmarks.plist -- Windows: \\Users\\\*\\NTUSER.DAT - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -Microsoft Office information. - -```typescript -import { PlatformType } from "./artemis-api/mod"; -import { officeMruFiles } from "./artemis-api/src/applications/office"; - -function main() { - const results = officeMruFiles(PlatformType.Darwin); - console.log(results); -} -``` - -## Output Structure - -On macOS an array of `OfficeRecentFilesMacos` entries. See -[bookmarks](../macOS%20Artifacts/bookmarks.md) for the format. - -On Windows an array of `OfficeRecentFilesWindows`. - -```typescript -export interface OfficeRecentFilesWindows { - path: string; - last_opened: string; - application: string; - registry_file: string; - key_path: string; -} -``` +--- +description: Microsoft Office suite +keywords: + - office software + - plist + - registry +--- + +# Microsoft Office + +Microsoft Office software (Word, PowerPoint, Excel, etc) track recently open +files. Artemis supports parsing Office configurations associated with tracking +recently opened files. + +- macOS: + /Users/\*/Library/Containers/com.microsoft\*/Data/Library/Preferences/com.microsoft.\*.securebookmarks.plist +- Windows: \\Users\\\*\\NTUSER.DAT + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +Microsoft Office information. + +```typescript +import { PlatformType } from "./artemis-api/mod"; +import { officeMruFiles } from "./artemis-api/src/applications/office"; + +function main() { + const results = officeMruFiles(PlatformType.Darwin); + console.log(results); +} +``` + +## Output Structure + +On macOS an array of `OfficeRecentFilesMacos` entries. See +[bookmarks](../macOS%20Artifacts/bookmarks.md) for the format. + +On Windows an array of `OfficeRecentFilesWindows`. + +```typescript +export interface OfficeRecentFilesWindows { + path: string; + last_opened: string; + application: string; + key_path: string; + timestamp_desc: "Last Opened"; + artifact: "Office Recent File"; + data_type: "application:office:recent:entry"; + message: string; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/onedrive.md b/artemis-docs/docs/Artifacts/Application Artifacts/onedrive.md index 0bac4d91..b8fe9e1a 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/onedrive.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/onedrive.md @@ -1,137 +1,138 @@ ---- -description: Cloud storage software -keywords: - - office software - - sqlite - - registry - - plist ---- - -# OneDrive - -Microsoft OneDrive is a cloud storage service that is used to store files in the -cloud. Artemis supports parsing several artifacts containing OneDrive metadata -such as: - -- Basic support for OneDrive Logs (ODL files, version 3 only) -- SyncDatabase files -- Registry files (NTUSER.DAT) -- PLIST files - -References: - -- [OneDrive blog](http://www.swiftforensics.com/2022/02/reading-onedrive-logs.html) -- [OneDrive blog part 2](http://www.swiftforensics.com/2022/11/reading-onedrive-logs-part-2.html) - -Other Parsers: - -- [OneDrive Explorer](https://github.com/Beercow/OneDriveExplorer) - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -Microsoft OneDrive information. - -```typescript -import { Format, OneDrive, Output, OutputType, PlatformType } from "./artemis-api/mod"; - -function main() { - const results = new OneDrive(PlatformType.Windows); - const output: Output = { - name: "local", - directory: "tmp", - format: Format.JSONL, - compress: false, - timeline: false, - endpoint_id: "", - collection_id: 0, - output: OutputType.LOCAL - }; - - results.retrospect(output); -} - -main(); -``` - -## Output Structure - -Depending on the functions called several objects can be returned - -```typescript -export interface OneDriveLog { - path: string; - filename: string; - created: string; - code_file: string; - function: string; - flags: number; - params: string; - version: string; - os_version: string; - description: string; - message: string; - datetime: string; - timestamp_desc: "OneDrive Log Entry Created"; - artifact: "OneDrive Log"; - data_type: "applications:onedrive:logs:entry"; -} - -export interface OneDriveSyncEngineRecord { - parent_resource_id: string; - resource_id: string; - etag: string; - filename: string; - path: string; - directory: string; - file_status: number | null; - permissions: number | null; - volume_id: number | null; - item_index: number | null; - last_change: string; - size: number | null; - hash_digest: string; - shared_item: string | null; - media_date_taken: string | null; - media_width: number | null; - media_height: number | null; - media_duration: number | null; - /**JSON string */ - graph_metadata: string; - created_by: string; - modified_by: string; - last_write_count: number; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "OneDrive Sync Last Change"; - artifact: "OneDrive Sync Record"; - data_type: "applications:onedrive:sync:entry"; -} - -export interface OneDriveAccount { - email: string; - device_id: string; - account_id: string; - /**Not available on macOS */ - last_signin: string; - cid: string; - message: string; - datetime: string; - timestamp_desc: "OneDrive Last Signin"; - artifact: "OneDrive Account Info"; - data_type: "applications:onedrive:account:entry"; -} - -export interface KeyInfo { - path: string; - key: string; -} - -export interface OnedriveProfile { - sync_db: string[]; - odl_files: string[]; - key_file: string[]; - config_file: string[]; -} -``` +--- +description: Cloud storage software +keywords: + - office software + - sqlite + - registry + - plist +--- + +# OneDrive + +Microsoft OneDrive is a cloud storage service that is used to store files in the +cloud. Artemis supports parsing several artifacts containing OneDrive metadata +such as: + +- Basic support for OneDrive Logs (ODL files, version 3 only) +- SyncDatabase files +- Registry files (NTUSER.DAT) +- PLIST files + +References: + +- [OneDrive blog](http://www.swiftforensics.com/2022/02/reading-onedrive-logs.html) +- [OneDrive blog part 2](http://www.swiftforensics.com/2022/11/reading-onedrive-logs-part-2.html) + +Other Parsers: + +- [OneDrive Explorer](https://github.com/Beercow/OneDriveExplorer) + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +Microsoft OneDrive information. + +```typescript +import { Format, OneDrive, Output, OutputType, PlatformType } from "./artemis-api/mod"; + +function main() { + const results = new OneDrive(PlatformType.Windows); + const output: Output = { + name: "local", + directory: "tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + output: OutputType.LOCAL + }; + + results.retrospect(output); +} + +main(); +``` + +## Output Structure + +Depending on the functions called several objects can be returned + +```typescript +export interface OneDriveLog { + evidence: string; + filename: string; + created: string; + code_file: string; + function: string; + flags: number; + params: string; + version: string; + os_version: string; + description: string; + message: string; + datetime: string; + timestamp_desc: "OneDrive Log Entry Created"; + artifact: "OneDrive Log"; + data_type: "applications:onedrive:logs:entry"; +} + +export interface OneDriveSyncEngineRecord { + parent_resource_id: string; + resource_id: string; + etag: string; + filename: string; + path: string; + directory: string; + file_status: number | null; + permissions: number | null; + volume_id: number | null; + item_index: number | null; + last_change: string; + size: number | null; + hash_digest: string; + shared_item: string | null; + media_date_taken: string | null; + media_width: number | null; + media_height: number | null; + media_duration: number | null; + /**JSON string */ + graph_metadata: string; + created_by: string; + modified_by: string; + last_write_count: number; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "OneDrive Sync Last Change"; + artifact: "OneDrive Sync Record"; + data_type: "applications:onedrive:sync:entry"; +} + +export interface OneDriveAccount { + email: string; + device_id: string; + account_id: string; + /**Not available on macOS */ + last_signin: string; + cid: string; + message: string; + datetime: string; + timestamp_desc: "OneDrive Last Signin"; + artifact: "OneDrive Account Info"; + data_type: "applications:onedrive:account:entry"; + evidence: string; +} + +export interface KeyInfo { + path: string; + key: string; +} + +export interface OnedriveProfile { + sync_db: string[]; + odl_files: string[]; + key_file: string[]; + config_file: string[]; +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/rustdesk.md b/artemis-docs/docs/Artifacts/Application Artifacts/rustdesk.md new file mode 100644 index 00000000..c5bc39f8 --- /dev/null +++ b/artemis-docs/docs/Artifacts/Application Artifacts/rustdesk.md @@ -0,0 +1,51 @@ +--- +description: RustDesk Remote Access Tool +keywords: + - remote access tool + - remote monitoring and management +--- + +# RustDesk + +RustDesk is a popular remote access tool to connect to remote systems. +Artemis supports parsing log files related to RustDesk. + +Other parsers: + +- Any program that can read a text file + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect AnyDesk data + +```typescript +import { PlatformType, RustDesk } from "../../../mod"; + +function main() { + const results = new RustDesk(PlatformType.Linux); + const hits = results.logs(); + + console.log(JSON.stringify(hits)); +} + +main(); +``` + +## Output Structure + +Array of RustDeskLogs + +```typescript +export interface RustDeskLogs { + evidence: string; + message: string; + datetime: string; + level: string; + code_path: string; + local_time: string; + remote_id: string; + timestamp_desc: "Log Event"; + artifact: "RustDesk Log"; + data_type: "applications:rustdesk:log:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/syncthing.md b/artemis-docs/docs/Artifacts/Application Artifacts/syncthing.md index d42163a8..72156424 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/syncthing.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/syncthing.md @@ -1,46 +1,46 @@ ---- -description: An open source peer to peer file synchronization application -keywords: - - cloud storage ---- - -# Syncthing - -Syncthing is a popular open source peer to peer file synchronization application. It can be used to save and share files to a remote devices. - -Artemis supports parsing Syncthing logs on Linux and macOS platforms. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Syncthing client data - - -```typescript -import { PlatformType, Syncthing } from "./artemis-api/mod"; - - -function main() { - const client = new Syncthing(PlatformType.Linux); - const results = client.logs(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -## Output Structure - -Dependent on artifacts the user wants to parse. - -```typescript -export interface SyncthingLogs { - full_path: string; - tag: string; - datetime: string; - timestamp_desc: "Syncthing Log Entry"; - level: string; - message: string; - artifact: "Syncthing Log"; - data_type: "application:syncthing:log:entry"; -} -``` +--- +description: An open source peer to peer file synchronization application +keywords: + - cloud storage +--- + +# Syncthing + +Syncthing is a popular open source peer to peer file synchronization application. It can be used to save and share files to a remote devices. + +Artemis supports parsing Syncthing logs on Linux and macOS platforms. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Syncthing client data + + +```typescript +import { PlatformType, Syncthing } from "./artemis-api/mod"; + + +function main() { + const client = new Syncthing(PlatformType.Linux); + const results = client.logs(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +Dependent on artifacts the user wants to parse. + +```typescript +export interface SyncthingLogs { + evidence: string; + tag: string; + datetime: string; + timestamp_desc: "Syncthing Log Entry"; + level: string; + message: string; + artifact: "Syncthing Log"; + data_type: "application:syncthing:log:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Application Artifacts/vscode.md b/artemis-docs/docs/Artifacts/Application Artifacts/vscode.md index 696e5a2b..5321c2de 100644 --- a/artemis-docs/docs/Artifacts/Application Artifacts/vscode.md +++ b/artemis-docs/docs/Artifacts/Application Artifacts/vscode.md @@ -1,101 +1,101 @@ ---- -description: Open source text editor -keywords: - - text editor - - microsoft ---- - -# VSCode - -VSCode is a popular open source text editor created by Microsoft. Artemis -supports parsing several components from VSCode: -- File History -- Installed extensions -- Recently opened files and folders - -Artemis also supports parsing the [VSCodium](https://vscodium.com/) application. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -VSCode information. - -## Sample API Script - -```typescript -import { - fileHistory, - getExtensions, - PlatformType, -} from "./artemis-api/mod"; - -function main() { - const results = fileHistory(PlatformType.Darwin); - const data = getExtensions(PlatformType.Darwin); - - console.log(results); -} -``` - -## Output Structure - -An array of `FileHistory` for file history, `Extensions` for installed -extensions, `RecentFiles` for recent files and folders - -```typescript -/**History of files in VSCode */ -export interface FileHistory { - /**Version of History format */ - version: number; - /**To source file */ - path?: string; - /**Path to source file */ - resource: string; - /**Path to history source */ - history_path: string; - message: string; - datetime: string; - timestamp_desc: "File Saved"; - artifact: "File History"; - data_type: "applications:vscode:filehistory:entry"; - /**Name of history file */ - id: string; - /**Time when file was saved */ - file_saved: number | string; - /**Based64 encoded file content */ - content?: string; - source?: string; - sourceDescription?: string; - [ key: string ]: unknown; -} -/** - * Metadata related to file history entry - */ -interface Entries { - /**Name of history file */ - id: string; - /**Time when file was saved */ - timestamp: string; - /**Based64 encoded file content */ - content: string; -} - -export interface Extensions { - path: string; - data: Record[]; -} - -export interface RecentFiles { - path_type: RecentType; - path: string; - enabled: boolean; - label: string; - external: string; - storage_path: string; -} - -export enum RecentType { - File = "File", - Folder = "Folder" -} -``` +--- +description: Open source text editor +keywords: + - text editor + - microsoft +--- + +# VSCode + +VSCode is a popular open source text editor created by Microsoft. Artemis +supports parsing several components from VSCode: +- File History +- Installed extensions +- Recently opened files and folders + +Artemis also supports parsing the [VSCodium](https://vscodium.com/) application. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +VSCode information. + +## Sample API Script + +```typescript +import { + fileHistory, + getExtensions, + PlatformType, +} from "./artemis-api/mod"; + +function main() { + const results = fileHistory(PlatformType.Darwin); + const data = getExtensions(PlatformType.Darwin); + + console.log(results); +} +``` + +## Output Structure + +An array of `FileHistory` for file history, `Extensions` for installed +extensions, `RecentFiles` for recent files and folders + +```typescript +/**History of files in VSCode */ +export interface FileHistory { + /**Version of History format */ + version: number; + /**To source file */ + path?: string; + /**Path to source file */ + resource: string; + /**Path to history source */ + evidence: string; + message: string; + datetime: string; + timestamp_desc: "File Saved"; + artifact: "File History"; + data_type: "applications:vscode:filehistory:entry"; + /**Name of history file */ + id: string; + /**Time when file was saved */ + file_saved: number | string; + /**Based64 encoded file content */ + content?: string; + source?: string; + sourceDescription?: string; + [ key: string ]: unknown; +} +/** + * Metadata related to file history entry + */ +interface Entries { + /**Name of history file */ + id: string; + /**Time when file was saved */ + timestamp: string; + /**Based64 encoded file content */ + content: string; +} + +export interface Extensions { + evidence: string; + data: Record[]; +} + +export interface RecentFiles { + path_type: RecentType; + path: string; + enabled: boolean; + label: string; + external: string; + evidence: string; +} + +export enum RecentType { + File = "File", + Folder = "Folder" +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/_category_.json b/artemis-docs/docs/Artifacts/ESXi Artifacts/_category_.json new file mode 100644 index 00000000..e95b9453 --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "ESXi Artifacts", + "position": 15, + "link": { + "type": "generated-index", + "description": "Forensic artifacts for ESXi systems" + } +} \ No newline at end of file diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/accounts.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/accounts.md new file mode 100644 index 00000000..3bc67476 --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/accounts.md @@ -0,0 +1,49 @@ +--- +description: User accounts file +keywords: + - esxi + - plaintext +--- + +# Accounts + +ESXi tracks user account info in the file `/etc/passwd`. Artemis supports extracting account info from the passwd file. + +Other parsers: + +- Any program that can read a text file + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to parse ESXi user accounts. + +```typescript +import { esxiAccounts } from "./artemis-api/mod"; + +function main() { + const results = esxiAccounts(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `Accounts`. + +```typescript +export interface Accounts { + message: string; + datetime: string; + timestamp_desc: "Passwd File Modified"; + artifact: "ESXi User Account"; + data_type: "esxi:users:entry"; + evidence: string; + uid: number; + gid: number; + info: string; + shell: string; + home: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/elf.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/elf.md new file mode 100644 index 00000000..012af026 --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/elf.md @@ -0,0 +1,51 @@ +--- +description: The native executable format for ESXi +keywords: + - esxi + - executable + - binary +--- + +# ELF + +The Executable Linkable Format (ELF) is the executable format for +applications on ESXi systems. + +Artemis is able to parse basic metadata from ELF files. + +Other Parsers: + +- [radare2](https://rada.re/n/) +- [LIEF](https://lief-project.github.io/) + +References: + +- [LIEF](https://lief-project.github.io/) +- [ELF](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format) + +## TOML Collection + +There is no way to collect just ELF data with artemis instead it is an optional +feature for the ESXi [filelisting](./files.md). + +However, it is possible to directly parse ELF files by using TypeScript. See the +[API](../../API/overview.md) documentation for details. + +## Collection Options + +N/A + +## Output Structure + +An array of `ElfInfo` entries + +```typescript +export interface ElfInfo { + /**Array of symbols in ELF binary */ + symbols: string[]; + /**Array of sections in ELF binary */ + sections: string[]; + /**Machine type information in ELF binary */ + machine_type: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/files.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/files.md new file mode 100644 index 00000000..93f4d844 --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/files.md @@ -0,0 +1,130 @@ +--- +description: ESXi file metadata +keywords: + - esxi + - file meta +--- + +# Files + +A regular ESXi filelisting. Artemis uses the +[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files +and directories on the system. + +Since a filelisting can be extremely large, every 10k entries artemis will +output the data and then continue. + +Other Parsers: + +- Any tool that can recursively list files and directories + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "files_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "files" # Name of artifact +[artifacts.files] +start_path = "/usr/bin" # Start of file listing +# Optional +depth = 5 # How many sub directories to descend +# Optional +metadata = true # Get executable metadata +# Optional +md5 = true # MD5 all files +# Optional +sha1 = false # SHA1 all files +# Optional +sha256 = false # SHA256 all files +# Optional +path_regex = "" # Regex for paths +# Optional +file_regex = "" # Regex for files +# Optional +yara = "" # Base64 encoded Yara rule or a remote Yara rule +``` + +## Collection Options + +- `start_path` Where to start the file listing. Must exist on the endpoint. To + start at root use `/`. This configuration is **required** +- `depth` Specify how many directories to descend from the `start_path`. Default + is one (1). Must be a postive number. Max value is 255. This configuration is + **optional** +- `metadata` Get [ELF](elf.md) data from `ELF` files. This configuration is + **optional**. Default is **false** +- `md5` Boolean value to enable MD5 hashing on all files. This configuration is + **optional**. Default is **false** +- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration + is **optional**. Default is **false** +- `sha256` Boolean value to enable SHA256 hashing on all files. This + configuration is **optional**. Default is **false** +- `path_regex` Only descend into paths (directories) that match the provided + regex. This configuration is **optional**. Default is no Regex +- `file_regex` Only return entries that match the provided regex. This + configuration is **optional**. Default is no Regex +- `yara` A base64 encoded Yara rule + +## Output Structure + +An array of `LinuxFileInfo` entries + +```typescript +export interface LinuxFileInfo { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Size of file in bytes */ + size: number; + /**Inode associated with entry */ + inode: number; + /**Mode of file entry */ + mode: number; + /**User ID associated with file */ + uid: number; + /**Group ID associated with file */ + gid: number; + /**MD5 of file */ + md5: string; + /**SHA1 of file */ + sha1: string; + /**SHA256 of file */ + sha256: string; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /**Is the entry a symbolic links */ + is_symlink: boolean; + /**Depth the file from provided start point */ + depth: number; + /**ELF binary metadata */ + binary_info: ElfInfo; +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/shellhistory.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/shellhistory.md new file mode 100644 index 00000000..82c6a96b --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/shellhistory.md @@ -0,0 +1,48 @@ +--- +description: Shell history +keywords: + - esxi + - plaintext +--- + +# Shell History + +ESXi systems track shell history when connected via SSH in the file `/.ash_history`. Artemis supports extracting entries from the .ash_history file. +The ash_history file is cleared on reboot. + +Other parsers: + +- Any program that can read a text file + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to parse ESXi Shell History files. + +```typescript +import { getAshHistory, PlatformType } from "./artemis-api/mod"; + +function main() { + let path = "/.ash_history"; + // Override the default ash_history path normal linux systems. + // ESXi ash history is located at "/.ash_history" + const results = getAshHistory(PlatformType.Linux, path); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `AshHistory`. + +```typescript +export interface AshHistory { + /**Line entry */ + history: string; + /**Line number */ + line: number; + /**Path to `.zsh_history` file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/shelllog.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/shelllog.md new file mode 100644 index 00000000..3dabb452 --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/shelllog.md @@ -0,0 +1,48 @@ +--- +description: Shell log file +keywords: + - esxi + - plaintext +--- + +# Shell Log + +ESXi systems track shell commands executed when connected via SSH in the file shell.log. Artemis supports extracting entries from the shell.log file. + +Other parsers: + +- Any program that can read a text file + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to parse ESXi shell log files. + +```typescript +import { shellLogHistory } from "./artemis-api/mod"; + +function main() { + const results = shellLogHistory(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `ShellHistory`. + +```typescript +export interface ShellHistory { + message: string; + datetime: string; + timestamp_desc: "Shell Command Execution"; + artifact: "ESXi Shell History"; + data_type: "esxi:shell:entry"; + pid: number; + account: string; + command: string; + evidence: string; + category: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/syslog.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/syslog.md new file mode 100644 index 00000000..bffc04ac --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/syslog.md @@ -0,0 +1,47 @@ +--- +description: Syslog file +keywords: + - esxi + - plaintext +--- + +# Syslog Log + +ESXi systems primarily log events to syslog files. Artemis supports extracting entries from syslog.log and gzip compressed syslog files. + +Other parsers: + +- Any program that can read a text file + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to parse ESXi Syslog files. + +```typescript +import { syslogEsxi } from "./artemis-api/mod"; + +function main() { + const results = syslogEsxi(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `Syslog`. + +```typescript +export interface Syslog { + message: string; + datetime: string; + timestamp_desc: "Syslog Entry Generated"; + artifact: "ESXi Syslog"; + data_type: "esxi:syslog:entry"; + pid: number; + evidence: string; + category: string; + process: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/ESXi Artifacts/vib.md b/artemis-docs/docs/Artifacts/ESXi Artifacts/vib.md new file mode 100644 index 00000000..74f78a30 --- /dev/null +++ b/artemis-docs/docs/Artifacts/ESXi Artifacts/vib.md @@ -0,0 +1,63 @@ +--- +description: vSphere Installation Bundles +keywords: + - esxi + - plaintext +--- + +# VIB Packages + +ESXi vSphere Installation Bundles (VIB) are packages used to install applications on ESXi devices. Artemis supports parsing installed VIB packages on ESXi systems. + +Other parsers: + +- Any program that can read a text file + +References: + +- [Whats a vib](https://blogs.vmware.com/cloud-foundation/2011/09/13/whats-in-a-vib/) + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to parse ESXi VIB files. + +```typescript +import { getVibs } from "./artemis-api/mod"; + +function main() { + const results = getVibs(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `VibInfo`. + +```typescript +export interface VibInfo { + message: string; + datetime: string; + timestamp_desc: "VIB Package Installed" | "VIB Package Released"; + artifact: "ESXi VIB Package"; + data_type: "esxi:vib:entry"; + vib_version: number; + install_date: string; + name: string; + version: string; + vendor: string; + summary: string; + description: string; + /**Can be spoofed easily. No guarantee to be UTC*/ + release_date: string; + level: string; + vib_type: string; + payloads: VibPayload[]; + urls: string[]; + evidence: string; + installed: boolean; + timezone: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/_category_.json b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/_category_.json index 1247b943..edb13adf 100644 --- a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/_category_.json +++ b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/_category_.json @@ -1,6 +1,6 @@ { "label": "FreeBSD Artifacts", - "position": 13, + "position": 14, "link": { "type": "generated-index", "description": "Forensic artifacts for FreeBSD systems" diff --git a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/cron.md b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/cron.md index 5d3860ac..d6874eb3 100644 --- a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/cron.md +++ b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/cron.md @@ -14,7 +14,7 @@ supported systems. Other parsers: -- Any program that read a text file +- Any program that can read a text file References: diff --git a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/files.md b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/files.md index f45deedd..ea2fd5a9 100644 --- a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/files.md +++ b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/files.md @@ -1,126 +1,130 @@ ---- -description: FreeBSD file metadata -keywords: - - freebsd ---- - -# Files - -A regular FreeBSD filelisting. Artemis uses the -[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files -and directories on the system. - -Since a filelisting can be extremely large, every 100k entries artemis will -output the data and then continue. - -Other Parsers: - -- Any tool that can recursively list files and directories - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "files_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "files" # Name of artifact -[artifacts.files] -start_path = "/usr/bin" # Start of file listing -# Optional -depth = 5 # How many sub directories to descend -# Optional -metadata = true # Get executable metadata -# Optional -md5 = true # MD5 all files -# Optional -sha1 = false # SHA1 all files -# Optional -sha256 = false # SHA256 all files -# Optional -path_regex = "" # Regex for paths -# Optional -file_regex = "" # Regex for files -``` - -## Collection Options - -- `start_path` Where to start the file listing. Must exist on the endpoint. To - start at root use `/`. This configuration is **required** -- `depth` Specify how many directories to descend from the `start_path`. Default - is one (1). Must be a postive number. Max value is 255. This configuration is - **optional** -- `metadata` Get [ELF](elf.md) data from `ELF` files. This configuration is - **optional**. Default is **false** -- `md5` Boolean value to enable MD5 hashing on all files. This configuration is - **optional**. Default is **false** -- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration - is **optional**. Default is **false** -- `sha256` Boolean value to enable SHA256 hashing on all files. This - configuration is **optional**. Default is **false** -- `path_regex` Only descend into paths (directories) that match the provided - regex. This configuration is **optional**. Default is no Regex -- `file_regex` Only return entries that match the provided regex. This - configuration is **optional**. Default is no Regex - -## Output Structure - -An array of `LinuxFileInfo` entries - -```typescript -export interface LinuxFileInfo { - /**Full path to file or directory */ - full_path: string; - /**Directory path */ - directory: string; - /**Filename */ - filename: string; - /**Extension of file if any */ - extension: string; - /**Created timestamp */ - created: string; - /**Modified timestamp */ - modified: string; - /**Changed timestamp */ - changed: string; - /**Accessed timestamp */ - accessed: string; - /**Size of file in bytes */ - size: number; - /**Inode associated with entry */ - inode: number; - /**Mode of file entry */ - mode: number; - /**User ID associated with file */ - uid: number; - /**Group ID associated with file */ - gid: number; - /**MD5 of file */ - md5: string; - /**SHA1 of file */ - sha1: string; - /**SHA256 of file */ - sha256: string; - /**Is the entry a file */ - is_file: boolean; - /**Is the entry a directory */ - is_directory: boolean; - /**Is the entry a symbolic links */ - is_symlink: boolean; - /**Depth the file from provided start point */ - depth: number; - /**ELF binary metadata */ - binary_info: ElfInfo; -} -``` +--- +description: FreeBSD file metadata +keywords: + - freebsd +--- + +# Files + +A regular FreeBSD filelisting. Artemis uses the +[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files +and directories on the system. + +Since a filelisting can be extremely large, every 10k entries artemis will +output the data and then continue. + +Other Parsers: + +- Any tool that can recursively list files and directories + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "files_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "files" # Name of artifact +[artifacts.files] +start_path = "/usr/bin" # Start of file listing +# Optional +depth = 5 # How many sub directories to descend +# Optional +metadata = true # Get executable metadata +# Optional +md5 = true # MD5 all files +# Optional +sha1 = false # SHA1 all files +# Optional +sha256 = false # SHA256 all files +# Optional +path_regex = "" # Regex for paths +# Optional +file_regex = "" # Regex for files +# Optional +yara = "" # Base64 encoded Yara rule or a remote Yara rule +``` + +## Collection Options + +- `start_path` Where to start the file listing. Must exist on the endpoint. To + start at root use `/`. This configuration is **required** +- `depth` Specify how many directories to descend from the `start_path`. Default + is one (1). Must be a postive number. Max value is 255. This configuration is + **optional** +- `metadata` Get [ELF](elf.md) data from `ELF` files. This configuration is + **optional**. Default is **false** +- `md5` Boolean value to enable MD5 hashing on all files. This configuration is + **optional**. Default is **false** +- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration + is **optional**. Default is **false** +- `sha256` Boolean value to enable SHA256 hashing on all files. This + configuration is **optional**. Default is **false** +- `path_regex` Only descend into paths (directories) that match the provided + regex. This configuration is **optional**. Default is no Regex +- `file_regex` Only return entries that match the provided regex. This + configuration is **optional**. Default is no Regex +# Optional +yara = "" # Base64 encoded Yara rule or a remote Yara rule + +## Output Structure + +An array of `LinuxFileInfo` entries + +```typescript +export interface LinuxFileInfo { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Size of file in bytes */ + size: number; + /**Inode associated with entry */ + inode: number; + /**Mode of file entry */ + mode: number; + /**User ID associated with file */ + uid: number; + /**Group ID associated with file */ + gid: number; + /**MD5 of file */ + md5: string; + /**SHA1 of file */ + sha1: string; + /**SHA256 of file */ + sha256: string; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /**Is the entry a symbolic links */ + is_symlink: boolean; + /**Depth the file from provided start point */ + depth: number; + /**ELF binary metadata */ + binary_info: ElfInfo; +} +``` diff --git a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/shellhistory.md b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/shellhistory.md index 857b79ef..f0fb7220 100644 --- a/artemis-docs/docs/Artifacts/FreeBSD Artifacts/shellhistory.md +++ b/artemis-docs/docs/Artifacts/FreeBSD Artifacts/shellhistory.md @@ -22,7 +22,7 @@ artemis supports parsing zsh and bash shell history. Other parsers: -- Any program that read a text file +- Any program that can read a text file References: @@ -68,6 +68,6 @@ export interface ZshHistory { /**Line number */ line: number; /**Path to `.zsh_history` file */ - path: string; + evidence: string; } ``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/_category_.json b/artemis-docs/docs/Artifacts/Linux Artifacts/_category_.json index 364c7a39..e12f4d32 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/_category_.json +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/_category_.json @@ -1,6 +1,6 @@ { "label": "Linux Artifacts", - "position": 9, + "position": 10, "link": { "type": "generated-index", "description": "Forensic artifacts for Linux systems" diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/abrt.md b/artemis-docs/docs/Artifacts/Linux Artifacts/abrt.md index be536cf1..7147189b 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/abrt.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/abrt.md @@ -41,7 +41,7 @@ export interface Abrt { hostname: string; last_occurrence: string; user: string; - data_directory: string; + evidence: string; backtrace: string | Record; environment: string; home: string; diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/connections.md b/artemis-docs/docs/Artifacts/Linux Artifacts/connections.md index 45bcb016..f5a31081 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/connections.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/connections.md @@ -1,89 +1,89 @@ ---- -description: Linux network connections -keywords: - - linux - - volatile ---- - -# Connections - -Gets a standard network connections listing using the Linux API - -Other Parsers: - -- Any tool that calls the Linux API or can parse the raw Linux memory - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "connections_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "connections" -``` - -## Collection Options - -None - -## Output Structure - -An array of `Connection` entries - -```typescript -export interface Connection { - /**Process ID with connection */ - pid: number; - /**Process name with connection */ - process_name: string; - /**Local IP. Can be either IPv4 or IPv6 */ - local_address: string; - /**Local port used for connection */ - local_port: number; - /**Remote IP. Can be either IPv4 or IPv6 */ - remote_address: string; - /**Remote port used for connection */ - remote_port: number; - /**State of the process connection */ - state: NetworkState; - /**Connection protocol */ - protcol: Protocol; -} - -export enum Protocol { - Tcp = "Tcp", - Udp = "Udp", - Icmp = "Icmp", - Unknown = "Unknown", -} - -export enum NetworkState { - Listen = "Listen", - Established = "Established", - SynRecv = "SynRecv", - SynSent = "SynSent", - FinWait = "FinWait", - FinWait2 = "FinWait2", - TimeWait = "TimeWait", - Close = "Close", - CloseWait = "CloseWait", - LastAck = "LastAck", - Closing = "Closing", - DeleteTcb = "DeleteTcb", - Unknown = "Unknown", - None = "None", -} -} -``` +--- +description: Linux network connections +keywords: + - linux + - volatile +--- + +# Connections + +Gets a standard network connections listing using the Linux API + +Other Parsers: + +- Any tool that calls the Linux API or can parse the raw Linux memory + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "connections_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "connections" +``` + +## Collection Options + +None + +## Output Structure + +An array of `Connection` entries + +```typescript +export interface Connection { + /**Process ID with connection */ + pid: number; + /**Process name with connection */ + process_name: string; + /**Local IP. Can be either IPv4 or IPv6 */ + local_address: string; + /**Local port used for connection */ + local_port: number; + /**Remote IP. Can be either IPv4 or IPv6 */ + remote_address: string; + /**Remote port used for connection */ + remote_port: number; + /**State of the process connection */ + state: NetworkState; + /**Connection protocol */ + protocol: Protocol; +} + +export enum Protocol { + Tcp = "Tcp", + Udp = "Udp", + Icmp = "Icmp", + Unknown = "Unknown", +} + +export enum NetworkState { + Listen = "Listen", + Established = "Established", + SynRecv = "SynRecv", + SynSent = "SynSent", + FinWait = "FinWait", + FinWait2 = "FinWait2", + TimeWait = "TimeWait", + Close = "Close", + CloseWait = "CloseWait", + LastAck = "LastAck", + Closing = "Closing", + DeleteTcb = "DeleteTcb", + Unknown = "Unknown", + None = "None", +} +} +``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/cron.md b/artemis-docs/docs/Artifacts/Linux Artifacts/cron.md index 2dd02754..52eda020 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/cron.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/cron.md @@ -14,7 +14,7 @@ supported systems. Other parsers: -- Any program that read a text file +- Any program that can read a text file References: diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/epiphany.md b/artemis-docs/docs/Artifacts/Linux Artifacts/epiphany.md index 1fabeff4..c5108dc2 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/epiphany.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/epiphany.md @@ -50,7 +50,7 @@ export interface EpiphanyHistory { hidden_from_overview: boolean; visit_type: VisitType; referring_visit: string | null; - db_path: string; + evidence: string; unfold?: Url; } @@ -68,13 +68,13 @@ export interface EpiphanyCookies { is_secure: boolean | null; is_http_only: boolean | null; same_site: boolean | null; - db_path: string; + evidence: string; } export interface EpiphanyPermissions { url: string; permissions: Record; - file_path: string; + evidence: string; } export enum VisitType { @@ -96,6 +96,6 @@ export interface EpiphanyPrint { printer: string; pages: string | number; collate: boolean; - file_path: string; + evidence: string; } ``` \ No newline at end of file diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/falkon.md b/artemis-docs/docs/Artifacts/Linux Artifacts/falkon.md index 37dd71af..edeb2991 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/falkon.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/falkon.md @@ -42,7 +42,7 @@ export interface FalkonHistory { url: string; unfold: Url | undefined; /**Path to the browsedata.db sqlite file */ - db_path: string; + evidence: string; /**Browser version */ version: string; title: string | null; @@ -78,7 +78,7 @@ export interface FalkonCookie { has_cross_site_ancestor: boolean; message: string; /**Path to the Cookies sqlite file */ - db_path: string; + evidence: string; /**Browser version */ version: string; datetime: string; @@ -95,7 +95,7 @@ export interface FalkonBookmark { url: string; visit_count: number; /**Path to the bookmarks.json file */ - path: string; + evidence: string; /**Browser version */ version: string; message: string; diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/files.md b/artemis-docs/docs/Artifacts/Linux Artifacts/files.md index 184df4fe..8c41ceb4 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/files.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/files.md @@ -1,127 +1,130 @@ ---- -description: Linux file metadata -keywords: - - linux - - file meta ---- - -# Files - -A regular Linux filelisting. Artemis uses the -[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files -and directories on the system. - -Since a filelisting can be extremely large, every 100k entries artemis will -output the data and then continue. - -Other Parsers: - -- Any tool that can recursively list files and directories - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "files_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "files" # Name of artifact -[artifacts.files] -start_path = "/usr/bin" # Start of file listing -# Optional -depth = 5 # How many sub directories to descend -# Optional -metadata = true # Get executable metadata -# Optional -md5 = true # MD5 all files -# Optional -sha1 = false # SHA1 all files -# Optional -sha256 = false # SHA256 all files -# Optional -path_regex = "" # Regex for paths -# Optional -file_regex = "" # Regex for files -``` - -## Collection Options - -- `start_path` Where to start the file listing. Must exist on the endpoint. To - start at root use `/`. This configuration is **required** -- `depth` Specify how many directories to descend from the `start_path`. Default - is one (1). Must be a postive number. Max value is 255. This configuration is - **optional** -- `metadata` Get [ELF](elf.md) data from `ELF` files. This configuration is - **optional**. Default is **false** -- `md5` Boolean value to enable MD5 hashing on all files. This configuration is - **optional**. Default is **false** -- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration - is **optional**. Default is **false** -- `sha256` Boolean value to enable SHA256 hashing on all files. This - configuration is **optional**. Default is **false** -- `path_regex` Only descend into paths (directories) that match the provided - regex. This configuration is **optional**. Default is no Regex -- `file_regex` Only return entries that match the provided regex. This - configuration is **optional**. Default is no Regex - -## Output Structure - -An array of `LinuxFileInfo` entries - -```typescript -export interface LinuxFileInfo { - /**Full path to file or directory */ - full_path: string; - /**Directory path */ - directory: string; - /**Filename */ - filename: string; - /**Extension of file if any */ - extension: string; - /**Created timestamp */ - created: string; - /**Modified timestamp */ - modified: string; - /**Changed timestamp */ - changed: string; - /**Accessed timestamp */ - accessed: string; - /**Size of file in bytes */ - size: number; - /**Inode associated with entry */ - inode: number; - /**Mode of file entry */ - mode: number; - /**User ID associated with file */ - uid: number; - /**Group ID associated with file */ - gid: number; - /**MD5 of file */ - md5: string; - /**SHA1 of file */ - sha1: string; - /**SHA256 of file */ - sha256: string; - /**Is the entry a file */ - is_file: boolean; - /**Is the entry a directory */ - is_directory: boolean; - /**Is the entry a symbolic links */ - is_symlink: boolean; - /**Depth the file from provided start point */ - depth: number; - /**ELF binary metadata */ - binary_info: ElfInfo; -} -``` +--- +description: Linux file metadata +keywords: + - linux + - file meta +--- + +# Files + +A regular Linux filelisting. Artemis uses the +[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files +and directories on the system. + +Since a filelisting can be extremely large, every 10k entries artemis will +output the data and then continue. + +Other Parsers: + +- Any tool that can recursively list files and directories + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "files_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "files" # Name of artifact +[artifacts.files] +start_path = "/usr/bin" # Start of file listing +# Optional +depth = 5 # How many sub directories to descend +# Optional +metadata = true # Get executable metadata +# Optional +md5 = true # MD5 all files +# Optional +sha1 = false # SHA1 all files +# Optional +sha256 = false # SHA256 all files +# Optional +path_regex = "" # Regex for paths +# Optional +file_regex = "" # Regex for files +# Optional +yara = "" # Base64 encoded Yara rule or a remote Yara rule +``` + +## Collection Options + +- `start_path` Where to start the file listing. Must exist on the endpoint. To + start at root use `/`. This configuration is **required** +- `depth` Specify how many directories to descend from the `start_path`. Default + is one (1). Must be a postive number. Max value is 255. This configuration is + **optional** +- `metadata` Get [ELF](elf.md) data from `ELF` files. This configuration is + **optional**. Default is **false** +- `md5` Boolean value to enable MD5 hashing on all files. This configuration is + **optional**. Default is **false** +- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration + is **optional**. Default is **false** +- `sha256` Boolean value to enable SHA256 hashing on all files. This + configuration is **optional**. Default is **false** +- `path_regex` Only descend into paths (directories) that match the provided + regex. This configuration is **optional**. Default is no Regex +- `file_regex` Only return entries that match the provided regex. This + configuration is **optional**. Default is no Regex +- `yara` Either a base64 encoded Yara rule or a Yara rule hosted on a remote server + +## Output Structure + +An array of `LinuxFileInfo` entries + +```typescript +export interface LinuxFileInfo { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Size of file in bytes */ + size: number; + /**Inode associated with entry */ + inode: number; + /**Mode of file entry */ + mode: number; + /**User ID associated with file */ + uid: number; + /**Group ID associated with file */ + gid: number; + /**MD5 of file */ + md5: string; + /**SHA1 of file */ + sha1: string; + /**SHA256 of file */ + sha256: string; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /**Is the entry a symbolic links */ + is_symlink: boolean; + /**Depth the file from provided start point */ + depth: number; + /**ELF binary metadata */ + binary_info: ElfInfo; +} +``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/firmware.md b/artemis-docs/docs/Artifacts/Linux Artifacts/firmware.md index 3c8fe74c..77035090 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/firmware.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/firmware.md @@ -58,5 +58,6 @@ export interface FirmwareHistory { timestamp_desc: "Firmware Device Created"; artifact: "Firmware Updates"; data_type: "linux:firmware:entry"; + evidence: string; } ``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/gedit.md b/artemis-docs/docs/Artifacts/Linux Artifacts/gedit.md index 1dd6c8cc..f1be13ba 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/gedit.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/gedit.md @@ -40,7 +40,7 @@ export interface RecentFiles { /**Last accessed */ accessed: string; /**Path to `gedit-metdata.xml` */ - gedit_source: string; + evidence: string; message: string; datetime: string; timestamp_desc: "Last Accessed"; diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeappusage.md b/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeappusage.md index 910f36d7..f359d4c0 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeappusage.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeappusage.md @@ -42,7 +42,7 @@ export interface AppUsage { /**Application last seen timestamp */ "last-seen": string; /**Path to the parsed application_state file */ - source: string; + evidence: string; message: string; datetime: string; timestamp_desc: "Last Seen"; diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeextensions.md b/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeextensions.md index 9c42f837..31290957 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeextensions.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/gnomeextensions.md @@ -41,7 +41,7 @@ An array of `Extension` entries. */ export interface Extension { /**Path to extension metadata.json file */ - extension_path: string; + evidence: string; /**Name of the extension */ name: string; /**Extension description */ diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/gvfs.md b/artemis-docs/docs/Artifacts/Linux Artifacts/gvfs.md index 02df6bb2..89ff2864 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/gvfs.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/gvfs.md @@ -7,7 +7,7 @@ keywords: # GVFS -GNOME Virtual FileSystem (GVFS) is a userspace filesystem that GNOME +GNOME Virtual FileSystem (GVFS) is a user space filesystem that GNOME applications may use. The metadata for the GVFS is typically stored at: `/home/%/.local/share/gvfs-metadata/%`, they are in a binary format and must be parsed. @@ -98,7 +98,7 @@ export interface GvfsEntry { /**Last change timestamp of the **metadata** */ last_change: string; /**GFVS file source */ - source: string; + evidence: string; message: string; datetime: string; timestamp_desc: "Last Changed"; diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/journals.md b/artemis-docs/docs/Artifacts/Linux Artifacts/journals.md index 70391168..11f54de5 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/journals.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/journals.md @@ -1,142 +1,144 @@ ---- -description: Systemd logging files -keywords: - - linux - - logs - - binary ---- - -# Journals - -Linux Journals are the log files associated with the systemd service. Systemd -is a popular system service that is common on most Linux distros. The logs can -contain data related to application activity, sudo commands, and much more. - -Similar to macOS Unified Logs and Windows Event Logs, in order to keep memory -usage low, every 100,000 entries artemis will output the data. - -Other Parsers: - -- None - -References: - -- [Journal format](https://systemd.io/JOURNAL_FILE_FORMAT/) -- [Arch Wiki](https://wiki.archlinux.org/title/Systemd/Journal) - -## TOML Collection - -```toml -[output] -name = "journals_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "journals" -[artifacts.journals] -# Optional -# alt_path = "" -``` - -## Collection Options - -- `alt_path` Path to a directory containing Journal files. This configuration is - **optional** - -## Output Structure - -An array of `Journal` entries - -````typescript -export interface Journal { - /**User ID associated with entry */ - uid: number; - /**Group ID associated with entry */ - gid: number; - /**Process ID associated with entry */ - pid: number; - /**Thread ID associated with entry */ - thread_id: number; - /**Command associated with entry */ - comm: string; - /**Priority associated with entry */ - priority: string; - /**Syslog facility associated with entry */ - syslog_facility: string; - /**Executable file associated with entry */ - executable: string; - /**Cmdline args associated with entry */ - cmdline: string; - /**Effective capabilities of process associated with entry */ - cap_effective: string; - /**Session of the process associated with entry */ - audit_session: number; - /**Login UID of the process associated with entry */ - audit_loginuid: number; - /**Systemd Control Group associated with entry */ - systemd_cgroup: string; - /**Systemd owner UID associated with entry */ - systemd_owner_uid: number; - /**Systemd unit associated with entry */ - systemd_unit: string; - /**Systemd user unit associated with entry */ - systemd_user_unit: string; - /**Systemd slice associated with entry */ - systemd_slice: string; - /**Systemd user slice associated with entry */ - systemd_user_slice: string; - /**Systemd invocation ID associated with entry */ - systemd_invocation_id: string; - /**Kernel Boot ID associated with entry */ - boot_id: string; - /**Machine ID of host associated with entry */ - machine_id: string; - /**Hostname associated with entry */ - hostname: string; - /**Runtime scope associated with entry */ - runtime_scope: string; - /**Source timestamp associated with entry */ - source_realtime: string; - /**Timestamp associated with entry */ - realtime: string; - /**How entry was received by the Journal service */ - transport: string; - /**Journal message entry */ - message: string; - /**Message ID associated with Journal Catalog */ - message_id: string; - /**Unit result associated with entry */ - unit_result: string; - /**Code line for file associated with entry */ - code_line: number; - /**Code function for file associated with entry */ - code_function: string; - /**Code file associated with entry */ - code_file: string; - /**User invocation ID associated with entry */ - user_invocation_id: string; - /**User unit associated with entry */ - user_unit: string; - /** - * Custom fields associated with entry. - * Example: - * ``` - * "custom": { - * "_SOURCE_MONOTONIC_TIMESTAMP": "536995", - * "_UDEV_SYSNAME": "0000:00:1c.3", - * "_KERNEL_DEVICE": "+pci:0000:00:1c.3", - * "_KERNEL_SUBSYSTEM": "pci" - * } - * ``` - */ - custom: Record; - /**Sequence Number associated with entry */ - seqnum: number; -} -```` +--- +description: Systemd logging files +keywords: + - linux + - logs + - binary +--- + +# Journals + +Linux Journals are the log files associated with the systemd service. Systemd +is a popular system service that is common on most Linux distros. The logs can +contain data related to application activity, sudo commands, and much more. + +Similar to macOS Unified Logs and Windows Event Logs, in order to keep memory +usage low, every 100,000 entries artemis will output the data. + +Other Parsers: + +- None + +References: + +- [Journal format](https://systemd.io/JOURNAL_FILE_FORMAT/) +- [Arch Wiki](https://wiki.archlinux.org/title/Systemd/Journal) + +## TOML Collection + +```toml +[output] +name = "journals_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "journals" +[artifacts.journals] +# Optional +# alt_dir = "" +``` + +## Collection Options + +- `alt_dir` Path to a directory containing Journal files. This configuration is + **optional** + +## Output Structure + +An array of `Journal` entries + +````typescript +export interface Journal { + /**User ID associated with entry */ + uid: number; + /**Group ID associated with entry */ + gid: number; + /**Process ID associated with entry */ + pid: number; + /**Thread ID associated with entry */ + thread_id: number; + /**Command associated with entry */ + comm: string; + /**Priority associated with entry */ + priority: string; + /**Syslog facility associated with entry */ + syslog_facility: string; + /**Executable file associated with entry */ + executable: string; + /**Cmdline args associated with entry */ + cmdline: string; + /**Effective capabilities of process associated with entry */ + cap_effective: string; + /**Session of the process associated with entry */ + audit_session: number; + /**Login UID of the process associated with entry */ + audit_loginuid: number; + /**Systemd Control Group associated with entry */ + systemd_cgroup: string; + /**Systemd owner UID associated with entry */ + systemd_owner_uid: number; + /**Systemd unit associated with entry */ + systemd_unit: string; + /**Systemd user unit associated with entry */ + systemd_user_unit: string; + /**Systemd slice associated with entry */ + systemd_slice: string; + /**Systemd user slice associated with entry */ + systemd_user_slice: string; + /**Systemd invocation ID associated with entry */ + systemd_invocation_id: string; + /**Kernel Boot ID associated with entry */ + boot_id: string; + /**Machine ID of host associated with entry */ + machine_id: string; + /**Hostname associated with entry */ + hostname: string; + /**Runtime scope associated with entry */ + runtime_scope: string; + /**Source timestamp associated with entry */ + source_realtime: string; + /**Timestamp associated with entry */ + realtime: string; + /**How entry was received by the Journal service */ + transport: string; + /**Journal message entry */ + message: string; + /**Message ID associated with Journal Catalog */ + message_id: string; + /**Unit result associated with entry */ + unit_result: string; + /**Code line for file associated with entry */ + code_line: number; + /**Code function for file associated with entry */ + code_function: string; + /**Code file associated with entry */ + code_file: string; + /**User invocation ID associated with entry */ + user_invocation_id: string; + /**User unit associated with entry */ + user_unit: string; + /** + * Custom fields associated with entry. + * Example: + * ``` + * "custom": { + * "_SOURCE_MONOTONIC_TIMESTAMP": "536995", + * "_UDEV_SYSNAME": "0000:00:1c.3", + * "_KERNEL_DEVICE": "+pci:0000:00:1c.3", + * "_KERNEL_SUBSYSTEM": "pci" + * } + * ``` + */ + custom: Record; + /**Sequence Number associated with entry */ + seqnum: number; + /**Path to the Journal file */ + evidence: string; +} +```` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/kate.md b/artemis-docs/docs/Artifacts/Linux Artifacts/kate.md index 5a8cdb2b..9ae27012 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/kate.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/kate.md @@ -33,7 +33,7 @@ An array of `RecentFiles` entries. ```typescript export interface RecentFiles { /**Path Kate session file */ - session_file: string; + evidence: string; /**Path to recent file */ file: string; /**Session file created */ diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/logons.md b/artemis-docs/docs/Artifacts/Linux Artifacts/logons.md index 5d30d971..1ddea06e 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/logons.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/logons.md @@ -1,105 +1,107 @@ ---- -description: Linux logon info -keywords: - - linux - - binary - - sqlite ---- - -# Logons - -Linux stores Logon information in several different files depending on the -distro and software installed. Typically the following files contain logon -information on Linux: - -- wtmp - Historical logons -- btmp - Failed logons -- utmp - Users currently logged on -- wtmp.db - Historical logons (Requires Artemis API) - -Other Parsers: - -- N/A - -References: - -- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/Utmp%20login%20records%20format.asciidoc) -- [utmp](https://man7.org/linux/man-pages/man5/utmp.5.html) - -## TOML Collection - -```toml -[output] -name = "logon_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "logons" -[artifacts.logons] -# Optional -# alt_file = "" -``` - -## Collection Options - -- `alt_file` An alternative path to a wtmp, utmp, or btmp file. This - configuration is **optional** - -## Output Structure - -An array of `Logon` entries - -```typescript -export interface Logon { - /**Logon type for logon entry */ - logon_type: string; - /**Process ID */ - pid: number; - /** Terminal info */ - terminal: string; - /**Terminal ID for logon entry */ - terminal_id: number; - /**Username for logon */ - username: string; - /**Hostname for logon source */ - hostname: string; - /**Termination status for logon entry */ - termination_status: number; - /**Exit status logon entry */ - exit_status: number; - /**Session for logon entry */ - session: number; - /**Timestamp for logon */ - timestamp: string; - /**Microseconds for logon */ - microseconds: number; - /**Source IP for logon entry */ - ip: string; - /**Status of logon entry: `Success` or `Failed` */ - status: string; -} - -An array of `LastLogons` entries when querying the wtmp.db file - -export interface LastLogons { - id: number; - type: number; - user: string; - login: string; - logout: string; - tty: string; - remote: string; - service: string; - message: string; - datetime: string; - timestamp_desc: "User Logon"; - artifact: "wtmpdb Logons"; - data_type: "linux:wtmpdb:entry"; -} -``` +--- +description: Linux logon info +keywords: + - linux + - binary + - sqlite +--- + +# Logons + +Linux stores Logon information in several different files depending on the +distro and software installed. Typically the following files contain logon +information on Linux: + +- wtmp - Historical logons +- btmp - Failed logons +- utmp - Users currently logged on +- wtmp.db - Historical logons (Requires Artemis API) + +Other Parsers: + +- N/A + +References: + +- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/Utmp%20login%20records%20format.asciidoc) +- [utmp](https://man7.org/linux/man-pages/man5/utmp.5.html) + +## TOML Collection + +```toml +[output] +name = "logon_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "logons" +[artifacts.logons] +# Optional +# alt_file = "" +``` + +## Collection Options + +- `alt_file` An alternative path to a wtmp, utmp, or btmp file. This + configuration is **optional** + +## Output Structure + +An array of `Logon` entries + +```typescript +export interface Logon { + /**Logon type for logon entry */ + logon_type: string; + /**Process ID */ + pid: number; + /** Terminal info */ + terminal: string; + /**Terminal ID for logon entry */ + terminal_id: number; + /**Username for logon */ + username: string; + /**Hostname for logon source */ + hostname: string; + /**Termination status for logon entry */ + termination_status: number; + /**Exit status logon entry */ + exit_status: number; + /**Session for logon entry */ + session: number; + /**Timestamp for logon */ + timestamp: string; + /**Microseconds for logon */ + microseconds: number; + /**Source IP for logon entry */ + ip: string; + /**Status of logon entry: `Success` or `Failed` */ + status: string; +} + +An array of `LastLogons` entries when querying the wtmp.db file + +export interface LastLogons { + id: number; + type: number; + user: string; + login: string; + logout: string; + tty: string; + remote: string; + service: string; + message: string; + datetime: string; + timestamp_desc: "User Logon"; + artifact: "wtmpdb Logons"; + data_type: "linux:wtmpdb:entry"; + /**Path to the Logon file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/rpm.md b/artemis-docs/docs/Artifacts/Linux Artifacts/rpm.md index 2e721450..f4fb00ac 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/rpm.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/rpm.md @@ -78,6 +78,7 @@ export interface RpmPackages { timestamp_desc: "RPM Package Installed"; artifact: "RPM Package"; data_type: "linux:rpm:entry"; + evidence: string; } ``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/shellhistory.md b/artemis-docs/docs/Artifacts/Linux Artifacts/shellhistory.md index 68b7f717..1432ecf2 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/shellhistory.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/shellhistory.md @@ -22,7 +22,7 @@ Artemis supports parsing zsh and bash shell history. Other parsers: -- Any program that read a text file +- Any program that can read a text file References: @@ -57,7 +57,7 @@ export interface BashHistory { /**Line number */ line: number; /**Path to `.bash_history` file */ - path: string; + evidence: string; } export interface ZshHistory { @@ -68,6 +68,6 @@ export interface ZshHistory { /**Line number */ line: number; /**Path to `.zsh_history` file */ - path: string; + evidence: string; } ``` diff --git a/artemis-docs/docs/Artifacts/Linux Artifacts/sudo.md b/artemis-docs/docs/Artifacts/Linux Artifacts/sudo.md index 6ad3a4e1..2abbf169 100644 --- a/artemis-docs/docs/Artifacts/Linux Artifacts/sudo.md +++ b/artemis-docs/docs/Artifacts/Linux Artifacts/sudo.md @@ -1,53 +1,53 @@ ---- -description: Linux sudo records -keywords: - - linux - - logs - - binary ---- - -# Sudo Logs - -Unix sudo logs are the log files associated with sudo execution. Sudo ("super -user do" or "substitute user") is used to run programs with elevated privileges. - -Linux sudo are stored in the Systemd Journal files. -The log entries -show evidence of commands executed with elevated privileges - -Other Parsers: - -- None - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "sudologs_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "sudologs-linux" -[artifacts.sudologs_linux] -# Optional -# alt_path = "" -``` - -## Collection Options - -- `alt_path` Path to a directory containing Journal files. This configuration is - **optional** - -## Output Structure - -An array of [Journal](./journals.md) entries containing sudo activity +--- +description: Linux sudo records +keywords: + - linux + - logs + - binary +--- + +# Sudo Logs + +Unix sudo logs are the log files associated with sudo execution. Sudo ("super +user do" or "substitute user") is used to run programs with elevated privileges. + +Linux sudo are stored in the Systemd Journal files. +The log entries +show evidence of commands executed with elevated privileges + +Other Parsers: + +- None + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "sudologs_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "sudologs-linux" +[artifacts.sudologs_linux] +# Optional +# alt_dir = "" +``` + +## Collection Options + +- `alt_dir` Path to a directory containing Journal files. This configuration is + **optional** + +## Output Structure + +An array of [Journal](./journals.md) entries containing sudo activity diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/_category_.json b/artemis-docs/docs/Artifacts/Windows Artfacts/_category_.json index 28e5538d..096904bf 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/_category_.json +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/_category_.json @@ -1,6 +1,6 @@ { "label": "Windows Artifacts", - "position": 11, + "position": 12, "link": { "type": "generated-index", "description": "Forensic artifacts for Windows systems" diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/amcache.md b/artemis-docs/docs/Artifacts/Windows Artfacts/amcache.md index 697727ae..6f1c11a1 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/amcache.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/amcache.md @@ -1,103 +1,103 @@ ---- -description: Windows execution tracker -keywords: - - windows - - registry ---- - -# Amcache - -Windows Amcache stores metadata related to execution of Windows applications. -Data is stored in the `C:\Windows\appcompat\Programs\Amcache.hve` Registry file. -This Registry file also contains other metadata such as OS, hardware, and -application info. However, the Amcache artifact will only collect data related -to the possible execution of Windows applications. - -* While an entry in Amcache often implies the application was -executed, Windows may pre-populate Amcache with entries based on a user browsing -to a directory that contains an application. - -You can use the [Registry](./registry.md) artifact to parse the Amcache file if -you want to view other metadata such as OS, hardware, more. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.detection.amcache/) - -References: - -- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/AMCache%20file%20(AMCache.hve)%20format.asciidoc) -- [ANSSI](https://www.ssi.gouv.fr/uploads/2019/01/anssi-coriin_2019-analysis_amcache.pdf) - -## TOML Collection - -```toml -[output] -name = "amcache_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "amcache" -[artifacts.amcache] -# Optional -# alt_file = 'D:\\data\\Amcache.hve' -``` - -## Collection Options - -- `alt_file` Full path to an alternative Amcache.hve file. This configuration is - **optional**. By default artemis will parse the default Amcache.hve on the - system - -## Output Structure - -An array of `Amcache` entries - -```typescript -export interface Amcache { - /**Last modified time for Registry key */ - last_modified: string; - /**Path to application */ - path: string; - /**Name of application */ - name: string; - /**Original name of application from PE metadata */ - original_name: string; - /**Version of application from PE metadata */ - version: string; - /**Executable type and arch information */ - binary_type: string; - /**Application product version from PE metadata */ - product_version: string; - /**Application product name from PE metadata */ - product_name: string; - /**Application language */ - language: string; - /**Application file ID. This is also the SHA1 hash */ - file_id: string; - /**Application linking timestamp as MM/DD/YYYY HH:mm:ss*/ - link_date: string; - /**Hash of application path */ - path_hash: string; - /**Program ID associated with the application */ - program_id: string; - /**Size of application */ - size: string; - /**Application publisher from PE metadata */ - publisher: string; - /**Application Update Sequence Number (USN) */ - usn: string; - /**SHA1 hash of the first ~31MBs of the application */ - sha1: string; - /**Path in the Amcache.hve file */ - reg_path: string; - /**Path to the Amcache.hve file */ - source_path: string; -} -``` +--- +description: Windows execution tracker +keywords: + - windows + - registry +--- + +# Amcache + +Windows Amcache stores metadata related to execution of Windows applications. +Data is stored in the `C:\Windows\appcompat\Programs\Amcache.hve` Registry file. +This Registry file also contains other metadata such as OS, hardware, and +application info. However, the Amcache artifact will only collect data related +to the possible execution of Windows applications. + +* While an entry in Amcache often implies the application was +executed, Windows may pre-populate Amcache with entries based on a user browsing +to a directory that contains an application. + +You can use the [Registry](./registry.md) artifact to parse the Amcache file if +you want to view other metadata such as OS, hardware, more. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.detection.amcache/) + +References: + +- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/AMCache%20file%20(AMCache.hve)%20format.asciidoc) +- [ANSSI](https://www.ssi.gouv.fr/uploads/2019/01/anssi-coriin_2019-analysis_amcache.pdf) + +## TOML Collection + +```toml +[output] +name = "amcache_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "amcache" +[artifacts.amcache] +# Optional +# alt_file = 'D:\\data\\Amcache.hve' +``` + +## Collection Options + +- `alt_file` Full path to an alternative Amcache.hve file. This configuration is + **optional**. By default artemis will parse the default Amcache.hve on the + system + +## Output Structure + +An array of `Amcache` entries + +```typescript +export interface Amcache { + /**Last modified time for Registry key */ + last_modified: string; + /**Path to application */ + path: string; + /**Name of application */ + name: string; + /**Original name of application from PE metadata */ + original_name: string; + /**Version of application from PE metadata */ + version: string; + /**Executable type and arch information */ + binary_type: string; + /**Application product version from PE metadata */ + product_version: string; + /**Application product name from PE metadata */ + product_name: string; + /**Application language */ + language: string; + /**Application file ID. This is also the SHA1 hash */ + file_id: string; + /**Application linking timestamp as MM/DD/YYYY HH:mm:ss*/ + link_date: string; + /**Hash of application path */ + path_hash: string; + /**Program ID associated with the application */ + program_id: string; + /**Size of application */ + size: string; + /**Application publisher from PE metadata */ + publisher: string; + /**Application Update Sequence Number (USN) */ + usn: string; + /**SHA1 hash of the first ~31MBs of the application */ + sha1: string; + /**Path in the Amcache.hve file */ + reg_path: string; + /**Path to the Amcache.hve file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/appcrashes.md b/artemis-docs/docs/Artifacts/Windows Artfacts/appcrashes.md new file mode 100644 index 00000000..9a6e4c5a --- /dev/null +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/appcrashes.md @@ -0,0 +1,46 @@ +--- +description: Windows Application Crashes +keywords: + - windows +--- + +# Application Crashes + +Artemis supports extracting application crash events from the Windows Report.wer files. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +application crashes from Report.wer files. + +## Sample API Script + +```typescript +import { extractAppCrash } from "./artemis-api/mod"; + +function main() { + const results = extractAppCrash(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `AppCrash` + +```typescript +export interface AppCrash { + timestamp_desc: "Application Crash"; + artifact: "AppCrash File"; + data_type: "windows:app:crash:entry"; + evidence: string; + message: string; + path: string; + datetime: string; + report_id: string; + report_type: number; + application_name: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/bam.md b/artemis-docs/docs/Artifacts/Windows Artfacts/bam.md index aff33b91..5c5e79e7 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/bam.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/bam.md @@ -1,47 +1,48 @@ ---- -description: Background Activities Manager -keywords: - - windows - - registry ---- - -# BAM - -Artemis supports extracting Background Activities Manager (BAM) entries from the Windows Registry. BAM entries can record evidence of program execution. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -BAM entries. - -## Sample API Script - -```typescript -import { backgroundActivitiesManager } from "../artemis-api/src/windows/registry/bam"; - -function main() { - const data = backgroundActivitiesManager(); - console.log(JSON.stringify(data)); -} - -main(); -``` - -## Output Structure - -An array of `Bam` - -```typescript -export interface Bam{ - key_path: string; - reg_path: string; - sid: string; - path: string; - last_execution: string; - message: string; - datetime: string; - timestamp_desc: "Last Execution"; - artifact: "Windows Background Activity Monitor"; - data_type: "windows:registry:bam:entry"; -} -``` +--- +description: Background Activities Manager +keywords: + - windows + - registry +--- + +# BAM + +Artemis supports extracting Background Activities Manager (BAM) entries from the Windows Registry. BAM entries can record evidence of program execution. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +BAM entries. + +## Sample API Script + +```typescript +import { backgroundActivitiesManager } from "../artemis-api/src/windows/registry/bam"; + +function main() { + const data = backgroundActivitiesManager(); + console.log(JSON.stringify(data)); +} + +main(); +``` + +## Output Structure + +An array of `Bam` + +```typescript +export interface Bam{ + key_path: string; + reg_path: string; + sid: string; + path: string; + last_execution: string; + message: string; + datetime: string; + timestamp_desc: "Last Execution"; + artifact: "Windows Background Activity Monitor"; + data_type: "windows:registry:bam:entry"; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/bits.md b/artemis-docs/docs/Artifacts/Windows Artfacts/bits.md index 43bc7bb8..ee1c59f7 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/bits.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/bits.md @@ -1,132 +1,134 @@ ---- -description: The Background Intelligent Transer Service (BITS) -keywords: - - windows - - ese - - persistence ---- - -# BITS - -Windows Background Intelligent Transfer Service (BITS) is a service that -allows applications and users to register jobs to upload/download file(s). - -It is commonly used by applications to download updates. Starting on Windows 10 -BITS data is stored in an ESE database. Pre-Windows 10 it is stored in a -proprietary binary format - -BITS data is stored at -- C:\ProgramData\Microsoft\Network\Downloader\qmgr.db - -Other Parsers: - -- [BitsParser](https://github.com/fireeye/BitsParser) -- [Bits_Parser](https://github.com/ANSSI-FR/bits_parser) (Only supports - pre-Windows 10 BITS files) - -References: - -- [BitsAdmin](https://ss64.com/nt/bitsadmin.html) -- [Background Intelligent Transfer Service](https://en.wikipedia.org/wiki/Background_Intelligent_Transfer_Service) -- [BITS](https://www.mandiant.com/resources/blog/attacker-use-of-windows-background-intelligent-transfer-service) - -## TOML Collection - -```toml -[output] -name = "bits_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "bits" -[artifacts.bits] -carve = true -# Optional -# alt_path = "D:\\ProgramData\\Microsoft\\Network\\Downloader\\qmgr.db" -``` - -## Collection Options - -- `carve` Boolean value to carve deleted BITS jobs and files from qmgr.db -- `alt_path` Use an alternative path to the qmgr.db file. This configuration - is **optional**. By default artemis will use - **%systemdrive%\ProgramData\Microsoft\Network\Downloader\qmgr.db** - -## Output Structure - -An array of `BitsInfo` - -```typescript -export interface BitsInfo { - /**ID for the Job */ - job_id: string; - /**ID for the File */ - file_id: string; - /**SID associated with the Job */ - owner_sid: string; - /**Timestamp when the Job was created */ - created: string; - /**Timestamp when the Job was modified */ - modified: string; - /**Timestamp when the Job was completed */ - completed: string; - /**Timestamp when the Job was expired */ - expiration: string; - /**Number of bytes downloaded */ - bytes_downloaded: number | bigint | string; - /**Number of bytes transferred */ - bytes_transferred: number | bigint | string; - /**Name associated with Job */ - job_name: string; - /**Description associated with Job */ - job_description: string; - /**Commands associated with Job */ - job_command: string; - /**Arguments associated with Job */ - job_arguments: string; - /**Error count with the Job */ - error_count: number; - /**BITS Job type */ - job_type: string; - /**BITS Job state */ - job_state: string; - /**Job priority */ - priority: string; - /**BITS Job flags */ - flags: string; - /**HTTP Method associated with Job */ - http_method: string; - /**Full file path associated with Job */ - full_path: string; - /**Filename associated with Job */ - filename: string; - /**Target file path associated with Job */ - target_path: string; - /**Volume path associated with the file */ - volume: string; - /**URL associated with the Job */ - url: string; - /**If the BITS info was carved */ - carved: boolean; - /**Transient error count with Job */ - transient_error_count: number; - /**Permissions associated with the Job */ - acls: AccessControl[]; - /**Job timeout in seconds */ - timeout: number; - /**Job retry delay in seconds */ - retry_delay: number; - /**Additional SIDs associated with Job */ - additional_sids: string[]; - /**Drive associated with the BITS Job */ - drive: string; - /**Temporary file path for the file download */ - tmp_fullpath: string; -} -``` +--- +description: The Background Intelligent Transer Service (BITS) +keywords: + - windows + - ese + - persistence +--- + +# BITS + +Windows Background Intelligent Transfer Service (BITS) is a service that +allows applications and users to register jobs to upload/download file(s). + +It is commonly used by applications to download updates. Starting on Windows 10 +BITS data is stored in an ESE database. Pre-Windows 10 it is stored in a +proprietary binary format + +BITS data is stored at +- C:\ProgramData\Microsoft\Network\Downloader\qmgr.db + +Other Parsers: + +- [BitsParser](https://github.com/fireeye/BitsParser) +- [Bits_Parser](https://github.com/ANSSI-FR/bits_parser) (Only supports + pre-Windows 10 BITS files) + +References: + +- [BitsAdmin](https://ss64.com/nt/bitsadmin.html) +- [Background Intelligent Transfer Service](https://en.wikipedia.org/wiki/Background_Intelligent_Transfer_Service) +- [BITS](https://www.mandiant.com/resources/blog/attacker-use-of-windows-background-intelligent-transfer-service) + +## TOML Collection + +```toml +[output] +name = "bits_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "bits" +[artifacts.bits] +carve = true +# Optional +# alt_file = "D:\\ProgramData\\Microsoft\\Network\\Downloader\\qmgr.db" +``` + +## Collection Options + +- `carve` Boolean value to carve deleted BITS jobs and files from qmgr.db +- `alt_file` Use an alternative path to the qmgr.db file. This configuration + is **optional**. By default artemis will use + **%systemdrive%\ProgramData\Microsoft\Network\Downloader\qmgr.db** + +## Output Structure + +An array of `BitsInfo` + +```typescript +export interface BitsInfo { + /**ID for the Job */ + job_id: string; + /**ID for the File */ + file_id: string; + /**SID associated with the Job */ + owner_sid: string; + /**Timestamp when the Job was created */ + created: string; + /**Timestamp when the Job was modified */ + modified: string; + /**Timestamp when the Job was completed */ + completed: string; + /**Timestamp when the Job was expired */ + expiration: string; + /**Number of bytes downloaded */ + bytes_downloaded: number | bigint | string; + /**Number of bytes transferred */ + bytes_transferred: number | bigint | string; + /**Name associated with Job */ + job_name: string; + /**Description associated with Job */ + job_description: string; + /**Commands associated with Job */ + job_command: string; + /**Arguments associated with Job */ + job_arguments: string; + /**Error count with the Job */ + error_count: number; + /**BITS Job type */ + job_type: string; + /**BITS Job state */ + job_state: string; + /**Job priority */ + priority: string; + /**BITS Job flags */ + flags: string; + /**HTTP Method associated with Job */ + http_method: string; + /**Full file path associated with Job */ + full_path: string; + /**Filename associated with Job */ + filename: string; + /**Target file path associated with Job */ + target_path: string; + /**Volume path associated with the file */ + volume: string; + /**URL associated with the Job */ + url: string; + /**If the BITS info was carved */ + carved: boolean; + /**Transient error count with Job */ + transient_error_count: number; + /**Permissions associated with the Job */ + acls: AccessControl[]; + /**Job timeout in seconds */ + timeout: number; + /**Job retry delay in seconds */ + retry_delay: number; + /**Additional SIDs associated with Job */ + additional_sids: string[]; + /**Drive associated with the BITS Job */ + drive: string; + /**Temporary file path for the file download */ + tmp_fullpath: string; + /**Path to the BITS database */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/bits_event.md b/artemis-docs/docs/Artifacts/Windows Artfacts/bits_event.md new file mode 100644 index 00000000..ac76d7fe --- /dev/null +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/bits_event.md @@ -0,0 +1,63 @@ +--- +description: Windows BITS Job events +keywords: + - windows + - eventlogs +--- + +# BITS Job Event + +Artemis supports extracting BITS Job events from the Windows EventLog +Microsoft-Windows-Bits-Client%4Operational.evtx file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +BITS Job EventLog entries. + +## Sample API Script + +```typescript +import { bitsEvents } from "./artemis-api/mod"; + +function main() { + const results = bitsEvents(); + console.log(JSON.stringify(results)); + +} + +main(); +``` + +## Output Structure + +An array of `BitsEvent` + +```typescript +export interface BitsEvent { + status: BitsState; + evidence: string; + job_id: string; + process: string; + pid: number; + user: string; + title: string; + message: string; + datetime: string; + file_count: number; + provider: string; + event_id: number; + bits_event_time: string; + activity_id: string; + thread_id: number; + bytes_transferred: number; + timestamp_desc: "BITS Job Created" | "BITS Job Completed"; + artifact: "BITS EventLog"; + data_type: "windows:eventlogs:bits:entry"; +} + +export enum BitsState { + Completed = "Complete", + Created = "Created", +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/certificates.md b/artemis-docs/docs/Artifacts/Windows Artfacts/certificates.md index 3a0091f8..aa4761b7 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/certificates.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/certificates.md @@ -86,7 +86,7 @@ export enum RequestType { /** * Various status codes for certificate requests - * There are [alot](https://github.com/fox-it/dissect.database/pull/7/files#diff-60ccdb935072fd3aa71d6810f7c3aa7ca8ef42aadc6ce9e39ed5f54c5cc36d31R28) + * There are [a lot](https://github.com/fox-it/dissect.database/pull/7/files#diff-60ccdb935072fd3aa71d6810f7c3aa7ca8ef42aadc6ce9e39ed5f54c5cc36d31R28) * Only some listed so far */ export enum StatusCode { @@ -94,7 +94,7 @@ export enum StatusCode { BadRequestSubject = "Bad Request Subject", NoRequest = "No Request", Empty = "Property Empty", - InvalidCA = "Invalid CA Certificte", + InvalidCA = "Invalid CA Certificate", Suspended = "Server Suspended", EncodingLength = "Encoding Length", Conflict = "Role Conflict", diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/connections.md b/artemis-docs/docs/Artifacts/Windows Artfacts/connections.md index 89410b6a..c9c6a69b 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/connections.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/connections.md @@ -59,7 +59,7 @@ export interface Connection { /**State of the process connection */ state: NetworkState; /**Connection protocol */ - protcol: Protocol; + protocol: Protocol; } export enum Protocol { @@ -85,5 +85,4 @@ export enum NetworkState { Unknown = "Unknown", None = "None", } -} ``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/crash_event.md b/artemis-docs/docs/Artifacts/Windows Artfacts/crash_event.md new file mode 100644 index 00000000..22b49b76 --- /dev/null +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/crash_event.md @@ -0,0 +1,55 @@ +--- +description: Windows Application Crash events +keywords: + - windows + - eventlogs +--- + +# Crash Event + +Artemis supports extracting application crash events from the Windows EventLog +Microsoft-Windows-WER-Diag%4Operational.evtx file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +application crashes from EventLog. + +## Sample API Script + +```typescript +import { crashEvents } from "./artemis-api/mod"; + +function main() { + const results = crashEvents(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `CrashEvent` + +```typescript +export interface CrashEvent { + evidence: string; + pid: number; + path: string; + application_start: string; + crash_time: string; + crash_time_from_start: number; + hostname: string; + provider: string; + guid: string; + channel: string; + sid: string; + trigger: string; + message: string; + datetime: string; + timestamp_desc: "Application Crash"; + artifact: "Crash EventLog"; + data_type: "windows:eventlogs:crash:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/defender.md b/artemis-docs/docs/Artifacts/Windows Artfacts/defender.md index bb4b3388..147913f9 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/defender.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/defender.md @@ -13,7 +13,7 @@ Microsoft-Windows-Windows Defender%4Operational.evtx file. ## Collection You have to use the artemis [api](../../API/overview.md) in order to collect -Logon entries. +Defender Quarantine entries. ## Sample API Script diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/eventlog_providers.md b/artemis-docs/docs/Artifacts/Windows Artfacts/eventlog_providers.md index 43dfdb64..905861f1 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/eventlog_providers.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/eventlog_providers.md @@ -1,59 +1,59 @@ ---- -description: Windows EventLog Providers -keywords: - - windows - - registry ---- - -# EventLog Providers - -Artemis supports extracting the registered Windows EventLog providers from the Windows SYSTEM and SOFTWARE Registry files. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect EventLog providers rules. - -## Sample API Script - -```typescript -import { getEventlogProviders } from "./artemis-api/mod"; -function main() { - const events = getEventlogProviders(); - console.log(JSON.stringify(events)); - -} - -main(); -``` - -## Output Structure - -An array of `RegistryEventlogProviders` - -```typescript -export interface RegistryEventlogProviders { - registry_file: string; - key_path: string; - name: string; - channel_names: string[]; - message_file: string; - last_modified: string; - parameter_file: string; - guid: string; - enabled: boolean; - channel_types: ChannelType[]; - message: string; - datetime: string; - timestamp_desc: "Registry Last Modified"; - artifact: "Windows EventLog Provider"; - data_type: "windows:registry:eventlogprovider:entry"; -} - -export enum ChannelType { - Admin = "Admin", - Operational = "Operational", - Analytic = "Analytic", - Debug = "Debug", - Unknown = "Unknown", -} -``` +--- +description: Windows EventLog Providers +keywords: + - windows + - registry +--- + +# EventLog Providers + +Artemis supports extracting the registered Windows EventLog providers from the Windows SYSTEM and SOFTWARE Registry files. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect EventLog providers rules. + +## Sample API Script + +```typescript +import { getEventlogProviders } from "./artemis-api/mod"; +function main() { + const events = getEventlogProviders(); + console.log(JSON.stringify(events)); + +} + +main(); +``` + +## Output Structure + +An array of `RegistryEventlogProviders` + +```typescript +export interface RegistryEventlogProviders { + evidence: string; + key_path: string; + name: string; + channel_names: string[]; + message_file: string; + last_modified: string; + parameter_file: string; + guid: string; + enabled: boolean; + channel_types: ChannelType[]; + message: string; + datetime: string; + timestamp_desc: "Registry Last Modified"; + artifact: "Windows EventLog Provider"; + data_type: "windows:registry:eventlogprovider:entry"; +} + +export enum ChannelType { + Admin = "Admin", + Operational = "Operational", + Analytic = "Analytic", + Debug = "Debug", + Unknown = "Unknown", +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/eventlogs.md b/artemis-docs/docs/Artifacts/Windows Artfacts/eventlogs.md index 1809881e..d29755ee 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/eventlogs.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/eventlogs.md @@ -1,458 +1,460 @@ ---- -description: Primary source of logs on Windows -keywords: - - windows - - logs - - binary ---- - -# Event Logs - -Windows EventLogs are the primary files associated with logging system -activity. They are stored in a binary format, typically at -C:\Windows\System32\winevt\Logs - -Artemis also has the capability to extract EventLog template data from PE files. -This is a powerful (but complex) feature that allows an analyst to potentially -obtain the full EventLog message instead of just the data found in evtx files. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.eventlogs.evtx/) - -References: - -- [Libyal](https://github.com/libyal/libevtx/blob/main/documentation/Windows%20XML%20Event%20Log%20(EVTX).asciidoc) - -## TOML Collection - -```toml -[output] -name = "eventlog_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "eventlogs" -[artifacts.eventlogs] -# Optional -# alt_file = "C:\\Artifacts\\Security.evtx" -# alt_dir = "C:\\LogFiles" # Optional -# alt_template_file = "C:\\Resources\\templates.json" # Optional -include_templates = false -dump_templates = false # Works only on Windows -only_templates = false # Works only on Windows -``` - -## Collection Options - -- `alt_file` Full path to an EventLog file. This configuration is **optional**. - By default artemis will parse all Event Logs on the system under the default - path -- `alt_dir` Full path to a directory containing EventLogs. This configuration is - **optional**. -- `include_templates` Whether artemis should parse PE files and extract EventLog - template strings. This configuration is **required**. -- `dump_templates` Whether artemis should output the parsed EventLog template - files. This output file can then be used to the evtx files on a different - system. This configuration is **required**. -- `only_templates` Whether artemis should only output the parsed EventLog - template files and skip evtx files. This configuration is **required**. - -## EventLog Providers ands Template Parsing - -Artemis uses the popular [evtx](https://github.com/omerbenamram/evtx) crate to -parse EventLog files. However, this library does not completely return the log -message. It only returns the data found in the EventLog file. For example, the -evtx crate will output the following message data from an EventLog file - -```json -"EventData": { - "SubjectUserSid": "S-1-5-21-549467458-3727351111-1684278619-1001", - "SubjectUserName": "bob", - "SubjectDomainName": "DESKTOP-9FSUKAJ", - "SubjectLogonId": "0x3311b1", - "TargetName": "MicrosoftAccount:user=testemail@outlook.com", - "Type": 0, - "CountOfCredentialsReturned": 1, - "ReadOperation": "%%8100", - "ReturnCode": 0, - "ProcessCreationTime": "2024-10-01T02:49:28.150359Z", - "ClientProcessId": 1848 -} -``` - -Now compared to the Event Viewer on Windows - -``` -Credential Manager credentials were read. - -Subject: - Security ID: DESKTOP-9FSUKAJ\bob - Account Name: bob - Account Domain: DESKTOP-9FSUKAJ - Logon ID: 0x3311b1 - Read Operation: Enumerate Credentials - -This event occurs when a user performs a read operation on stored credentials in Credential Manager. -``` - -In order to get the entire EventLog message, we need to parse EventLog provider -files (PE files) and extract resource data. The Windows Registry contains -information about EventLog providers. If you choose to enable EventLog Provider parsing, artemis will perform the following high -level actions to attempt to extract EventLog template strings: - -1. Read and parse the SOFTWARE and SYSTEM Registry files to identify EventLog - providers. The Registry files point to PE files that contain EventLog Provider template strings. -2. Next read and parse all PE files associated with EventLog providers -3. Extract and parse the MUI, MESSAGETABLE, and WEVT_TEMPLATE resources from the PE - files -4. Attempt to use: MESSAGETABLE, WEVT_TEMPLATE, and the evtx EventLog data to - assemble the full EventLog message - -If parsing is successful artemis should return a more detailed EventLog message -if you use the `include_templates` option 🥳 : - -``` -Credential Manager credentials were read. - -Subject: - Security ID: S-1-5-21-549467458-3727351111-1684278619-1001 - Account Name: bob - Account Domain: DESKTOP-9FSUKAJ - Logon ID: 0x3311b1 - Read Operation: Enumerate Credentials - - -This event occurs when a user performs a read operation on stored credentials in Credential Manager. -``` - -### Template Parsing Caveats - -Trying to include template strings in the EventLog messages is very complex. -There are a number of caveats and limitations you should be aware of. More can -also be found in several -[velociratpor](https://docs.velociraptor.app/blog/2019/2019-11-12_windows-event-logs-d8d8e615c9ca/) -[blogs](https://docs.velociraptor.app/docs/forensic/event_logs/) - -1. No support for enum lookups. Artemis cannot lookup enum EventLog data values. - Ex: `IntendedPackageState - 5112`. The enum number 5512 - is converted to the value "Installed" in Event Viewer -2. You should not try parsing EventLog files on different Windows platforms. For - example, if you acquire a `Security.evtx` on a Windows 11 system, you should - not try to parse the evtx file on a Windows 10 system. Microsoft updates the - template strings on different versions of Windows. If you try to parse evtx - files on a different version of Windows you may get odd results! - - If you do try to parse an evtx file on a different artemis will still try - to complete the EventLog message, but if it fails it will return the raw - EventData - - It may ok to parse older evtx on newer Windows versions. For example, - acquiring `System.evtx` on Windows 10 and then parsing on Windows 11 may be - ok. Typically Windows will also contain older versions of template strings. -3. If you acquire only an evtx file such as `System.evtx` you must use a Windows - system (or VM) in order to include template strings. You cannot use Linux or - macOS. If you want to include template strings on Linux or macOS you must - also provide a template file - -### Template Files (aka EventLog Provider strings) - -The `dump_templates` option will make artemis dump the parsed template strings from EventLog Providers on a Windows system -to a JSON file. This option also requires `include_templates`. You must be on a -Windows system in order for this to work. - -:::info - -To include the combine EventLog message you would run the following on -Windows.\ -`artemis.exe acquire eventlogs --include-templates` - -If you only want a template JSON file (and not evtx data) you can run the following -on Windows.\ -`artemis.exe acquire eventlogs --include-templates --dump-templates --only-templates` -::: - -Dumping the template data will return a single JSON file that can then be used -to parse evtx files on different platforms. An example scenario where this could be useful: - -1. You dump template strings on a Windows server via - `artemis.exe acquire eventlogs --include-templates --dump-templates --only-templates`. - The JSON file size will vary but ~90MB seems typical -2. You acquire the template JSON file and move it to your Linux workstation -3. You acquire a 2GB Application.evtx file from the **same** Windows server - and copy it to your Linux workstation -4. Run - `artemis acquire eventlogs --include-templates --alt-template-file --alt-file ` - on Linux and you should hopefully get the full EventLog message! - -If artemis fails to assemble the full EventLog message for any reason, it will -fallback to the raw EventLog data obtained from the evtx file. - -## Output Structure - -Depending on options provided there are several different structures that -artemis can produce. - -By default artemis will return an array of `EventLogRecord` entries. This is the -raw data obtained from the evtx file. Artemis will also fallback to -EventLogRecord entries if it fails to create an `EventLogMessage` structure - -````typescript -export interface EventLogRecord { - /**Event record number */ - record_id: number; - /**Timestamp of eventlog message */ - timestamp: string; - /** - * JSON object representation of the Eventlog message - * Depending on the log the JSON object may have different types of keys - * Example entry: - * ``` - * "data": { - * "Event": { - * "#attributes": { - * "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" - * }, - * "System": { - * "Provider": { - * "#attributes": { - * "Name": "Microsoft-Windows-Bits-Client", - * "Guid": "EF1CC15B-46C1-414E-BB95-E76B077BD51E" - * } - * }, - * "EventID": 3, - * "Version": 3, - * "Level": 4, - * "Task": 0, - * "Opcode": 0, - * "Keywords": "0x4000000000000000", - * "TimeCreated": { - * "#attributes": { - * "SystemTime": "2022-10-31T04:24:19.946430Z" - * } - * }, - * "EventRecordID": 2, - * "Correlation": null, - * "Execution": { - * "#attributes": { - * "ProcessID": 1332, - * "ThreadID": 780 - * } - * }, - * "Channel": "Microsoft-Windows-Bits-Client/Operational", - * "Computer": "DESKTOP-EIS938N", - * "Security": { - * "#attributes": { - * "UserID": "S-1-5-18" - * } - * } - * }, - * "EventData": { - * "jobTitle": "Font Download", - * "jobId": "174718A5-F630-43D9-B378-728240ECE152", - * "jobOwner": "NT AUTHORITY\\LOCAL SERVICE", - * "processPath": "C:\\Windows\\System32\\svchost.exe", - * "processId": 1456, - * "ClientProcessStartKey": 844424930132016 - * } - * } - * } - * ``` - */ - data: Record; -} -```` - -If you choose to include template strings, artemis will output `EventLogMessage` -structure. - -```typescript -/** - * Parsed EventLog files with template strings included. A flatten version of `EventLogRecord` - */ -export interface EventLogMessage { - /**Full EventLog message rendered using both the template string and evtx data */ - message: string; - /**The raw template string */ - template_message: string; - /**The raw evtx event data. Ex: `EventData` or `UserData` */ - raw_event_data: Record; - /**EventID for the entry */ - event_id: bigint; - /**Qualifier ID for the entry */ - qualifier: bigint; - /**Version number for the entry */ - version: bigint; - /**GUID associated with the provider */ - guid: string; - /**EventLog provider name */ - provider: string; - /**Alternative provider name */ - source_name: string; - /**EventLog record number */ - record_id: bigint; - /**Task number for entry */ - task: bigint; - /**EventLog level value */ - level: string; - /**Opcode number for entry */ - opcode: bigint; - /**Keywords value for entry. Is a hex number */ - keywords: string; - /**Generated timestamp for entry */ - generated: string; - /**System timestamp for entry */ - system_time: string; - /**Activity ID for entry if available */ - activity_id: string; - /**Process ID for entry if available */ - process_id: bigint; - /**Thread ID for entry if available */ - thread_id: bigint; - /**SID value for entry if available */ - sid: string; - /**Channel name for entry */ - channel: string; - /**Hostname of system */ - computer: string; - /**Full path the evtx file that was parsed */ - source_file: string; - /**Full path to the PE file that was used to obtain the template string */ - message_file: string; - /**Full path to the PE file containing parameters for the entry */ - parameter_file: string; - /**Source Registry file used to get provider info */ - registry_file: string; - /**Registry key path to the provider info */ - registry_path: string; -} -``` - -If you choose to dump template strings to a JSON file, artemis will output a -`TemplateStrings` structure: - -```typescript -/** - * A complex structure that represents the parsed EventLog template strings. - * Can be used to create a full EventLog message - */ -export interface TemplateStrings { - /**Object containing Template provider info. Key is the provider name or a GUID */ - providers: Record; - /**Object containing Template string info. Key is the path to the PE file */ - templates: Record; -} - -/**Information about the EventLog provider */ -export interface Provider { - /**Source Registry file used to get provider info */ - registry_file_path: string; - /**Registry key path to the provider info */ - registry_path: string; - /**Name of provider. Might be a GUID */ - name: string; - /**Array of PE files that point to the template strings*/ - message_file: string[]; - /**Array of PE files that point to the parameter values for the provider. So far this seems to always be one file */ - parameter_file: string[]; -} - -/**The parsed WEVT_TEMPLATE info */ -export interface TemplateInfo { - /** Path to PE file */ - path: string; - /**Internal data used by artemis when parsing the PE data. These arrays will always be empty */ - resource_data: { - mui_data: number[]; - wevt_data: number[]; - message_data: number[]; - /** Path to PE file */ - path: string; - }; - /**Info related to the template message string. Key is the `MessageTable` ID */ - message_table: Record; - /**Extreme details on the EventLog provider template. Key is a GUID */ - wevt_template: Record; -} - -export interface MessageTable { - id: bigint; - size: number; - flags: string; - message: string; -} - -export interface WevtTemplate { - offset: number; - element_offsets: number[]; - channels: TemplateData[]; - keywords: TemplateData[]; - opcodes: TemplateData[]; - levels: TemplateData[]; - maps: Maps[]; - tasks: TemplateData[]; - /**Information related to a EventID associated with a provider. Key is combination of the `Definition` ID and version. Ex: The key: `100_0` would be EventID 100 version 0 */ - definitions: Record; -} - -export interface Definition { - /**EventID. Makes up the `definitions` key along with the `version` */ - id: number; - /**Version number of the definition object. Makes up the `definitions` key along with the `id` */ - version: number; - level: number; - opcode: number; - task: number; - keywords: number; - message_id: bigint; - temp_offset: number; - template: Template | null; - opcode_offset: number; - level_offset: number; - task_offset: number; -} - -export interface Template { - template_id: string; - event_data_type: string; - elements: { - token: string; - token_number: number; - dependency_id: number; - size: number; - attribute_list: { - attribute_token: string; - attribute_token_number: number; - value: string; - value_token: string; - value_token_number: number; - name: string; - input_type: string; - substitution: string; - substitution_id: number; - }[] | null; - element_name: string; - input_type: string; - substitution: string; - substitution_id: 0; - }[]; - guid: string; -} - -export interface TemplateData { - message_id: bigint; - id: number; - value: string; - /**Only `Tasks` have this value */ - guid: string | undefined; -} - -export interface Maps { - name: string; - data: Record; -} -``` +--- +description: Primary source of logs on Windows +keywords: + - windows + - logs + - binary +--- + +# Event Logs + +Windows EventLogs are the primary files associated with logging system +activity. They are stored in a binary format, typically at +C:\Windows\System32\winevt\Logs + +Artemis also has the capability to extract EventLog template data from PE files. +This is a powerful (but complex) feature that allows an analyst to potentially +obtain the full EventLog message instead of just the data found in evtx files. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.eventlogs.evtx/) + +References: + +- [Libyal](https://github.com/libyal/libevtx/blob/main/documentation/Windows%20XML%20Event%20Log%20(EVTX).asciidoc) + +## TOML Collection + +```toml +[output] +name = "eventlog_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "eventlogs" +[artifacts.eventlogs] +# Optional +# alt_file = "C:\\Artifacts\\Security.evtx" +# alt_dir = "C:\\LogFiles" # Optional +# alt_template_file = "C:\\Resources\\templates.json" # Optional +include_templates = false +dump_templates = false # Works only on Windows +only_templates = false # Works only on Windows +``` + +## Collection Options + +- `alt_file` Full path to an EventLog file. This configuration is **optional**. + By default artemis will parse all Event Logs on the system under the default + path +- `alt_dir` Full path to a directory containing EventLogs. This configuration is + **optional**. +- `include_templates` Whether artemis should parse PE files and extract EventLog + template strings. This configuration is **required**. +- `dump_templates` Whether artemis should output the parsed EventLog template + files. This output file can then be used to the evtx files on a different + system. This configuration is **required**. +- `only_templates` Whether artemis should only output the parsed EventLog + template files and skip evtx files. This configuration is **required**. + +## EventLog Providers ands Template Parsing + +Artemis uses the popular [evtx](https://github.com/omerbenamram/evtx) crate to +parse EventLog files. However, this library does not completely return the log +message. It only returns the data found in the EventLog file. For example, the +evtx crate will output the following message data from an EventLog file + +```json +"EventData": { + "SubjectUserSid": "S-1-5-21-549467458-3727351111-1684278619-1001", + "SubjectUserName": "bob", + "SubjectDomainName": "DESKTOP-9FSUKAJ", + "SubjectLogonId": "0x3311b1", + "TargetName": "MicrosoftAccount:user=testemail@outlook.com", + "Type": 0, + "CountOfCredentialsReturned": 1, + "ReadOperation": "%%8100", + "ReturnCode": 0, + "ProcessCreationTime": "2024-10-01T02:49:28.150359Z", + "ClientProcessId": 1848 +} +``` + +Now compared to the Event Viewer on Windows + +``` +Credential Manager credentials were read. + +Subject: + Security ID: DESKTOP-9FSUKAJ\bob + Account Name: bob + Account Domain: DESKTOP-9FSUKAJ + Logon ID: 0x3311b1 + Read Operation: Enumerate Credentials + +This event occurs when a user performs a read operation on stored credentials in Credential Manager. +``` + +In order to get the entire EventLog message, we need to parse EventLog provider +files (PE files) and extract resource data. The Windows Registry contains +information about EventLog providers. If you choose to enable EventLog Provider parsing, artemis will perform the following high +level actions to attempt to extract EventLog template strings: + +1. Read and parse the SOFTWARE and SYSTEM Registry files to identify EventLog + providers. The Registry files point to PE files that contain EventLog Provider template strings. +2. Next read and parse all PE files associated with EventLog providers +3. Extract and parse the MUI, MESSAGETABLE, and WEVT_TEMPLATE resources from the PE + files +4. Attempt to use: MESSAGETABLE, WEVT_TEMPLATE, and the evtx EventLog data to + assemble the full EventLog message + +If parsing is successful artemis should return a more detailed EventLog message +if you use the `include_templates` option 🥳 : + +``` +Credential Manager credentials were read. + +Subject: + Security ID: S-1-5-21-549467458-3727351111-1684278619-1001 + Account Name: bob + Account Domain: DESKTOP-9FSUKAJ + Logon ID: 0x3311b1 + Read Operation: Enumerate Credentials + + +This event occurs when a user performs a read operation on stored credentials in Credential Manager. +``` + +### Template Parsing Caveats + +Trying to include template strings in the EventLog messages is very complex. +There are a number of caveats and limitations you should be aware of. More can +also be found in several +[velociratpor](https://docs.velociraptor.app/blog/2019/2019-11-12_windows-event-logs-d8d8e615c9ca/) +[blogs](https://docs.velociraptor.app/docs/forensic/event_logs/) + +1. No support for enum lookups. Artemis cannot lookup enum EventLog data values. + Ex: `IntendedPackageState - 5112`. The enum number 5512 + is converted to the value "Installed" in Event Viewer +2. You should not try parsing EventLog files on different Windows platforms. For + example, if you acquire a `Security.evtx` on a Windows 11 system, you should + not try to parse the evtx file on a Windows 10 system. Microsoft updates the + template strings on different versions of Windows. If you try to parse evtx + files on a different version of Windows you may get odd results! + - If you do try to parse an evtx file on a different artemis will still try + to complete the EventLog message, but if it fails it will return the raw + EventData + - It may ok to parse older evtx on newer Windows versions. For example, + acquiring `System.evtx` on Windows 10 and then parsing on Windows 11 may be + ok. Typically Windows will also contain older versions of template strings. +3. If you acquire only an evtx file such as `System.evtx` you must use a Windows + system (or VM) in order to include template strings. You cannot use Linux or + macOS. If you want to include template strings on Linux or macOS you must + also provide a template file + +### Template Files (aka EventLog Provider strings) + +The `dump_templates` option will make artemis dump the parsed template strings from EventLog Providers on a Windows system +to a JSON file. This option also requires `include_templates`. You must be on a +Windows system in order for this to work. + +:::info + +To include the combine EventLog message you would run the following on +Windows.\ +`artemis.exe acquire eventlogs --include-templates` + +If you only want a template JSON file (and not evtx data) you can run the following +on Windows.\ +`artemis.exe acquire eventlogs --include-templates --dump-templates --only-templates` +::: + +Dumping the template data will return a single JSON file that can then be used +to parse evtx files on different platforms. An example scenario where this could be useful: + +1. You dump template strings on a Windows server via + `artemis.exe acquire eventlogs --include-templates --dump-templates --only-templates`. + The JSON file size will vary but ~90MB seems typical +2. You acquire the template JSON file and move it to your Linux workstation +3. You acquire a 2GB Application.evtx file from the **same** Windows server + and copy it to your Linux workstation +4. Run + `artemis acquire eventlogs --include-templates --alt-template-file --alt-file ` + on Linux and you should hopefully get the full EventLog message! + +If artemis fails to assemble the full EventLog message for any reason, it will +fallback to the raw EventLog data obtained from the evtx file. + +## Output Structure + +Depending on options provided there are several different structures that +artemis can produce. + +By default artemis will return an array of `EventLogRecord` entries. This is the +raw data obtained from the evtx file. Artemis will also fallback to +EventLogRecord entries if it fails to create an `EventLogMessage` structure + +````typescript +export interface EventLogRecord { + /**Event record number */ + record_id: number; + /**Timestamp of eventlog message */ + timestamp: string; + /** + * JSON object representation of the Eventlog message + * Depending on the log the JSON object may have different types of keys + * Example entry: + * ``` + * "data": { + * "Event": { + * "#attributes": { + * "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + * }, + * "System": { + * "Provider": { + * "#attributes": { + * "Name": "Microsoft-Windows-Bits-Client", + * "Guid": "EF1CC15B-46C1-414E-BB95-E76B077BD51E" + * } + * }, + * "EventID": 3, + * "Version": 3, + * "Level": 4, + * "Task": 0, + * "Opcode": 0, + * "Keywords": "0x4000000000000000", + * "TimeCreated": { + * "#attributes": { + * "SystemTime": "2022-10-31T04:24:19.946430Z" + * } + * }, + * "EventRecordID": 2, + * "Correlation": null, + * "Execution": { + * "#attributes": { + * "ProcessID": 1332, + * "ThreadID": 780 + * } + * }, + * "Channel": "Microsoft-Windows-Bits-Client/Operational", + * "Computer": "DESKTOP-EIS938N", + * "Security": { + * "#attributes": { + * "UserID": "S-1-5-18" + * } + * } + * }, + * "EventData": { + * "jobTitle": "Font Download", + * "jobId": "174718A5-F630-43D9-B378-728240ECE152", + * "jobOwner": "NT AUTHORITY\\LOCAL SERVICE", + * "processPath": "C:\\Windows\\System32\\svchost.exe", + * "processId": 1456, + * "ClientProcessStartKey": 844424930132016 + * } + * } + * } + * ``` + */ + data: Record; + /**Path to the EventLog file */ + evidence: string; +} +```` + +If you choose to include template strings, artemis will output `EventLogMessage` +structure. + +```typescript +/** + * Parsed EventLog files with template strings included. A flatten version of `EventLogRecord` + */ +export interface EventLogMessage { + /**Full EventLog message rendered using both the template string and evtx data */ + message: string; + /**The raw template string */ + template_message: string; + /**The raw evtx event data. Ex: `EventData` or `UserData` */ + raw_event_data: Record; + /**EventID for the entry */ + event_id: bigint; + /**Qualifier ID for the entry */ + qualifier: bigint; + /**Version number for the entry */ + version: bigint; + /**GUID associated with the provider */ + guid: string; + /**EventLog provider name */ + provider: string; + /**Alternative provider name */ + source_name: string; + /**EventLog record number */ + record_id: bigint; + /**Task number for entry */ + task: bigint; + /**EventLog level value */ + level: string; + /**Opcode number for entry */ + opcode: bigint; + /**Keywords value for entry. Is a hex number */ + keywords: string; + /**Generated timestamp for entry */ + generated: string; + /**System timestamp for entry */ + system_time: string; + /**Activity ID for entry if available */ + activity_id: string; + /**Process ID for entry if available */ + process_id: bigint; + /**Thread ID for entry if available */ + thread_id: bigint; + /**SID value for entry if available */ + sid: string; + /**Channel name for entry */ + channel: string; + /**Hostname of system */ + computer: string; + /**Full path to the PE file that was used to obtain the template string */ + message_file: string; + /**Full path to the PE file containing parameters for the entry */ + parameter_file: string; + /**Source Registry file used to get provider info */ + registry_file: string; + /**Registry key path to the provider info */ + registry_path: string; + /**Path to the EventLog file */ + evidence: string; +} +``` + +If you choose to dump template strings to a JSON file, artemis will output a +`TemplateStrings` structure: + +```typescript +/** + * A complex structure that represents the parsed EventLog template strings. + * Can be used to create a full EventLog message + */ +export interface TemplateStrings { + /**Object containing Template provider info. Key is the provider name or a GUID */ + providers: Record; + /**Object containing Template string info. Key is the path to the PE file */ + templates: Record; +} + +/**Information about the EventLog provider */ +export interface Provider { + /**Source Registry file used to get provider info */ + registry_file_path: string; + /**Registry key path to the provider info */ + registry_path: string; + /**Name of provider. Might be a GUID */ + name: string; + /**Array of PE files that point to the template strings*/ + message_file: string[]; + /**Array of PE files that point to the parameter values for the provider. So far this seems to always be one file */ + parameter_file: string[]; +} + +/**The parsed WEVT_TEMPLATE info */ +export interface TemplateInfo { + /** Path to PE file */ + path: string; + /**Internal data used by artemis when parsing the PE data. These arrays will always be empty */ + resource_data: { + mui_data: number[]; + wevt_data: number[]; + message_data: number[]; + /** Path to PE file */ + path: string; + }; + /**Info related to the template message string. Key is the `MessageTable` ID */ + message_table: Record; + /**Extreme details on the EventLog provider template. Key is a GUID */ + wevt_template: Record; +} + +export interface MessageTable { + id: bigint; + size: number; + flags: string; + message: string; +} + +export interface WevtTemplate { + offset: number; + element_offsets: number[]; + channels: TemplateData[]; + keywords: TemplateData[]; + opcodes: TemplateData[]; + levels: TemplateData[]; + maps: Maps[]; + tasks: TemplateData[]; + /**Information related to a EventID associated with a provider. Key is combination of the `Definition` ID and version. Ex: The key: `100_0` would be EventID 100 version 0 */ + definitions: Record; +} + +export interface Definition { + /**EventID. Makes up the `definitions` key along with the `version` */ + id: number; + /**Version number of the definition object. Makes up the `definitions` key along with the `id` */ + version: number; + level: number; + opcode: number; + task: number; + keywords: number; + message_id: bigint; + temp_offset: number; + template: Template | null; + opcode_offset: number; + level_offset: number; + task_offset: number; +} + +export interface Template { + template_id: string; + event_data_type: string; + elements: { + token: string; + token_number: number; + dependency_id: number; + size: number; + attribute_list: { + attribute_token: string; + attribute_token_number: number; + value: string; + value_token: string; + value_token_number: number; + name: string; + input_type: string; + substitution: string; + substitution_id: number; + }[] | null; + element_name: string; + input_type: string; + substitution: string; + substitution_id: 0; + }[]; + guid: string; +} + +export interface TemplateData { + message_id: bigint; + id: number; + value: string; + /**Only `Tasks` have this value */ + guid: string | undefined; +} + +export interface Maps { + name: string; + data: Record; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/files.md b/artemis-docs/docs/Artifacts/Windows Artfacts/files.md index b98b3f13..3eed34ca 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/files.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/files.md @@ -1,134 +1,137 @@ ---- -description: Windows file metadata -keywords: - - windows - - file meta ---- - -# Files - -A regular Windows filelisting. Artemis uses the -[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files -and directories on the system. If hashing or `PE` parsing is enabled this will -update the `Last Accessed` timestamps on files since the native OS APIs are used -to access the files and it will fail on any locked files. Use -[RawFiles](./rawfiles.md) to bypass locked files. - -The standard Rust API does not support getting `Changed/Entry Modified` -timestamp on Windows. Use [RawFiles](./rawfiles.md) to include the -`Changed/Entry Modified` timestamp. - -Since a filelisting can be extremely large, every 100k entries artemis will -output the data and then continue. - -Other Parsers: - -- Any tool that can recursively list files and directories - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "files_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "files" # Name of artifact -[artifacts.files] -start_path = "C:\\Windows" # Where to start the listing -# Optional -depth = 1 # How many sub directories to descend -# Optional -metadata = true # Get PE metadata -# Optional -md5 = true # MD5 all files -# Optional -sha1 = false # SHA1 all files -# Optional -sha256 = false # SHA256 all files -# Optional -path_regex = "" # Regex for paths -# Optional -file_regex = "" # Regex for files -``` - -## Collection Options - -- `start_path` Where to start the file listing. Must exist on the endpoint. To - start at root use `C:\\`. This configuration is **required** -- `depth` Specify how many directories to descend from the `start_path`. Default - is one (1). Must be a postive number. Max value is 255. This configuration is - **optional** -- `metadata` Get [PE](pe.md) data from `PE` files. This configuration is - **optional**. Default is **false** -- `md5` Boolean value to enable MD5 hashing on all files. This configuration is - **optional**. Default is **false** -- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration - is **optional**. Default is **false** -- `sha256` Boolean value to enable SHA256 hashing on all files. This - configuration is **optional**. Default is **false** -- `path_regex` Only descend into paths (directories) that match the provided - regex. This configuration is **optional**. Default is no Regex -- `file_regex` Only return entres that match the provided regex. This - configuration is **optional**. Default is no Regex - -## Output Structure - -An array of `WindowsFileInfo` entries - -```typescript -export interface WindowsFileInfo { - /**Full path to file or directory */ - full_path: string; - /**Directory path */ - directory: string; - /**Filename */ - filename: string; - /**Extension of file if any */ - extension: string; - /**Created timestamp */ - created: string; - /**Modified timestamp */ - modified: string; - /**Changed timestamp */ - changed: string; - /**Accessed timestamp */ - accessed: string; - /**Size of file in bytes */ - size: number; - /**Inode associated with entry */ - inode: number; - /**Mode of file entry */ - mode: number; - /**User ID associated with file */ - uid: number; - /**Group ID associated with file */ - gid: number; - /**MD5 of file */ - md5: string; - /**SHA1 of file */ - sha1: string; - /**SHA256 of file */ - sha256: string; - /**Is the entry a file */ - is_file: boolean; - /**Is the entry a directory */ - is_directory: boolean; - /**Is the entry a symbolic links */ - is_symlink: boolean; - /**Depth the file from provided start poin */ - depth: number; - /**PE binary metadata */ - binary_info: PeInfo; -} -``` +--- +description: Windows file metadata +keywords: + - windows + - file meta +--- + +# Files + +A regular Windows filelisting. Artemis uses the +[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files +and directories on the system. If hashing or `PE` parsing is enabled this will +update the `Last Accessed` timestamps on files since the native OS APIs are used +to access the files and it will fail on any locked files. Use +[RawFiles](./rawfiles.md) to bypass locked files. + +The standard Rust API does not support getting `Changed/Entry Modified` +timestamp on Windows. Use [RawFiles](./rawfiles.md) to include the +`Changed/Entry Modified` timestamp. + +Since a filelisting can be extremely large, every 10k entries artemis will +output the data and then continue. + +Other Parsers: + +- Any tool that can recursively list files and directories + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "files_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "files" # Name of artifact +[artifacts.files] +start_path = "C:\\Windows" # Where to start the listing +# Optional +depth = 1 # How many sub directories to descend +# Optional +metadata = true # Get PE metadata +# Optional +md5 = true # MD5 all files +# Optional +sha1 = false # SHA1 all files +# Optional +sha256 = false # SHA256 all files +# Optional +path_regex = "" # Regex for paths +# Optional +file_regex = "" # Regex for files +# Optional +yara = "" # Base64 encoded Yara rule or a remote Yara rule +``` + +## Collection Options + +- `start_path` Where to start the file listing. Must exist on the endpoint. To + start at root use `C:\\`. This configuration is **required** +- `depth` Specify how many directories to descend from the `start_path`. Default + is one (1). Must be a postive number. Max value is 255. This configuration is + **optional** +- `metadata` Get [PE](pe.md) data from `PE` files. This configuration is + **optional**. Default is **false** +- `md5` Boolean value to enable MD5 hashing on all files. This configuration is + **optional**. Default is **false** +- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration + is **optional**. Default is **false** +- `sha256` Boolean value to enable SHA256 hashing on all files. This + configuration is **optional**. Default is **false** +- `path_regex` Only descend into paths (directories) that match the provided + regex. This configuration is **optional**. Default is no Regex +- `file_regex` Only return entres that match the provided regex. This + configuration is **optional**. Default is no Regex +- `yara` Either a base64 encoded Yara rule or a Yara rule hosted on a remote server + +## Output Structure + +An array of `WindowsFileInfo` entries + +```typescript +export interface WindowsFileInfo { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Size of file in bytes */ + size: number; + /**Inode associated with entry */ + inode: number; + /**Mode of file entry */ + mode: number; + /**User ID associated with file */ + uid: number; + /**Group ID associated with file */ + gid: number; + /**MD5 of file */ + md5: string; + /**SHA1 of file */ + sha1: string; + /**SHA256 of file */ + sha256: string; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /**Is the entry a symbolic links */ + is_symlink: boolean; + /**Depth the file from provided start poin */ + depth: number; + /**PE binary metadata */ + binary_info: PeInfo; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/firewall_rules.md b/artemis-docs/docs/Artifacts/Windows Artfacts/firewall_rules.md index 3b644b87..75da3ef1 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/firewall_rules.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/firewall_rules.md @@ -1,78 +1,78 @@ ---- -description: Windows Firewall Rules -keywords: - - windows - - registry ---- - -# Firewall Rules - -Artemis supports extracting the Windows Firewall rules from the Windows SYSTEM Registry files. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Firewall rules. - -## Sample API Script - -```typescript -import { firewallRules } from "./artemis-api/mod"; - -function main() { - const results = firewallRules(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -## Output Structure - -An array of `FirewallRules` - -```typescript -export interface FirewallRules { - action: string; - active: boolean; - direction: Direction; - protocol: Protocol; - protocol_number: number; - local_port: number; - remote_port:number; - name: string; - registry_key_name: string; - description: string; - application: string; - registry_file: string; - key_path: string; - last_modified: string; - rule_version: string; - profile: string; - service: string; - remote_address: string[]; - local_address: string[]; - [ key: string ]: unknown; - message: string; - datetime: string; - timestamp_desc: "Registry Last Modified"; - artifact: "Windows Firewall Rule"; - data_type: "windows:registry:firewallrule:entry"; -} - -export enum Direction { - Inbound = "Inbound", - Outbound = "Outbound", - Unknown = "Unknown", -} - -export enum Protocol { - TCP = "TCP", - UDP = "UDP", - ICMP = "ICMP", - ICMP_v6 = "ICMP_v6", - Unkonwn = "Unknown", - IPV6 = "IPv6", - GRE = "GRE", - IGMP = "IGMP", -} -``` +--- +description: Windows Firewall Rules +keywords: + - windows + - registry +--- + +# Firewall Rules + +Artemis supports extracting the Windows Firewall rules from the Windows SYSTEM Registry files. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Firewall rules. + +## Sample API Script + +```typescript +import { firewallRules } from "./artemis-api/mod"; + +function main() { + const results = firewallRules(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `FirewallRules` + +```typescript +export interface FirewallRules { + action: string; + active: boolean; + direction: Direction; + protocol: Protocol; + protocol_number: number; + local_port: number; + remote_port:number; + name: string; + registry_key_name: string; + description: string; + application: string; + evidence: string; + key_path: string; + last_modified: string; + rule_version: string; + profile: string; + service: string; + remote_address: string[]; + local_address: string[]; + [ key: string ]: unknown; + message: string; + datetime: string; + timestamp_desc: "Registry Last Modified"; + artifact: "Windows Firewall Rule"; + data_type: "windows:registry:firewallrule:entry"; +} + +export enum Direction { + Inbound = "Inbound", + Outbound = "Outbound", + Unknown = "Unknown", +} + +export enum Protocol { + TCP = "TCP", + UDP = "UDP", + ICMP = "ICMP", + ICMP_v6 = "ICMP_v6", + Unknown = "Unknown", + IPV6 = "IPv6", + GRE = "GRE", + IGMP = "IGMP", +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/jumplists.md b/artemis-docs/docs/Artifacts/Windows Artfacts/jumplists.md index c52cee3b..6b1182fa 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/jumplists.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/jumplists.md @@ -1,101 +1,101 @@ ---- -description: Tracks files opened by applications in Windows Taskbar -keywords: - - windows - - binary ---- - -# Jumplists - -Windows Jumplists files track opened files via applications in the Taskbar or -Start Menu. Jumplists are actually a collection of embedded -[Shortcut](./shortcuts.md) files and therefore can show evidence of file -interaction. - -There are two (2) types of Jumplist files: - -- Custom - Files that are pinned to Taskbar applications -- Automatic - Files that are not pinned to Taskbar applications - -Other parsers: - -References: - -- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidoc) - -## TOML Collection - -```toml -[output] -name = "jumplists_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "jumplists" -[artifacts.jumplists] -# Optional -# alt_file = "C:\\Artifacts\\CustomJumplist" -``` - -## Collection Options - -- `alt_file` Full path to an alternative Jumplist file. This configuration is - **optional**. By default artemis will parse all user Jumplist files on the - system. - -## Output Structure - -An array of `Jumplists` entries - -```typescript -export interface Jumplists { - /**Path to Jumplist file */ - source: string; - /**Jupmlist type. Custom or Automatic */ - jumplist_type: string; - /**Application ID for Jumplist file */ - app_id: string; - /**Metadata associated with Jumplist entry */ - jumplist_metadata: DestEntries; - /**Shortcut information for Jumplist entry */ - lnk_info: Shortcut; -} - -/** - * Metadata associated with Jumplist entry - */ -interface DestEntries { - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_file_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_file_id: string; - /**Hostname associated with Jumplist entry */ - hostname: string; - /**Jumplist entry number */ - entry: number; - /**Modified timestamp of Jumplist entry */ - modified: string; - /**Status if Jumplist entry is pinned. `Pinned` or `NotPinned` */ - pin_status: string; - /**Path associated with Jumplist entry */ - path: string; -} -``` +--- +description: Tracks files opened by applications in Windows Taskbar +keywords: + - windows + - binary +--- + +# Jumplists + +Windows Jumplists files track opened files via applications in the Taskbar or +Start Menu. Jumplists are actually a collection of embedded +[Shortcut](./shortcuts.md) files and therefore can show evidence of file +interaction. + +There are two (2) types of Jumplist files: + +- Custom - Files that are pinned to Taskbar applications +- Automatic - Files that are not pinned to Taskbar applications + +Other parsers: + +References: + +- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidoc) + +## TOML Collection + +```toml +[output] +name = "jumplists_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "jumplists" +[artifacts.jumplists] +# Optional +# alt_dir = "C:\\Artifacts\\CustomJumplist\\*" +``` + +## Collection Options + +- `alt_dir` Glob to an alternative Jumplist file(s). This configuration is + **optional**. By default artemis will parse all user Jumplist files on the + system. + +## Output Structure + +An array of `Jumplists` entries + +```typescript +export interface Jumplists { + /**Path to Jumplist file */ + evidence: string; + /**Jupmlist type. Custom or Automatic */ + jumplist_type: string; + /**Application ID for Jumplist file */ + app_id: string; + /**Metadata associated with Jumplist entry */ + jumplist_metadata: DestEntries; + /**Shortcut information for Jumplist entry */ + lnk_info: Shortcut; +} + +/** + * Metadata associated with Jumplist entry + */ +interface DestEntries { + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_file_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_file_id: string; + /**Hostname associated with Jumplist entry */ + hostname: string; + /**Jumplist entry number */ + entry: number; + /**Modified timestamp of Jumplist entry */ + modified: string; + /**Status if Jumplist entry is pinned. `Pinned` or `NotPinned` */ + pin_status: string; + /**Path associated with Jumplist entry */ + path: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/logons.md b/artemis-docs/docs/Artifacts/Windows Artfacts/logons.md index 942dd2d3..6cfcb340 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/logons.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/logons.md @@ -1,70 +1,76 @@ ---- -description: Windows Logon events -keywords: - - windows - - eventlogs ---- - -# Logons - -Artemis supports extracting Logon entries from the Windows EventLog -Security.evtx file. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -Logon entries. - -## Sample API Script - -```typescript -import { - logons, -} from "./artemis-api/mod"; - -function main() { - const path = "path to Security.evtx"; - const results = logons(path); - - console.log(results); -} - -main(); -``` - -## Output Structure - -An array of `Logons` - -```typescript -export interface LogonsWindows { - logon_type: LogonType; - sid: string; - account_name: string; - account_domain: string; - logon_id: string; - logon_process: string; - authentication_package: string; - source_ip: string; - source_workstation: string; - eventlog_generated: string; - message: string; - datetime: string; - timestamp_desc: "Account Logon" | "Account Logoff"; - artifact: "Logon EventLog" | "Logoff EventLog"; - data_type: "windows:eventlogs:logon:entry" | "windows:eventlogs:logoff:entry"; -} - -export enum LogonType { - Network = "Network", - Interactive = "Interactive", - Batch = "Batch", - Service = "Service", - Unlock = "Unlock", - NetworkCleartext = "NetworkCleartext", - NewCredentials = "NewCredentials", - RemoteInteractive = "RemoteInteractive", - CacheInteractive = "CacheInteractive", - Unknown = "Unknown", -} -``` +--- +description: Windows Logon events +keywords: + - windows + - eventlogs +--- + +# Logons + +Artemis supports extracting Logon entries from the Windows EventLog +Security.evtx file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +Logon entries. + +## Sample API Script + +```typescript +import { + logons, +} from "./artemis-api/mod"; + +function main() { + const path = "path to Security.evtx"; + const results = logons(path); + + console.log(results); +} + +main(); +``` + +## Output Structure + +An array of `Logons` + +```typescript +export interface LogonsWindows { + logon_type: LogonType; + sid: string; + account_name: string; + account_domain: string; + logon_id: string; + logon_process: string; + authentication_package: string; + source_ip: string; + activity_id: string; + computer: string; + channel: string; + provider: string; + provider_guid: string; + source_workstation: string; + eventlog_generated: string; + message: string; + datetime: string; + timestamp_desc: "Account Logon" | "Account Logoff" | "Account Failed Logon"; + artifact: "Logon EventLog" | "Logoff EventLog" | "Failed Logon EventLog"; + data_type: "windows:eventlogs:logon:entry" | "windows:eventlogs:logoff:entry" | "windows:eventlogs:logon:failed:entry"; + evidence: string; +} + +export enum LogonType { + Network = "Network", + Interactive = "Interactive", + Batch = "Batch", + Service = "Service", + Unlock = "Unlock", + NetworkCleartext = "NetworkCleartext", + NewCredentials = "NewCredentials", + RemoteInteractive = "RemoteInteractive", + CacheInteractive = "CacheInteractive", + Unknown = "Unknown", +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/mft.md b/artemis-docs/docs/Artifacts/Windows Artfacts/mft.md index 726d4ddf..70181eea 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/mft.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/mft.md @@ -1,141 +1,143 @@ ---- -description: Windows Master File Table -keywords: - - windows - - file meta - - ntfs ---- - -# MFT - -The Windows MFT file contains a record of all files and directories on a Windows -NTFS volume. Artemis supports parsing the MFT file and can attempt to generate a -filelisting of the system. - -Since the MFT file can be extremely large, every 100k entries artemis will -output the data and then continue. - -Generating a filelisting from just the MFT file is challenging, there are a -[variety](https://osdfir.blogspot.com/2021/10/pearls-and-pitfalls-of-timeline-analysis.html) -of -[edge cases](https://harelsegev.github.io/posts/resolving-file-paths-using-the-mft/) -that can arise that can make path reconstruction incomplete. Artemis was -designed to try handle the most common issues and edge case scenarios. - -If you have concerns about the output, you should validate with another MFT -parsing tool. - -Other Parsers: - -- [velociraptor](https://github.com/Velocidex/velociraptor) - -References: - -- [Libyal](https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc) - -```toml -[output] -name = "mft_collection" -directory = "./tmp" -format = "csv" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "mft" -[artifacts.mft] -# alt_drive = 'C' -# alt_file = "/tmp/$MFT" -``` - -## Collection Options - -- `alt_drive` Alternative drive letter to use to parse the MFT. By default - artemis will parse the SystemDrive. This configuration is **optional** -- `alt_file` Alternative path to the MFT file. This configuration is - **optional** - -## Output Structure - -An array of `MftEntry` entries - -```typescript -export interface MftEntry { - /**Full path to file or directory */ - full_path: string; - /**Directory path */ - directory: string; - /**Filename */ - filename: string; - /**Extension of file if any */ - extension: string; - /**Created timestamp */ - created: string; - /**Modified timestamp */ - modified: string; - /**Changed timestamp */ - changed: string; - /**Accessed timestamp */ - accessed: string; - /**Filename created timestamp */ - filename_created: string; - /**Filename modified timestamp */ - filename_modified: string; - /**Filename accessed timestamp */ - filename_accessed: string; - /**Filename changed timestamp */ - filename_changed: string; - /**Size of file in bytes */ - size: number; - /**Inode entry */ - inode: number; - /**Parent Inode entry*/ - parent_inode: number; - /**Sequence number for entry */ - usn: number; - /**Is the entry a file */ - is_file: boolean; - /**Is the entry a directory */ - is_directory: boolean; - /**Is the entry deleted */ - deleted: boolean; - /**Namespace for the entry */ - namespace: Namespace; - /**Attributes for the entry */ - attributes: AttributeFlags[]; - /**Other attributes parsed for the entry */ - attribute_list: Record[]; -} - -export enum Namespace { - Posix = "Posix", - Windows = "Windows", - Dos = "Dos", - WindowsDos = "WindowsDos", - Unknown = "Unknown", -} - -export enum AttributeFlags { - ReadOnly = "ReadOnly", - Hidden = "Hidden", - System = "System", - Directory = "Directory", - Archive = "Archive", - Device = "Device", - Normal = "Normal", - Temporary = "Temporary", - Sparse = "Sparse", - Reparse = "Reparse", - Compressed = "Compressed", - Offline = "Offline", - NotIndexed = "NotIndexed", - Encrypted = "Encrypted", - Virtual = "Virtual", - Unknown = "Unknown", - IndexView = "IndexView", - Volume = "Volume", -} -``` +--- +description: Windows Master File Table +keywords: + - windows + - file meta + - ntfs +--- + +# MFT + +The Windows MFT file contains a record of all files and directories on a Windows +NTFS volume. Artemis supports parsing the MFT file and can attempt to generate a +filelisting of the system. + +Since the MFT file can be extremely large, every 100k entries artemis will +output the data and then continue. + +Generating a filelisting from just the MFT file is challenging, there are a +[variety](https://osdfir.blogspot.com/2021/10/pearls-and-pitfalls-of-timeline-analysis.html) +of +[edge cases](https://harelsegev.github.io/posts/resolving-file-paths-using-the-mft/) +that can arise that can make path reconstruction incomplete. Artemis was +designed to try handle the most common issues and edge case scenarios. + +If you have concerns about the output, you should validate with another MFT +parsing tool. + +Other Parsers: + +- [velociraptor](https://github.com/Velocidex/velociraptor) + +References: + +- [Libyal](https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc) + +```toml +[output] +name = "mft_collection" +directory = "./tmp" +format = "csv" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "mft" +[artifacts.mft] +# alt_drive = 'C' +# alt_file = "/tmp/$MFT" +``` + +## Collection Options + +- `alt_drive` Alternative drive letter to use to parse the MFT. By default + artemis will parse the SystemDrive. This configuration is **optional** +- `alt_file` Alternative path to the MFT file. This configuration is + **optional** + +## Output Structure + +An array of `MftEntry` entries + +```typescript +export interface MftEntry { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Filename created timestamp */ + filename_created: string; + /**Filename modified timestamp */ + filename_modified: string; + /**Filename accessed timestamp */ + filename_accessed: string; + /**Filename changed timestamp */ + filename_changed: string; + /**Size of file in bytes */ + size: number; + /**Inode entry */ + inode: number; + /**Parent Inode entry*/ + parent_inode: number; + /**Sequence number for entry */ + usn: number; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /**Is the entry deleted */ + deleted: boolean; + /**Namespace for the entry */ + namespace: Namespace; + /**Attributes for the entry */ + attributes: AttributeFlags[]; + /**Other attributes parsed for the entry */ + attribute_list: Record[]; + /**Path to the MFT file */ + evidence: string; +} + +export enum Namespace { + Posix = "Posix", + Windows = "Windows", + Dos = "Dos", + WindowsDos = "WindowsDos", + Unknown = "Unknown", +} + +export enum AttributeFlags { + ReadOnly = "ReadOnly", + Hidden = "Hidden", + System = "System", + Directory = "Directory", + Archive = "Archive", + Device = "Device", + Normal = "Normal", + Temporary = "Temporary", + Sparse = "Sparse", + Reparse = "Reparse", + Compressed = "Compressed", + Offline = "Offline", + NotIndexed = "NotIndexed", + Encrypted = "Encrypted", + Virtual = "Virtual", + Unknown = "Unknown", + IndexView = "IndexView", + Volume = "Volume", +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/mru.md b/artemis-docs/docs/Artifacts/Windows Artfacts/mru.md index 31f9ad7a..1b800527 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/mru.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/mru.md @@ -1,71 +1,72 @@ ---- -description: Most Recently Used entries -keywords: - - windows - - registry ---- - -# Most Recently Used - -Artemis supports extracting Most Recently Used (MRU) entries from multiple -Registry key paths in the NTUSER.DAT Registry file. MRU keys can provide -evidence if a was accessed on a system. Artemis currently supports the following -MRU keys: - -- Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU -- Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU -- Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect MRU -keys. - -## Sample API Script - -```typescript -import { - parseMru, -} from "./artemis-api/mod"; - -async function main() { - const path = "path to NTUSER.DAT"; - const results = parseMru(path); - - console.log(results); -} -``` - -## Output Structure - -An array of `Mru` - -```typescript -export interface Mru { - ntuser_path: string; - kind: MruType; - /**Filename of MRU entry*/ - filename: string; - /**Path to MRU entry */ - path: string; - /**Created time of MRU entry */ - created: string; - /**Modified time of MRU entry */ - modified: string; - /**Accessed time of MRU entry */ - accessed: string; - /**All ShellItems that make up the MRU entry */ - items: ShellItems[]; - message: string; - datetime: string; - timestamp_desc: "MRU Entry Created"; - artifact: "Windows Most Recently Used" | "MRU Open Save" | "MRU Last Visit" | "MRU Recent Docs"; - data_type: "windows:registry:mru:entry"; -} - -export enum MruType { - LASTVISITED = "LastVisisted", - OPENSAVE = "OpenSave", - RECENTDOCS = "RecentDocs", -} -``` +--- +description: Most Recently Used entries +keywords: + - windows + - registry +--- + +# Most Recently Used + +Artemis supports extracting Most Recently Used (MRU) entries from multiple +Registry key paths in the NTUSER.DAT Registry file. MRU keys can provide +evidence if a was accessed on a system. Artemis currently supports the following +MRU keys: + +- Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU +- Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedPidlMRU +- Software\Microsoft\Windows\CurrentVersion\Explorer\RecentDocs + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect MRU +keys. + +## Sample API Script + +```typescript +import { + parseMru, +} from "./artemis-api/mod"; + +async function main() { + const path = "path to NTUSER.DAT"; + const results = parseMru(path); + + console.log(results); +} +``` + +## Output Structure + +An array of `Mru` + +```typescript +export interface Mru { + ntuser_path: string; + kind: MruType; + /**Filename of MRU entry*/ + filename: string; + /**Path to MRU entry */ + path: string; + /**Created time of MRU entry */ + created: string; + /**Modified time of MRU entry */ + modified: string; + /**Accessed time of MRU entry */ + accessed: string; + /**All ShellItems that make up the MRU entry */ + items: ShellItems[]; + message: string; + datetime: string; + timestamp_desc: "MRU Entry Created"; + artifact: "Windows Most Recently Used" | "MRU Open Save" | "MRU Last Visit" | "MRU Recent Docs"; + data_type: "windows:registry:mru:entry"; + evidence: string; +} + +export enum MruType { + LASTVISITED = "LastVisisted", + OPENSAVE = "OpenSave", + RECENTDOCS = "RecentDocs", +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/msi_installed.md b/artemis-docs/docs/Artifacts/Windows Artfacts/msi_installed.md new file mode 100644 index 00000000..d46256bb --- /dev/null +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/msi_installed.md @@ -0,0 +1,54 @@ +--- +description: Windows MSI installed events +keywords: + - windows + - eventlogs +--- + +# MSI Installed + +Artemis supports extracting MSI installed events from the Windows EventLog +Application.evtx file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +MSI installed entries. + +## Sample API Script + +```typescript +import { msiInstalled } from "./artemis-api/mod"; + +function main() { + const results = msiInstalled(); + console.log(JSON.stringify(results)); + +} + +main(); +``` + +## Output Structure + +An array of `MsiInstalled` + +```typescript +export interface MsiInstalled { + name: string; + language: number; + version: string; + manufacturer: string; + installation_status: number; + hostname: string; + sid: string; + pid: number; + thread_id: number; + message: string; + datetime: string; + timestamp_desc: "MSI Installed"; + artifact: "EventLog MSI Installed 1033"; + data_type: "windows:eventlog:application:msi"; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/outlook.md b/artemis-docs/docs/Artifacts/Windows Artfacts/outlook.md index 22c90c32..4da7c624 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/outlook.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/outlook.md @@ -1,296 +1,296 @@ ---- -description: Windows Email Client -keywords: - - windows ---- - -# Outlook - -Outlook is a popular email client on Windows systems. Outlook on Windows stores -messages in OST or PST files. PST was used by older Outlook versions (prior to -Outlook 2013). OST is used by Outlook 2013+. - -Artemis supports parsing and extracting emails and attachments from OST files. - -## Limitations - -- Only OST files are supported, PST is not yet fully supported. -- Encrypted OST/PST files are not supported yet - -:::note - -Outlook was re-written in 2022 (New Outlook for Windows). Which is an online -only web app. This parser does not support that version - -::: - -Other parsers: - -- [libpff](https://github.com/libyal/libpff) - -References: - -- [libyal](https://github.com/libyal/libpff/blob/main/documentation/Personal%20Folder%20File%20(PFF)%20format.asciidoc) -- [Outlook Messages](https://www.forensafe.com/blogs/outlook.html) - -## TOML Collection - -```toml -[output] -name = "outlook_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "outlook" -[artifacts.outlook] -include_attachments = true -``` - -## Collection Options - -- `alt_path` An alternative path to the OST file. This configuration is - **optional**. By default will parse all OST files under - `%systemdrive%\Users\*\AppData\Local\Microsoft\Outlook\*.ost` -- `include_attachments` - Boolean value whether to extract attachments in email - messages. This configuration is **required**. -- `start_date` - Only extract emails after this date. This configuration is - **optional**. By default artemis will extract all emails -- `end_date` - Only extract emails before this date. This configuration is - **optional**. By default artemis will extract all emails -- `yara_rule_message` - Run provided Yara-X rule against the message. Only - matches will be returned. This configuration is **optional**. By default - artemis will not scan with Yara -- `yara_rule_attachment` - Run provided Yara-X rule against attachments. Only - matches will be returned. This configuration is **optional**. By default - artemis will not scan with Yara - -## Output Structure - -Array of `OutlookMessage` - -```typescript -export interface OutlookMessage { - /**Body of the message. Can be either plaintext, html, rtf */ - body: string; - /**Subject line */ - subject: string; - /**From line */ - from: string; - /**Who received the email */ - recipient: string; - /**Delivered timestamp */ - delivered: string; - /**Other recipients */ - recipients: string[]; - /**Attachment message metadata */ - attachments: AttachmentInfo[] | undefined; - /**Full path to the folder containing the message */ - folder_path: string; - /**Source path to the OST file */ - source_file: string; - /**Yara rule that matched if Yara scanning was enabled */ - yara_hits: string[] | undefined; -} -``` - -For the API multiple structures can be returned based on functions that are -called - -```typescript -/** - * Metadata about an Outlook folder - */ -export interface FolderInfo { - /**Name of the folder */ - name: string; - /**Created timestamp of folder */ - created: string; - /**Modified timestamp of folder */ - modified: string; - /**Array of properties for the folder */ - properties: PropertyContext[]; - /**Array of sub-folders */ - subfolders: SubFolder[]; - /**Array of sub-folders pointing to more metadata */ - associated_content: SubFolder[]; - /**Count of subfolders */ - subfolder_count: number; - /**Count of messages in the folder */ - message_count: number; - /**Internal structure containing information required to extract messages */ - messages_table: TableInfo; -} - -/** - * Preview of sub-folder metadata - */ -export interface SubFolder { - /**Name of the sub-folder */ - name: string; - /**Folder ID */ - node: number; -} - -/** - * Primary source of metadata for OST data - */ -export interface PropertyContext { - /**Property name(s) */ - name: string[]; - /**Type of property. Such as string, int, GUID, date, etc */ - property_type: string; - /**Property ID */ - prop_id: number; - /**Reference to the property in the OST */ - reference: number; - /**Property value. Value will depend on type. Ex: string, int, array, int, etc */ - value: unknown; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface TableInfo { - block_data: number[][]; - block_descriptors: Record; - rows: number[]; - columns: TableRows[]; - include_cols: string[]; - row_size: number; - map_offset: number; - node: HeapNode; - total_rows: number; - has_branch: TableBranchInfo | undefined; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface DescriptorData { - node_level: string; - node: Node; - block_subnode_id: number; - block_data_id: number; - block_descriptor_id: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface Node { - node_id: string; - node_id_num: number; - node: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface TableRows { - value: undefined; - column: ColumnDescriptor; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface ColumnDescriptor { - property_type: string; - id: number; - property_name: string[]; - offset: number; - size: number; - index: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface HeapNode { - node: string; - index: number; - block_index: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface TableBranchInfo { - node: HeapNode; - rows_info: RowsInfo; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface RowsInfo { - row_end: number; - count: number; -} - -/** - * Metadata associated with email messages - */ -export interface MessageDetails { - /**Array of properties for the message */ - props: PropertyContext[]; - /**Message body. Can be plaintext, html, or rtf */ - body: string; - /**Subject line for message */ - subject: string; - /**From address of the email */ - from: string; - /**Recipient of the email */ - recipient: string; - /**Delivered timestamp */ - delivered: string; - /**Preview of attachments in the email */ - attachments: AttachmentInfo[]; - /**Array of other recipients who also received the email*/ - recipients: TableRows[][]; -} - -/** - * Preview of the attachment metadata - */ -export interface AttachmentInfo { - /**Name of the attachment */ - name: string; - /**Size of the attachment */ - size: number; - /**How the attachment was attached */ - method: string; - /**Node ID for the attachment */ - node: number; - /**Block ID for the attachment */ - block_id: number; - /**Descriptor ID for the attachment */ - descriptor_id: number; -} - -/** - * Metadata about the attachment - */ -export interface Attachment { - /**Base64 string containing attachment*/ - data: string; - /**Size of the attachment */ - size: bigint; - /**Name of the attachment */ - name: string; - /**Mime type of the attachment */ - mime: string; - /**Attachment extension (includes the dot) */ - extension: string; - /**How the attachment was attached */ - method: string; - /**Array of properties for the attachment */ - props: PropertyContext[]; -} -``` +--- +description: Windows Email Client +keywords: + - windows +--- + +# Outlook + +Outlook is a popular email client on Windows systems. Outlook on Windows stores +messages in OST or PST files. PST was used by older Outlook versions (prior to +Outlook 2013). OST is used by Outlook 2013+. + +Artemis supports parsing and extracting emails and attachments from OST files. + +## Limitations + +- Only OST files are supported, PST is not yet fully supported. +- Encrypted OST/PST files are not supported yet + +:::note + +Outlook was re-written in 2022 (New Outlook for Windows). Which is an online +only web app. This parser does not support that version + +::: + +Other parsers: + +- [libpff](https://github.com/libyal/libpff) + +References: + +- [libyal](https://github.com/libyal/libpff/blob/main/documentation/Personal%20Folder%20File%20(PFF)%20format.asciidoc) +- [Outlook Messages](https://www.forensafe.com/blogs/outlook.html) + +## TOML Collection + +```toml +[output] +name = "outlook_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "outlook" +[artifacts.outlook] +include_attachments = true +``` + +## Collection Options + +- `alt_file` An alternative path to the OST file. This configuration is + **optional**. By default will parse all OST files under + `%systemdrive%\Users\*\AppData\Local\Microsoft\Outlook\*.ost` +- `include_attachments` - Boolean value whether to extract attachments in email + messages. This configuration is **required**. +- `start_date` - Only extract emails after this date. This configuration is + **optional**. By default artemis will extract all emails +- `end_date` - Only extract emails before this date. This configuration is + **optional**. By default artemis will extract all emails +- `yara_rule_message` - Run provided Yara-X rule against the message. Only + matches will be returned. This configuration is **optional**. By default + artemis will not scan with Yara +- `yara_rule_attachment` - Run provided Yara-X rule against attachments. Only + matches will be returned. This configuration is **optional**. By default + artemis will not scan with Yara + +## Output Structure + +Array of `OutlookMessage` + +```typescript +export interface OutlookMessage { + /**Body of the message. Can be either plaintext, html, rtf */ + body: string; + /**Subject line */ + subject: string; + /**From line */ + from: string; + /**Who received the email */ + recipient: string; + /**Delivered timestamp */ + delivered: string; + /**Other recipients */ + recipients: string[]; + /**Attachment message metadata */ + attachments: AttachmentInfo[] | undefined; + /**Full path to the folder containing the message */ + folder_path: string; + /**Source path to the OST file */ + evidence: string; + /**Yara rule that matched if Yara scanning was enabled */ + yara_hits: string[] | undefined; +} +``` + +For the API multiple structures can be returned based on functions that are +called + +```typescript +/** + * Metadata about an Outlook folder + */ +export interface FolderInfo { + /**Name of the folder */ + name: string; + /**Created timestamp of folder */ + created: string; + /**Modified timestamp of folder */ + modified: string; + /**Array of properties for the folder */ + properties: PropertyContext[]; + /**Array of sub-folders */ + subfolders: SubFolder[]; + /**Array of sub-folders pointing to more metadata */ + associated_content: SubFolder[]; + /**Count of subfolders */ + subfolder_count: number; + /**Count of messages in the folder */ + message_count: number; + /**Internal structure containing information required to extract messages */ + messages_table: TableInfo; +} + +/** + * Preview of sub-folder metadata + */ +export interface SubFolder { + /**Name of the sub-folder */ + name: string; + /**Folder ID */ + node: number; +} + +/** + * Primary source of metadata for OST data + */ +export interface PropertyContext { + /**Property name(s) */ + name: string[]; + /**Type of property. Such as string, int, GUID, date, etc */ + property_type: string; + /**Property ID */ + prop_id: number; + /**Reference to the property in the OST */ + reference: number; + /**Property value. Value will depend on type. Ex: string, int, array, int, etc */ + value: unknown; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface TableInfo { + block_data: number[][]; + block_descriptors: Record; + rows: number[]; + columns: TableRows[]; + include_cols: string[]; + row_size: number; + map_offset: number; + node: HeapNode; + total_rows: number; + has_branch: TableBranchInfo | undefined; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface DescriptorData { + node_level: string; + node: Node; + block_subnode_id: number; + block_data_id: number; + block_descriptor_id: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface Node { + node_id: string; + node_id_num: number; + node: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface TableRows { + value: undefined; + column: ColumnDescriptor; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface ColumnDescriptor { + property_type: string; + id: number; + property_name: string[]; + offset: number; + size: number; + index: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface HeapNode { + node: string; + index: number; + block_index: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface TableBranchInfo { + node: HeapNode; + rows_info: RowsInfo; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface RowsInfo { + row_end: number; + count: number; +} + +/** + * Metadata associated with email messages + */ +export interface MessageDetails { + /**Array of properties for the message */ + props: PropertyContext[]; + /**Message body. Can be plaintext, html, or rtf */ + body: string; + /**Subject line for message */ + subject: string; + /**From address of the email */ + from: string; + /**Recipient of the email */ + recipient: string; + /**Delivered timestamp */ + delivered: string; + /**Preview of attachments in the email */ + attachments: AttachmentInfo[]; + /**Array of other recipients who also received the email*/ + recipients: TableRows[][]; +} + +/** + * Preview of the attachment metadata + */ +export interface AttachmentInfo { + /**Name of the attachment */ + name: string; + /**Size of the attachment */ + size: number; + /**How the attachment was attached */ + method: string; + /**Node ID for the attachment */ + node: number; + /**Block ID for the attachment */ + block_id: number; + /**Descriptor ID for the attachment */ + descriptor_id: number; +} + +/** + * Metadata about the attachment + */ +export interface Attachment { + /**Base64 string containing attachment*/ + data: string; + /**Size of the attachment */ + size: bigint; + /**Name of the attachment */ + name: string; + /**Mime type of the attachment */ + mime: string; + /**Attachment extension (includes the dot) */ + extension: string; + /**How the attachment was attached */ + method: string; + /**Array of properties for the attachment */ + props: PropertyContext[]; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/pca.md b/artemis-docs/docs/Artifacts/Windows Artfacts/pca.md index d075f37b..dfc4acd8 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/pca.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/pca.md @@ -1,57 +1,57 @@ ---- -description: Program Compatability Assistant -keywords: - - windows ---- - -# PCA - -Windows Program Compatibility Assistant (PCA) tracks recent applications that are executed. - -References: -- [DFIR blog](https://aboutdfir.com/new-windows-11-pro-22h2-evidence-of-execution-artifact/) - -## Collection -You have to use the artemis [api](../../API/overview.md) in order to collect PCA entries. - -## Sample API Script - -```typescript -import { parsePca } from "./artemis-api/mod"; - -function main() { - const results = parsePca(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -## Output Structure - -An array of `ProgramCompatibilityAssist` - -```typescript -export interface ProgramCompatibilityAssist { - last_run: string; - path: string; - run_status: number; - file_description: string; - vendor: string; - version: string; - program_id: string; - exit_message: string; - pca_type: PcaType; - message: string; - datetime: string; - source: string; - timestamp_desc: "Last Run"; - artifact: "Windows Program Compatibility Assist"; - data_type: "windows:pca:entry"; -} - -export enum PcaType { - AppLaunch = "AppLaunch", - General = "General", -} +--- +description: Program Compatability Assistant +keywords: + - windows +--- + +# PCA + +Windows Program Compatibility Assistant (PCA) tracks recent applications that are executed. + +References: +- [DFIR blog](https://aboutdfir.com/new-windows-11-pro-22h2-evidence-of-execution-artifact/) + +## Collection +You have to use the artemis [api](../../API/overview.md) in order to collect PCA entries. + +## Sample API Script + +```typescript +import { parsePca } from "./artemis-api/mod"; + +function main() { + const results = parsePca(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `ProgramCompatibilityAssist` + +```typescript +export interface ProgramCompatibilityAssist { + last_run: string; + path: string; + run_status: number; + file_description: string; + vendor: string; + version: string; + program_id: string; + exit_message: string; + pca_type: PcaType; + message: string; + datetime: string; + evidence: string; + timestamp_desc: "Last Run"; + artifact: "Windows Program Compatibility Assist"; + data_type: "windows:pca:entry"; +} + +export enum PcaType { + AppLaunch = "AppLaunch", + General = "General", +} ``` \ No newline at end of file diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/powershell.md b/artemis-docs/docs/Artifacts/Windows Artfacts/powershell.md index 303d93e2..825967b2 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/powershell.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/powershell.md @@ -1,59 +1,59 @@ ---- -description: PowerShell history entries -keywords: - - windows - - plaintext ---- - -# PowerShell History - -Artemis support extracting PowerShell history entries from Windows systems. -Modern versions of PowerShell will now write commands executed to a history file -on the system. - -Depending on the platform artemis will try to parse PowerShell history for all users at: - -- \\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt -- /Users/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt -- /home/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt - -You may also provide an optional alternative path to ConsoleHost_history.txt. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -Logon entries. - -## Sample API Script - -```typescript -import { - powershellHistory, -} from "./artemis-api/mod"; - -function main() { - const results = powershellHistory(); - - console.log(results); -} -``` - -## Output Structure - -An array of `History` - -```typescript -export interface History { - line: string; - path: string; - created: string; - modified: string; - accessed: string; - changed: string; - message: string; - datetime: string; - timestamp_desc: "PowerShell History Modified"; - artifact: "PowerShell History"; - data_type: "application:powershell:entry"; -} -``` +--- +description: PowerShell history entries +keywords: + - windows + - plaintext +--- + +# PowerShell History + +Artemis support extracting PowerShell history entries from Windows systems. +Modern versions of PowerShell will now write commands executed to a history file +on the system. + +Depending on the platform artemis will try to parse PowerShell history for all users at: + +- \\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt +- /Users/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt +- /home/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt + +You may also provide an optional alternative path to ConsoleHost_history.txt. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +Logon entries. + +## Sample API Script + +```typescript +import { + powershellHistory, +} from "./artemis-api/mod"; + +function main() { + const results = powershellHistory(); + + console.log(results); +} +``` + +## Output Structure + +An array of `History` + +```typescript +export interface History { + line: string; + evidence: string; + created: string; + modified: string; + accessed: string; + changed: string; + message: string; + datetime: string; + timestamp_desc: "PowerShell History Modified"; + artifact: "PowerShell History"; + data_type: "application:powershell:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/prefetch.md b/artemis-docs/docs/Artifacts/Windows Artfacts/prefetch.md index e455ddf0..32715303 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/prefetch.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/prefetch.md @@ -1,83 +1,83 @@ ---- -description: Tracks execution of files on workstations -keywords: - - windows - - binary ---- - -# Prefetch - -Windows Prefetch data tracks execution of applications on Windows -Workstations. Prefetch files are typically located at `C:\Windows\Prefetch`. -On Windows servers Prefetch is disabled on servers and may also be disabled on systems -with SSDs. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.prefetch/) - -References: -[Libyal](https://github.com/libyal/libscca/blob/main/documentation/Windows%20Prefetch%20File%20(PF)%20format.asciidoc) - -## TOML Collection - -```toml -[output] -name = "prefetch_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "prefetch" -[artifacts.prefetch] -# Optional -# alt_dir = "C:\\Artifacts" -``` - -## Collection Options - -- `alt_dir` Full path to an alternative directory containing Prefetch files. - This configuration is **optional**. By default artemis parse all Prefetch - files on the system at the default location - -## Output Structure - -An array of `Prefetch` entries - -```typescript -export interface Prefetch { - /**Path to prefetch file */ - path: string; - /**Name of executed file */ - filename: string; - /**Prefetch hash */ - hash: string; - /**Most recent execution timestamp */ - last_run_time: string; - /**Array of up to eight (8) execution timestamps */ - all_run_times: string[]; - /**Number of executions */ - run_count: number; - /**Size of executed file */ - size: number; - /**Array of volume serial numbers associated with accessed files/directories */ - volume_serial: string[]; - /**Array of volume creation timestamps associated with accessed files/directories */ - volume_creation: string[]; - /**Array of volumes associated accessed files/directories */ - volume_path: string[]; - /**Number of files accessed by executed file */ - accessed_file_count: number; - /**Number of directories accessed by executed file */ - accessed_directories_count: number; - /**Array of accessed files by executed file */ - accessed_files: string[]; - /**Array of accessed directories by executed file */ - accessed_directories: string[]; -} -``` +--- +description: Tracks execution of files on workstations +keywords: + - windows + - binary +--- + +# Prefetch + +Windows Prefetch data tracks execution of applications on Windows +Workstations. Prefetch files are typically located at `C:\Windows\Prefetch`. +On Windows servers Prefetch is disabled on servers and may also be disabled on systems +with SSDs. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.prefetch/) + +References: +[Libyal](https://github.com/libyal/libscca/blob/main/documentation/Windows%20Prefetch%20File%20(PF)%20format.asciidoc) + +## TOML Collection + +```toml +[output] +name = "prefetch_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "prefetch" +[artifacts.prefetch] +# Optional +# alt_dir = "C:\\Artifacts" +``` + +## Collection Options + +- `alt_dir` Full path to an alternative directory containing Prefetch files. + This configuration is **optional**. By default artemis parse all Prefetch + files on the system at the default location + +## Output Structure + +An array of `Prefetch` entries + +```typescript +export interface Prefetch { + /**Path to prefetch file */ + evidence: string; + /**Name of executed file */ + filename: string; + /**Prefetch hash */ + hash: string; + /**Most recent execution timestamp */ + last_run_time: string; + /**Array of up to eight (8) execution timestamps */ + all_run_times: string[]; + /**Number of executions */ + run_count: number; + /**Size of executed file */ + size: number; + /**Array of volume serial numbers associated with accessed files/directories */ + volume_serial: string[]; + /**Array of volume creation timestamps associated with accessed files/directories */ + volume_creation: string[]; + /**Array of volumes associated accessed files/directories */ + volume_path: string[]; + /**Number of files accessed by executed file */ + accessed_file_count: number; + /**Number of directories accessed by executed file */ + accessed_directories_count: number; + /**Array of accessed files by executed file */ + accessed_files: string[]; + /**Array of accessed directories by executed file */ + accessed_directories: string[]; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/processtree.md b/artemis-docs/docs/Artifacts/Windows Artfacts/processtree.md index 28325a82..58b392d4 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/processtree.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/processtree.md @@ -1,60 +1,60 @@ ---- -description: Windows process trees -keywords: - - windows - - eventlogs ---- - -# Process Tree - -Artemis supports creating a process tree from the Windows Security.evtx EventLog. Specifically from the 4688 entries. -Artemis can read each of these log entries and attempt to create a process tree from the data - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to reconstruct process trees. - -## Sample API Script - -```typescript -import { processTreeEventLogs } from "./artemis-api/mod"; - -function main() { - const results = processTreeEventLogs(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -## Output Structure - -An array of `EventLogProcessTree` - -```typescript -/** - * Object representing a reassembled process tree from 4688 Security EventLog - * This object is Timesketch compatible. It does **not** need to be timeline - */ -export interface EventLogProcessTree { - pid: number; - parent_pid: number; - process_name: string; - process_path: string; - parent_name: string; - parent_path: string; - user: string; - sid: string; - domain: string; - commandline: string; - /**Complete process tree for a process */ - message: string; - datetime: string; - timestamp_desc: "EventLog Generated"; - artifact: "EventLogs Process Tree"; - evtx_path: string; - data_type: "windows:eventlogs:proctree:entry"; - record: number; - logon_id: number; -} -``` +--- +description: Windows process trees +keywords: + - windows + - eventlogs +--- + +# Process Tree + +Artemis supports creating a process tree from the Windows Security.evtx EventLog. Specifically from the 4688 entries. +Artemis can read each of these log entries and attempt to create a process tree from the data + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to reconstruct process trees. + +## Sample API Script + +```typescript +import { processTreeEventLogs } from "./artemis-api/mod"; + +function main() { + const results = processTreeEventLogs(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `EventLogProcessTree` + +```typescript +/** + * Object representing a reassembled process tree from 4688 Security EventLog + * This object is Timesketch compatible. It does **not** need to be timeline + */ +export interface EventLogProcessTree { + pid: number; + parent_pid: number; + process_name: string; + process_path: string; + parent_name: string; + parent_path: string; + user: string; + sid: string; + domain: string; + commandline: string; + /**Complete process tree for a process */ + message: string; + datetime: string; + timestamp_desc: "EventLog Generated"; + artifact: "EventLogs Process Tree"; + evidence: string; + data_type: "windows:eventlogs:proctree:entry"; + record: number; + logon_id: number; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/rawfiles.md b/artemis-docs/docs/Artifacts/Windows Artfacts/rawfiles.md index 59760e36..3499bc4a 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/rawfiles.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/rawfiles.md @@ -1,169 +1,169 @@ ---- -description: Windows NTFS file metadata -keywords: - - windows - - file meta ---- - -# Raw Files - -A raw Windows filelisting by parsing the NTFS file system using the -[ntfs](https://github.com/ColinFinck/ntfs) crate to recursively walk the files -and directories on the system. If hashing or PE parsing is enabled this will -also read the file contents. Raw Files also supports decompressing compressed -files with the **WofCompression** alternative data stream (ADS) attribute. - -Since a filelisting can be extremely large, every 100k entries artemis will -output the data and then continue. - -Other Parsers: - -- Any tool that parse the NTFS file system - -References: - -- [Libyal](https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc) - -## TOML Collection - -```toml -[output] -name = "ntfs_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "rawfiles" -[artifacts.rawfiles] -drive_letter = 'C' -start_path = "C:\\" -depth = 20 -recover_indx = false -# Optional -metadata = true # Get PE metadata -# Optional -md5 = false -# Optional -sha1 = false -# Optional -sha256 = false -# Optional -metadata = false -# Optional -path_regex = "" -# Optional -filename_regex = "" -``` - -## Collection Options - -- `drive_letter` Drive letter to use to parse the NTFS file system. This - configuration is **required** -- `start_path` Directory to start walking the filesystem. This configuration is - **required** -- `depth` How many directories to descend from the `start_path`. Must be a - positive number. Max value is 255. This configuration is **required** -- `recover_indx` Boolean value to carve deleted entries from the $INDX - attribute. Can show evidence of deleted files -- `metadata` Get [PE](pe.md) data from PE files. This configuration is - **optional**. Default is **false** -- `md5` Boolean value to enable MD5 hashing on all files. This configuration is - **optional**. Default is **false** -- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration - is **optional**. Default is **false** -- `sha256` Boolean value to enable SHA256 hashing on all files. This - configuration is **optional**. Default is **false** -- `path_regex` Only descend into paths (directories) that match the provided - regex. This configuration is **optional**. Default is no Regex -- `file_regex` Only return entres that match the provided regex. This - configuration is **optional**. Default is no Regex - -## Output Structure - -An array of `WindowsRawFileInfo` entries - -```typescript -export interface RawFileInfo { - /**Full path to file or directory */ - full_path: string; - /**Directory path */ - directory: string; - /**Filename */ - filename: string; - /**Extension of file if any */ - extension: string; - /**Created timestamp */ - created: string; - /**Modified timestamp */ - modified: string; - /**Changed timestamp */ - changed: string; - /**Accessed timestamp */ - accessed: string; - /**Filename created timestamp */ - filename_created: string; - /**Filename modified timestamp */ - filename_modified: string; - /**Filename accessed timestamp */ - filename_accessed: string; - /**Filename changed timestamp */ - filename_changed: string; - /**Size of file in bytes */ - size: number; - /**Size of file if compressed */ - compressed_size: number; - /**Compression type used on file */ - compression_type: string; - /**Inode entry */ - inode: number; - /**Sequence number for entry */ - sequence_number: number; - /**Parent MFT reference for entry */ - parent_mft_references: number; - /**Attributes associated with entry */ - attributes: AttributeFlags[]; - /**MD5 of file. Optional */ - md5: string; - /**SHA1 of file. Optional */ - sha1: string; - /**SHA256 of file. Optional */ - sha256: string; - /**Is the entry a file */ - is_file: boolean; - /**Is the entry a directory */ - is_directory: boolean; - /** Is the entry carved from $INDX */ - is_indx: boolean; - /**USN entry */ - usn: number; - /**SID number associated with entry */ - sid: number; - /**SID string associated with entry*/ - user_sid: string; - /**Group SID associated with entry */ - group_sid: string; - /**Drive letter */ - drive: string; - /**ADS info associated with entry */ - ads_info: AdsInfo[]; - /**Depth the file from provided start point*/ - depth: number; - /**PE binary metadata. Optional */ - binary_info: PeInfo[]; -} - -/** - * Alternative Data Streams (ADS) are a NTFS feature to embed data in another data stream - */ -export interface AdsInfo { - /**Name of the ADS entry */ - name: string; - /**Size of the ADS entry */ - size: number; -} -``` +--- +description: Windows NTFS file metadata +keywords: + - windows + - file meta +--- + +# Raw Files + +A raw Windows filelisting by parsing the NTFS file system using the +[ntfs](https://github.com/ColinFinck/ntfs) crate to recursively walk the files +and directories on the system. If hashing or PE parsing is enabled this will +also read the file contents. Raw Files also supports decompressing compressed +files with the **WofCompression** alternative data stream (ADS) attribute. + +Since a filelisting can be extremely large, every 10k entries artemis will +output the data and then continue. + +Other Parsers: + +- Any tool that parse the NTFS file system + +References: + +- [Libyal](https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc) + +## TOML Collection + +```toml +[output] +name = "ntfs_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "rawfiles" +[artifacts.rawfiles] +drive_letter = 'C' +start_path = "C:\\" +depth = 20 +recover_indx = false +# Optional +metadata = true # Get PE metadata +# Optional +md5 = false +# Optional +sha1 = false +# Optional +sha256 = false +# Optional +metadata = false +# Optional +path_regex = "" +# Optional +filename_regex = "" +``` + +## Collection Options + +- `drive_letter` Drive letter to use to parse the NTFS file system. This + configuration is **required** +- `start_path` Directory to start walking the filesystem. This configuration is + **required** +- `depth` How many directories to descend from the `start_path`. Must be a + positive number. Max value is 255. This configuration is **required** +- `recover_indx` Boolean value to carve deleted entries from the $INDX + attribute. Can show evidence of deleted files +- `metadata` Get [PE](pe.md) data from PE files. This configuration is + **optional**. Default is **false** +- `md5` Boolean value to enable MD5 hashing on all files. This configuration is + **optional**. Default is **false** +- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration + is **optional**. Default is **false** +- `sha256` Boolean value to enable SHA256 hashing on all files. This + configuration is **optional**. Default is **false** +- `path_regex` Only descend into paths (directories) that match the provided + regex. This configuration is **optional**. Default is no Regex +- `file_regex` Only return entres that match the provided regex. This + configuration is **optional**. Default is no Regex + +## Output Structure + +An array of `WindowsRawFileInfo` entries + +```typescript +export interface RawFileInfo { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Filename created timestamp */ + filename_created: string; + /**Filename modified timestamp */ + filename_modified: string; + /**Filename accessed timestamp */ + filename_accessed: string; + /**Filename changed timestamp */ + filename_changed: string; + /**Size of file in bytes */ + size: number; + /**Size of file if compressed */ + compressed_size: number; + /**Compression type used on file */ + compression_type: string; + /**Inode entry */ + inode: number; + /**Sequence number for entry */ + sequence_number: number; + /**Parent MFT reference for entry */ + parent_mft_references: number; + /**Attributes associated with entry */ + attributes: AttributeFlags[]; + /**MD5 of file. Optional */ + md5: string; + /**SHA1 of file. Optional */ + sha1: string; + /**SHA256 of file. Optional */ + sha256: string; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /** Is the entry carved from $INDX */ + is_indx: boolean; + /**USN entry */ + usn: number; + /**SID number associated with entry */ + sid: number; + /**SID string associated with entry*/ + user_sid: string; + /**Group SID associated with entry */ + group_sid: string; + /**Drive letter */ + drive: string; + /**ADS info associated with entry */ + ads_info: AdsInfo[]; + /**Depth the file from provided start point*/ + depth: number; + /**PE binary metadata. Optional */ + binary_info: PeInfo[]; +} + +/** + * Alternative Data Streams (ADS) are a NTFS feature to embed data in another data stream + */ +export interface AdsInfo { + /**Name of the ADS entry */ + name: string; + /**Size of the ADS entry */ + size: number; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/rdp.md b/artemis-docs/docs/Artifacts/Windows Artfacts/rdp.md index 93841e5b..63c8f9e5 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/rdp.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/rdp.md @@ -1,59 +1,60 @@ ---- -description: Windows RDP Logon events -keywords: - - windows - - eventlogs ---- - -# RDP - -Artemis supports extracting RDP Logon entries from the Windows EventLog -Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx file. Artemis will try to correlate logon and logoff entries. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -Logon entries. - -## Sample API Script - -```typescript -import { rdpLogons } from "./artemis-api/mod"; - -function main() { - const values = rdpLogons(); - console.log(JSON.stringify(values)); -} - -main(); -``` - -## Output Structure - -An array of `RdpActivity` - -```typescript -/** - * Parsed RDP events - */ -export interface RdpActivity { - /**Session ID associated with RDP */ - session_id: number; - /**User associated RDP logon */ - user: string; - /**Domain associated with the user */ - domain: string; - /**Account associated with the RDP logon */ - account: string; - source_ip: string; - /**Hostname associated with evtx file */ - hostname: string; - /**Activity ID associated with EventLog entry */ - activity_id: string; - message: string; - datetime: string; - timestamp_desc: "RDP Logon" | "RDP Reconnect" | "RDP Logoff" | "RDP Disconnect"; - artifact: "RDP EventLog"; - data_type: "windows:eventlogs:rdp:entry"; -} -``` +--- +description: Windows RDP Logon events +keywords: + - windows + - eventlogs +--- + +# RDP + +Artemis supports extracting RDP Logon entries from the Windows EventLog +Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx file. Artemis will try to correlate logon and logoff entries. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +RDP entries. + +## Sample API Script + +```typescript +import { rdpLogons } from "./artemis-api/mod"; + +function main() { + const values = rdpLogons(); + console.log(JSON.stringify(values)); +} + +main(); +``` + +## Output Structure + +An array of `RdpActivity` + +```typescript +/** + * Parsed RDP events + */ +export interface RdpActivity { + /**Session ID associated with RDP */ + session_id: number; + /**User associated RDP logon */ + user: string; + /**Domain associated with the user */ + domain: string; + /**Account associated with the RDP logon */ + account: string; + source_ip: string; + /**Hostname associated with evtx file */ + hostname: string; + /**Activity ID associated with EventLog entry */ + activity_id: string; + message: string; + datetime: string; + timestamp_desc: "RDP Logon" | "RDP Reconnect" | "RDP Logoff" | "RDP Disconnect"; + artifact: "RDP EventLog"; + data_type: "windows:eventlogs:rdp:entry"; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/recyclebin.md b/artemis-docs/docs/Artifacts/Windows Artfacts/recyclebin.md index 799e3d59..2ecc1951 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/recyclebin.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/recyclebin.md @@ -1,77 +1,77 @@ ---- -description: Windows files in the RecycleBin -keywords: - - windows - - file meta ---- - -# RecycleBin - -Windows RecycleBin is a special folder on Windows that stores files deleted -using the Explorer GUI. When a file is deleted (via Explorer) two types files -are generated in the RecycleBin: - -- Files that begin with `$I.`. Contains metadata - about deleted file -- Files that begin with `$R.`. Contents of deleted - file - -Currently artemis supports parsing the $I based files using the standard -Windows APIs to read the file for parsing. It does not try to recover files that -have been deleted/emptied from the RecycleBin - -Other parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.recyclebin/) - -References: - -- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/Windows%20Recycle.Bin%20file%20formats.asciidoc) -- [RecycleBin](https://cybersecurity.att.com/blogs/security-essentials/digital-dumpster-diving-exploring-the-intricacies-of-recycle-bin-forensics) - -## TOML Collection - -```toml -[output] -name = "recyclebin_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "recyclebin" -[artifacts.recyclebin] -# alt_file = "C:\\Artifacts\\$IAC12F" -``` - -## Collection Options - -- `alt_file` Full path to alternative RecycleBin file. This configuration is - **optional**. By default artemis will parse all RecycleBin files on the system - -## Output Structure - -An array of `RecycleBin` entries - -```typescript -export interface RecycleBin { - /**Size of deleted file */ - size: number; - /**Deleted timestamp of file */ - deleted: string; - /**Name of deleted file */ - filename: string; - /**Full path to the deleted file */ - full_path: string; - /**Directory associated with deleted file */ - directory: string; - /**SID associated with the deleted file */ - sid: string; - /**Path to the file in the Recycle Bin */ - recycle_path: string; -} -``` +--- +description: Windows files in the RecycleBin +keywords: + - windows + - file meta +--- + +# RecycleBin + +Windows RecycleBin is a special folder on Windows that stores files deleted +using the Explorer GUI. When a file is deleted (via Explorer) two types files +are generated in the RecycleBin: + +- Files that begin with `$I.`. Contains metadata + about deleted file +- Files that begin with `$R.`. Contents of deleted + file + +Currently artemis supports parsing the $I based files using the standard +Windows APIs to read the file for parsing. It does not try to recover files that +have been deleted/emptied from the RecycleBin + +Other parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.recyclebin/) + +References: + +- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/Windows%20Recycle.Bin%20file%20formats.asciidoc) +- [RecycleBin](https://cybersecurity.att.com/blogs/security-essentials/digital-dumpster-diving-exploring-the-intricacies-of-recycle-bin-forensics) + +## TOML Collection + +```toml +[output] +name = "recyclebin_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "recyclebin" +[artifacts.recyclebin] +# alt_file = "C:\\Artifacts\\$IAC12F" +``` + +## Collection Options + +- `alt_file` Full path to alternative RecycleBin file. This configuration is + **optional**. By default artemis will parse all RecycleBin files on the system + +## Output Structure + +An array of `RecycleBin` entries + +```typescript +export interface RecycleBin { + /**Size of deleted file */ + size: number; + /**Deleted timestamp of file */ + deleted: string; + /**Name of deleted file */ + filename: string; + /**Full path to the deleted file */ + full_path: string; + /**Directory associated with deleted file */ + directory: string; + /**SID associated with the deleted file */ + sid: string; + /**Path to the file in the Recycle Bin */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/registry.md b/artemis-docs/docs/Artifacts/Windows Artfacts/registry.md index 9c59c673..5577a37a 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/registry.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/registry.md @@ -1,125 +1,125 @@ ---- -description: Primary source of Windows configuration settings -keywords: - - windows - - registry ---- - -# Registry - -Windows Registry is a collection of binary files that store Windows -configuration settings and OS information. There are multiple Registry files -on a system such as: - -- `C:\Windows\System32\config\SYSTEM` -- `C:\Windows\System32\config\SOFTWARE` -- `C:\Windows\System32\config\SAM` -- `C:\Windows\System32\config\SECURITY` -- `C:\Users\%\NTUSER.DAT` -- `C:\Users\%\AppData\Local\Microsoft\Windows\UsrClass.dat` - -Other Parser: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.registry.ntuser/) - -References: - -- [Libyal](https://github.com/libyal/libregf) -- [Registry Format](https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md) - -## TOML Collection - -```toml -[output] -name = "registry_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "registry" # Parses the whole Registry file -[artifacts.registry] -user_hives = true # All NTUSER.DAT and UsrClass.dat -system_hives = true # SYSTEM, SOFTWARE, SAM, SECURITY -# Optional -# alt_file = "C:\\Artifacts\\SYSTEM" -# Optional -# path_regex = "" # Registry is converted to lowercase before all comparison operations. So any regex input will also be converted to lowercase -``` - -## Collection Options - -- `user_hives` Parse all user Registry files NTUSER.DAT and UsrClass.dat. - This configuration is **required** -- `system_hives` Parse all system Registry files SYSTEM, SAM, SOFTWARE, - `SECURITY`. This configuration is **required** -- `alt_file` Full path to alternative Registry file. This configuration is - **optional**. -- `path_regex` Only return Registry keys that match provided `path_regex`. All - comparisons are first converted to lowercase. This configuration is - **optional**. Default is no Regex - -## Output Structure - -An array of `Registry` entries for each parsed file - -```typescript -/** - * Inteface representing the parsed `Registry` structure - */ -export interface Registry { - /** - * Full path to `Registry` key and name. - * Ex: ` ROOT\...\CurrentVersion\Run` - */ - path: string; - /** - * Path to Key - * Ex: ` ROOT\...\CurrentVersion` - */ - key: string; - /** - * Key name - * Ex: `Run` - */ - name: string; - /** - * Values associated with key name - * Ex: `Run => Vmware`. Where Run is the `key` name and `Vmware` is the value name - */ - values: Value[]; - /**Timestamp of when the path was last modified */ - last_modified: number; - /**Depth of key name */ - depth: number; - /**Path to Registry file */ - registry_path: string; - /**Registry file name */ - registry_file: string; -} - -/** - * The value data associated with Registry key - * References: - * https://github.com/libyal/libregf - * https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md - */ -export interface Value { - /**Name of Value */ - value: string; - /** - * Data associated with value. All types are strings by default. The real type can be determined by `data_type`. - * `REG_BINARY` is a base64 encoded string - */ - data: string; - /** - * Value type. - * Full list of types at: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types - */ - data_type: string; -} -``` +--- +description: Primary source of Windows configuration settings +keywords: + - windows + - registry +--- + +# Registry + +Windows Registry is a collection of binary files that store Windows +configuration settings and OS information. There are multiple Registry files +on a system such as: + +- `C:\Windows\System32\config\SYSTEM` +- `C:\Windows\System32\config\SOFTWARE` +- `C:\Windows\System32\config\SAM` +- `C:\Windows\System32\config\SECURITY` +- `C:\Users\%\NTUSER.DAT` +- `C:\Users\%\AppData\Local\Microsoft\Windows\UsrClass.dat` + +Other Parser: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.registry.ntuser/) + +References: + +- [Libyal](https://github.com/libyal/libregf) +- [Registry Format](https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md) + +## TOML Collection + +```toml +[output] +name = "registry_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "registry" # Parses the whole Registry file +[artifacts.registry] +user_hives = true # All NTUSER.DAT and UsrClass.dat +system_hives = true # SYSTEM, SOFTWARE, SAM, SECURITY +# Optional +# alt_file = "C:\\Artifacts\\SYSTEM" +# Optional +# path_regex = "" # Registry is converted to lowercase before all comparison operations. So any regex input will also be converted to lowercase +``` + +## Collection Options + +- `user_hives` Parse all user Registry files NTUSER.DAT and UsrClass.dat. + This configuration is **required** +- `system_hives` Parse all system Registry files SYSTEM, SAM, SOFTWARE, + `SECURITY`. This configuration is **required** +- `alt_file` Full path to alternative Registry file. This configuration is + **optional**. +- `path_regex` Only return Registry keys that match provided `path_regex`. All + comparisons are first converted to lowercase. This configuration is + **optional**. Default is no Regex + +## Output Structure + +An array of `Registry` entries for each parsed file + +```typescript +/** + * Inteface representing the parsed `Registry` structure + */ +export interface Registry { + /** + * Full path to `Registry` key and name. + * Ex: ` ROOT\...\CurrentVersion\Run` + */ + path: string; + /** + * Path to Key + * Ex: ` ROOT\...\CurrentVersion` + */ + key: string; + /** + * Key name + * Ex: `Run` + */ + name: string; + /** + * Values associated with key name + * Ex: `Run => Vmware`. Where Run is the `key` name and `Vmware` is the value name + */ + values: Value[]; + /**Timestamp of when the path was last modified */ + last_modified: number; + /**Depth of key name */ + depth: number; + /**Path to Registry file */ + evidence: string; + /**Registry file name */ + registry_file: string; +} + +/** + * The value data associated with Registry key + * References: + * https://github.com/libyal/libregf + * https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md + */ +export interface Value { + /**Name of Value */ + value: string; + /** + * Data associated with value. All types are strings by default. The real type can be determined by `data_type`. + * `REG_BINARY` is a base64 encoded string + */ + data: string; + /** + * Value type. + * Full list of types at: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types + */ + data_type: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/runkeys.md b/artemis-docs/docs/Artifacts/Windows Artfacts/runkeys.md index 727579ae..aabd1ec4 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/runkeys.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/runkeys.md @@ -1,55 +1,55 @@ ---- -description: Windows Registry Run Keys - - windows - - registry ---- - -# Registry Run Keys - -Artemis supports extracting the Windows Registry Run key information from several different Registry files: -- NTUSER.DAT -- SOFTWARE - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Windows Run Keys. - -## Sample API Script - -```typescript -import { getRunKeys } from "./artemis-api/mod"; - -function main() { - const results = getRunKeys(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -## Output Structure - -An array of `RegistryRunKey` - -```typescript -export interface RegistryRunKey { - key_modified: string; - key_path: string; - registry_path: string; - registry_file: string; - path: string; - /**When file was created */ - created: string; - has_signature: boolean; - md5: string; - sha1: string; - sha256: string; - value: string; - name: string; - message: string; - datetime: string; - timestamp_desc: "Registry Last Modified"; - artifact: "Windows Registry Run Key"; - data_type: "windows:registry:run:entry"; -} -``` +--- +description: Windows Registry Run Keys + - windows + - registry +--- + +# Registry Run Keys + +Artemis supports extracting the Windows Registry Run key information from several different Registry files: +- NTUSER.DAT +- SOFTWARE + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Windows Run Keys. + +## Sample API Script + +```typescript +import { getRunKeys } from "./artemis-api/mod"; + +function main() { + const results = getRunKeys(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `RegistryRunKey` + +```typescript +export interface RegistryRunKey { + key_modified: string; + key_path: string; + evidence: string; + registry_file: string; + path: string; + /**When file was created */ + created: string; + has_signature: boolean; + md5: string; + sha1: string; + sha256: string; + value: string; + name: string; + message: string; + datetime: string; + timestamp_desc: "Registry Last Modified"; + artifact: "Windows Registry Run Key"; + data_type: "windows:registry:run:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/scriptblocks.md b/artemis-docs/docs/Artifacts/Windows Artfacts/scriptblocks.md index f8521b88..9fe15763 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/scriptblocks.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/scriptblocks.md @@ -1,64 +1,65 @@ ---- -description: Windows Scriptblock events -keywords: - - windows - - eventlogs ---- - -# Scriptblocks - -Artemis supports extracting and reassembling PowerShell Scriptblock entries from the Windows PowerShell EventLog. Whenever a large PowerShell script or command is executed, Windows will split the contents of the PowerShell script into multiple EventLog entries. - -Artemis can read each of these log entries and reconstruct the original script. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to reassemble PowerShell Scriptblocks. - -## Sample API Script - -```typescript -import { assembleScriptblocks } from "./artemis-api/mod"; - -function main() { - // Can provide optional alt path to Microsoft-Windows-PowerShell%4Operational.evtx - const data = assembleScriptblocks(); - console.log(JSON.stringify(data)); -} - -main(); -``` - -## Output Structure - -An array of `Scriptblock` - -```typescript -/** - * Object representing a sync log entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface Scriptblock { - total_parts: number; - message: string; - datetime: string; - timestamp_desc: "EventLog Entry Created"; - data_type: "windows:eventlogs:powershell:scriptblock:entry"; - artifact: "Windows PowerShell Scriptblock"; - id: string; - source_file: string; - path: string; - script_length: number; - has_signature_block: boolean; - has_copyright_string: boolean; - hostname: string; - version: number; - activity_id: string; - channel: string; - user_id: string; - process_id: number; - threat_id: number; - system_time: string; - created_time: string; -} -``` +--- +description: Windows Scriptblock events +keywords: + - windows + - eventlogs +--- + +# Scriptblocks + +Artemis supports extracting and reassembling PowerShell Scriptblock entries from the Windows PowerShell EventLog. Whenever a large PowerShell script or command is executed, Windows will split the contents of the PowerShell script into multiple EventLog entries. + +Artemis can read each of these log entries and reconstruct the original script. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to reassemble PowerShell Scriptblocks. + +## Sample API Script + +```typescript +import { assembleScriptblocks } from "./artemis-api/mod"; + +function main() { + // Can provide optional alt path to Microsoft-Windows-PowerShell%4Operational.evtx + const data = assembleScriptblocks(); + console.log(JSON.stringify(data)); +} + +main(); +``` + +## Output Structure + +An array of `Scriptblock` + +```typescript +/** + * Object representing a sync log entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface Scriptblock { + total_parts: number; + message: string; + datetime: string; + timestamp_desc: "EventLog Entry Created"; + data_type: "windows:eventlogs:powershell:scriptblock:entry"; + artifact: "Windows PowerShell Scriptblock"; + id: string; + source_file: string; + path: string; + script_length: number; + has_signature_block: boolean; + has_copyright_string: boolean; + hostname: string; + version: number; + activity_id: string; + channel: string; + user_id: string; + process_id: number; + threat_id: number; + system_time: string; + created_time: string; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/search.md b/artemis-docs/docs/Artifacts/Windows Artfacts/search.md index 189f8ed1..3b2375bc 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/search.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/search.md @@ -1,135 +1,137 @@ ---- -description: The Windows Search database -keywords: - - windows - - ese - - sqlite ---- - -# Search - -Windows Search is an indexing service for tracking files and content on -Windows. - -Windows Search can parse a large amount of metadata (properties) for each entry it -indexes. It has almost 600 different types of properties it can parse. It can -even index part of the contents of a file. - -The Windows Search database can index large parts of the file system, so parsing the -database can provide a partial file listing of the system. Windows Search is disabled -on Windows Servers. On newer versions of Windows 11 it can be stored -in three (3) SQLITE databases, in addition the database may be encrypted. - - -The Windows Search database can get extremely large (4GB+ sizes have been seen). - -Similar to the filelisting artifact, every 100k entries artemis will output the -data and then continue. - - -Other parsers: - -- [sidr](https://github.com/strozfriedberg/sidr) -- [WinSearchDBAnalyzer](https://github.com/moaistory/WinSearchDBAnalyzer) -- [libesedb](https://github.com/libyal/libesedb) - -References: - -- [libyal](https://github.com/libyal/esedb-kb/blob/main/documentation/Windows%20Search.asciidoc) -- [Windows Search](https://en.wikipedia.org/wiki/Windows_Search) - -## TOML Collection - -```toml -[output] -name = "search_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "search" -[artifacts.search] -# Optional -# alt_path = "C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb" -``` - -## Collection Options - -- `alt_path` An alternative path to the Search ESE or SQLITE database. This - configuration is **optional**. By default artemis will use - **%systemdrive%\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb** - -## Output Structure - -An array of `SearchEntry` entries - -````typescript -export interface SearchEntry { - /**Index ID for row */ - document_id: number; - /**Search entry name */ - entry: string; - /**Search entry last modified */ - last_modified: string; - /** - * JSON object representing the properties associated with the entry - * - * Example: - * ``` - * "properties": { - "3-System_ItemFolderNameDisplay": "Programs", - "4429-System_IsAttachment": "0", - "4624-System_Search_AccessCount": "0", - "4702-System_VolumeId": "08542f90-0000-0000-0000-501f00000000", - "17F-System_DateAccessed": "k8DVxD162QE=", - "4392-System_FileExtension": ".lnk", - "4631F-System_Search_GatherTime": "7B6taj962QE=", - "5-System_ItemTypeText": "Shortcut", - "4184-System_ComputerName": "DESKTOP-EIS938N", - "15F-System_DateModified": "EVHzDyR22QE=", - "4434-System_IsFolder": "0", - "4365-System_DateImported": "ABKRqWyI1QE=", - "4637-System_Search_Store": "file", - "4373-System_Document_DateSaved": "EVHzDyR22QE=", - "4448-System_ItemPathDisplayNarrow": "Firefox (C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs)", - "4559-System_NotUserContent": "0", - "33-System_ItemUrl": "file:C:/ProgramData/Microsoft/Windows/Start Menu/Programs/Firefox.lnk", - "4447-System_ItemPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Firefox.lnk", - "13F-System_Size": "7QMAAAAAAAA=", - "4441-System_ItemFolderPathDisplayNarrow": "Programs (C:\\ProgramData\\Microsoft\\Windows\\Start Menu)", - "0-InvertedOnlyPids": "cBFzESgSZRI=", - "4443-System_ItemNameDisplay": "Firefox.lnk", - "4442-System_ItemName": "Firefox.lnk", - "14F-System_FileAttributes": "32", - "4403-System_FolderNameDisplay": "Cygwin", - "4565-System_ParsingName": "Firefox.lnk", - "4456-System_Kind": "bGluawBwcm9ncmFt", - "27F-System_Search_Rank": "707406378", - "16F-System_DateCreated": "UUZNqWyI1QE=", - "4440-System_ItemFolderPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs", - "4397-System_FilePlaceholderStatus": "6", - "4465-System_Link_TargetParsingPath": "C:\\Program Files\\Mozilla Firefox\\firefox.exe", - "4431-System_IsEncrypted": "0", - "4457-System_KindText": "Link; Program", - "4444-System_ItemNameDisplayWithoutExtension": "Firefox", - "11-System_FileName": "Firefox.lnk", - "4623-System_SFGAOFlags": "1078002039", - "0F-InvertedOnlyMD5": "z1gPcor92OaNVyAAzRdOsw==", - "4371-System_Document_DateCreated": "ABKRqWyI1QE=", - "4633-System_Search_LastIndexedTotalTime": "0.03125", - "4396-System_FileOwner": "Administrators", - "4438-System_ItemDate": "ABKRqWyI1QE=", - "4466-System_Link_TargetSFGAOFlags": "1077936503", - "4450-System_ItemType": ".lnk", - "4678-System_ThumbnailCacheId": "DzpSS6gn5yg=" - } - * ``` - */ - properties: Record; -} -```` +--- +description: The Windows Search database +keywords: + - windows + - ese + - sqlite +--- + +# Search + +Windows Search is an indexing service for tracking files and content on +Windows. + +Windows Search can parse a large amount of metadata (properties) for each entry it +indexes. It has almost 600 different types of properties it can parse. It can +even index part of the contents of a file. + +The Windows Search database can index large parts of the file system, so parsing the +database can provide a partial file listing of the system. Windows Search is disabled +on Windows Servers. On newer versions of Windows 11 it can be stored +in three (3) SQLITE databases, in addition the database may be encrypted. + + +The Windows Search database can get extremely large (4GB+ sizes have been seen). + +Similar to the filelisting artifact, every 100k entries artemis will output the +data and then continue. + + +Other parsers: + +- [sidr](https://github.com/strozfriedberg/sidr) +- [WinSearchDBAnalyzer](https://github.com/moaistory/WinSearchDBAnalyzer) +- [libesedb](https://github.com/libyal/libesedb) + +References: + +- [libyal](https://github.com/libyal/esedb-kb/blob/main/documentation/Windows%20Search.asciidoc) +- [Windows Search](https://en.wikipedia.org/wiki/Windows_Search) + +## TOML Collection + +```toml +[output] +name = "search_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "search" +[artifacts.search] +# Optional +# alt_file = "C:\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb" +``` + +## Collection Options + +- `alt_file` An alternative path to the Search ESE or SQLITE database. This + configuration is **optional**. By default artemis will use + **%systemdrive%\ProgramData\Microsoft\Search\Data\Applications\Windows\Windows.edb** + +## Output Structure + +An array of `SearchEntry` entries + +````typescript +export interface SearchEntry { + /**Index ID for row */ + document_id: number; + /**Search entry name */ + entry: string; + /**Search entry last modified */ + last_modified: string; + /** + * JSON object representing the properties associated with the entry + * + * Example: + * ``` + * "properties": { + "3-System_ItemFolderNameDisplay": "Programs", + "4429-System_IsAttachment": "0", + "4624-System_Search_AccessCount": "0", + "4702-System_VolumeId": "08542f90-0000-0000-0000-501f00000000", + "17F-System_DateAccessed": "k8DVxD162QE=", + "4392-System_FileExtension": ".lnk", + "4631F-System_Search_GatherTime": "7B6taj962QE=", + "5-System_ItemTypeText": "Shortcut", + "4184-System_ComputerName": "DESKTOP-EIS938N", + "15F-System_DateModified": "EVHzDyR22QE=", + "4434-System_IsFolder": "0", + "4365-System_DateImported": "ABKRqWyI1QE=", + "4637-System_Search_Store": "file", + "4373-System_Document_DateSaved": "EVHzDyR22QE=", + "4448-System_ItemPathDisplayNarrow": "Firefox (C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs)", + "4559-System_NotUserContent": "0", + "33-System_ItemUrl": "file:C:/ProgramData/Microsoft/Windows/Start Menu/Programs/Firefox.lnk", + "4447-System_ItemPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Firefox.lnk", + "13F-System_Size": "7QMAAAAAAAA=", + "4441-System_ItemFolderPathDisplayNarrow": "Programs (C:\\ProgramData\\Microsoft\\Windows\\Start Menu)", + "0-InvertedOnlyPids": "cBFzESgSZRI=", + "4443-System_ItemNameDisplay": "Firefox.lnk", + "4442-System_ItemName": "Firefox.lnk", + "14F-System_FileAttributes": "32", + "4403-System_FolderNameDisplay": "Cygwin", + "4565-System_ParsingName": "Firefox.lnk", + "4456-System_Kind": "bGluawBwcm9ncmFt", + "27F-System_Search_Rank": "707406378", + "16F-System_DateCreated": "UUZNqWyI1QE=", + "4440-System_ItemFolderPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs", + "4397-System_FilePlaceholderStatus": "6", + "4465-System_Link_TargetParsingPath": "C:\\Program Files\\Mozilla Firefox\\firefox.exe", + "4431-System_IsEncrypted": "0", + "4457-System_KindText": "Link; Program", + "4444-System_ItemNameDisplayWithoutExtension": "Firefox", + "11-System_FileName": "Firefox.lnk", + "4623-System_SFGAOFlags": "1078002039", + "0F-InvertedOnlyMD5": "z1gPcor92OaNVyAAzRdOsw==", + "4371-System_Document_DateCreated": "ABKRqWyI1QE=", + "4633-System_Search_LastIndexedTotalTime": "0.03125", + "4396-System_FileOwner": "Administrators", + "4438-System_ItemDate": "ABKRqWyI1QE=", + "4466-System_Link_TargetSFGAOFlags": "1077936503", + "4450-System_ItemType": ".lnk", + "4678-System_ThumbnailCacheId": "DzpSS6gn5yg=" + } + * ``` + */ + properties: Record; + /**Path to the Search database */ + evidence: string; +} +```` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/serviceinstall.md b/artemis-docs/docs/Artifacts/Windows Artfacts/serviceinstall.md index 799d972a..7cec206a 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/serviceinstall.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/serviceinstall.md @@ -1,54 +1,55 @@ ---- -description: Windows Service Install events -keywords: - - windows - - eventlogs ---- - -# Service Installs - -Artemis supports extracting Service Install events from the Windows EventLog -System.evtx file. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect Service Install entries. - -## Sample API Script - -```typescript -import { serviceInstalls } from "./artemis-api/src/windows/eventlogs/services"; - -function main() { - const data = serviceInstalls( - "C:\\Windows\\System32\\winevt\\Logs\\System.evtx", - ); - console.log(data); -} - -main(); -``` - -## Output Structure - -An array of `ServiceInstalls` - -```typescript -export interface ServiceInstalls { - name: string; - image_path: string; - service_type: string; - start_type: string; - account: string; - hostname: string; - timestamp: string; - process_id: number; - thread_id: number; - sid: string; - message: string; - datetime: string; - timestamp_desc: "Windows Service Installed"; - artifact: "EventLog Service 7045"; - data_type: "windows:eventlog:system:service"; -} -``` +--- +description: Windows Service Install events +keywords: + - windows + - eventlogs +--- + +# Service Installs + +Artemis supports extracting Service Install events from the Windows EventLog +System.evtx file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect Service Install entries. + +## Sample API Script + +```typescript +import { serviceInstalls } from "./artemis-api/src/windows/eventlogs/services"; + +function main() { + const data = serviceInstalls( + "C:\\Windows\\System32\\winevt\\Logs\\System.evtx", + ); + console.log(data); +} + +main(); +``` + +## Output Structure + +An array of `ServiceInstalls` + +```typescript +export interface ServiceInstalls { + name: string; + image_path: string; + service_type: string; + start_type: string; + account: string; + hostname: string; + timestamp: string; + process_id: number; + thread_id: number; + sid: string; + message: string; + datetime: string; + timestamp_desc: "Windows Service Installed"; + artifact: "EventLog Service 7045"; + data_type: "windows:eventlog:system:service"; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/services.md b/artemis-docs/docs/Artifacts/Windows Artfacts/services.md index 499c7f5f..7d6cfdca 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/services.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/services.md @@ -1,100 +1,102 @@ ---- -description: Services installed on Windows -keywords: - - windows - - registry - - persistence ---- - -# Services - -Windows `Services` are a common form of persistence and privilege escalation on -Windows systems. Service data is stored in the SYSTEM Registry file. -Services run with SYSTEM level privileges. - -Other Parsers: - -- Any tool that can read the Registry -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.system.services/) - -References: - -- [Services](https://forensafe.com/blogs/windowsservices.html) -- [Velociraptor](https://github.com/Velocidex/velociraptor/blob/master/artifacts/definitions/Windows/System/Services.yaml) -- [Libyal](https://winreg-kb.readthedocs.io/en/latest/sources/system-keys/Services-and-drivers.html) - -## TOML Collection - -```toml -[output] -name = "services_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "services" -[artifacts.services] -# alt_file = "C:\\Artifacts\\SYSTEM" -``` - -## Collection Options - -- `alt_file` Full path to alternative SYSTEM Registry file. This configuration - is **optional**. By default artemis will parse the SYSTEM Registry at the - default location. - -## Output Structure - -An array of `Services` entries - -```typescript -export interface Services { - /**Current State of the Service */ - state: string; - /**Name of Service */ - name: string; - /**Display name of Service */ - display_name: string; - /**Service description */ - description: string; - /**Start mode of Service */ - start_mode: string; - /**Path to executable for Service */ - path: string; - /**Service types. Ex: KernelDriver */ - service_type: string[]; - /**Account associated with Service */ - account: string; - /**Registry modified timestamp. May be used to determine when the Service was created */ - modified: string; - /**DLL associated with Service */ - service_dll: string; - /**Service command upon failure */ - failure_command: string; - /**Reset period associated with Service */ - reset_period: number; - /**Service actions upon failure */ - failure_actions: FailureActions[]; - /**Privileges associated with Service */ - required_privileges: string[]; - /**Error associated with Service */ - error_control: string; - /**Registry path associated with Service */ - reg_path: string; -} - -/** - * Failure actions executed when Service fails - */ -interface FailureActions { - /**Action executed upon failure */ - action: string; - /**Delay in seconds on failure */ - delay: number; -} -``` +--- +description: Services installed on Windows +keywords: + - windows + - registry + - persistence +--- + +# Services + +Windows `Services` are a common form of persistence and privilege escalation on +Windows systems. Service data is stored in the SYSTEM Registry file. +Services run with SYSTEM level privileges. + +Other Parsers: + +- Any tool that can read the Registry +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.system.services/) + +References: + +- [Services](https://forensafe.com/blogs/windowsservices.html) +- [Velociraptor](https://github.com/Velocidex/velociraptor/blob/master/artifacts/definitions/Windows/System/Services.yaml) +- [Libyal](https://winreg-kb.readthedocs.io/en/latest/sources/system-keys/Services-and-drivers.html) + +## TOML Collection + +```toml +[output] +name = "services_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "services" +[artifacts.services] +# alt_file = "C:\\Artifacts\\SYSTEM" +``` + +## Collection Options + +- `alt_file` Full path to alternative SYSTEM Registry file. This configuration + is **optional**. By default artemis will parse the SYSTEM Registry at the + default location. + +## Output Structure + +An array of `Services` entries + +```typescript +export interface Services { + /**Current State of the Service */ + state: string; + /**Name of Service */ + name: string; + /**Display name of Service */ + display_name: string; + /**Service description */ + description: string; + /**Start mode of Service */ + start_mode: string; + /**Path to executable for Service */ + path: string; + /**Service types. Ex: KernelDriver */ + service_type: string[]; + /**Account associated with Service */ + account: string; + /**Registry modified timestamp. May be used to determine when the Service was created */ + modified: string; + /**DLL associated with Service */ + service_dll: string; + /**Service command upon failure */ + failure_command: string; + /**Reset period associated with Service */ + reset_period: number; + /**Service actions upon failure */ + failure_actions: FailureActions[]; + /**Privileges associated with Service */ + required_privileges: string[]; + /**Error associated with Service */ + error_control: string; + /**Registry path associated with Service */ + reg_path: string; + /**Path to the Registry file */ + evidence: string; +} + +/** + * Failure actions executed when Service fails + */ +interface FailureActions { + /**Action executed upon failure */ + action: string; + /**Delay in seconds on failure */ + delay: number; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/shellbags.md b/artemis-docs/docs/Artifacts/Windows Artfacts/shellbags.md index 2575c754..c26417b9 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/shellbags.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/shellbags.md @@ -1,99 +1,99 @@ ---- -description: List of directories accessed by Windows Explorer -keywords: - - windows - - registry ---- - -# Shellbags - -Windows Shellbags are Registry entries that track what directories a user -has browsed via Explorer GUI. These entries are stored in the undocumented -**ShellItem** binary format. - -Artemis supports parsing the most common types of shellitems, but if you -encounter a shellitem entry that is not supported please open an issue! - -Other parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.shellbags/) - -References: - -- [Libyal](https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc) - -## TOML Collection - -```toml -[output] -name = "shellbags_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "shellbags" -[artifacts.shellbags] -resolve_guids = true -# Optional -# alt_file = "C:\\Artifacts\\UsrClass.dat" -``` - -## Collection Options - -- `alt_file` Full path to alternative UsrClass.dat or NTUSER.DAT Registry file. - This configuration is **optional**. By default artemis will parse Shellbags - for all users. -- `resolve_guids` Boolean value whether to try to resolve GUIDs found when - parsing Shellbags. - - If **false**: - `"resolve_path": "20d04fe0-3aea-1069-a2d8-08002b30309d\C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current",` - - If **true**: - `"resolve_path": "This PC\C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current",` - -## Output Structure - -An array of `Shellbag` entries - -```typescript -export interface Shellbags { - /**Reconstructed directory path */ - path: string; - /**FAT created timestamp. Only applicable for Directory `shell_type` */ - created: number; - /**FAT modified timestamp. Only applicable for Directory `shell_type` */ - modified: number; - /**FAT modified timestamp. Only applicable for Directory `shell_type` */ - accessed: number; - /**Entry number in MFT. Only applicable for Directory `shell_type` */ - mft_entry: number; - /**Sequence number in MFT. Only applicable for Directory `shell_type` */ - mft_sequence: number; - /** - * Type of shellitem - * - * Can be: - * `Directory, URI, RootFolder, Network, Volume, ControlPanel, UserPropertyView, Delegate, Variable, MTP, Unknown, History` - * - * Most common is typically `Directory` - */ - shell_type: string; - /** - * Reconstructed directory with any GUIDs resolved - * Ex: `20d04fe0-3aea-1069-a2d8-08002b30309d` to `This PC` - */ - resolve_path: string; - /**Registry key last modified */ - reg_modified: string; - /**User Registry file associated with `Shellbags` */ - reg_file: string; - /**Registry key path to `Shellbags` data */ - reg_path: string; - /**Full file path to the User Registry file */ - reg_file_path: string; -} -``` +--- +description: List of directories accessed by Windows Explorer +keywords: + - windows + - registry +--- + +# Shellbags + +Windows Shellbags are Registry entries that track what directories a user +has browsed via Explorer GUI. These entries are stored in the undocumented +**ShellItem** binary format. + +Artemis supports parsing the most common types of shellitems, but if you +encounter a shellitem entry that is not supported please open an issue! + +Other parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.shellbags/) + +References: + +- [Libyal](https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc) + +## TOML Collection + +```toml +[output] +name = "shellbags_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "shellbags" +[artifacts.shellbags] +resolve_guids = true +# Optional +# alt_file = "C:\\Artifacts\\UsrClass.dat" +``` + +## Collection Options + +- `alt_file` Full path to alternative UsrClass.dat or NTUSER.DAT Registry file. + This configuration is **optional**. By default artemis will parse Shellbags + for all users. +- `resolve_guids` Boolean value whether to try to resolve GUIDs found when + parsing Shellbags. + - If **false**: + `"resolve_path": "20d04fe0-3aea-1069-a2d8-08002b30309d\C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current",` + - If **true**: + `"resolve_path": "This PC\C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current",` + +## Output Structure + +An array of `Shellbag` entries + +```typescript +export interface Shellbags { + /**Reconstructed directory path */ + path: string; + /**FAT created timestamp. Only applicable for Directory `shell_type` */ + created: number; + /**FAT modified timestamp. Only applicable for Directory `shell_type` */ + modified: number; + /**FAT modified timestamp. Only applicable for Directory `shell_type` */ + accessed: number; + /**Entry number in MFT. Only applicable for Directory `shell_type` */ + mft_entry: number; + /**Sequence number in MFT. Only applicable for Directory `shell_type` */ + mft_sequence: number; + /** + * Type of shellitem + * + * Can be: + * `Directory, URI, RootFolder, Network, Volume, ControlPanel, UserPropertyView, Delegate, Variable, MTP, Unknown, History` + * + * Most common is typically `Directory` + */ + shell_type: string; + /** + * Reconstructed directory with any GUIDs resolved + * Ex: `20d04fe0-3aea-1069-a2d8-08002b30309d` to `This PC` + */ + resolve_path: string; + /**Registry key last modified */ + reg_modified: string; + /**User Registry file associated with `Shellbags` */ + reg_file: string; + /**Registry key path to `Shellbags` data */ + reg_path: string; + /**Full file path to the User Registry file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/shimcache.md b/artemis-docs/docs/Artifacts/Windows Artfacts/shimcache.md index 9835f450..6a1f7e59 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/shimcache.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/shimcache.md @@ -1,71 +1,71 @@ ---- -description: Tracks execution* of applications -keywords: - - windows - - registry ---- - -# Shimcache - -Windows Shimcache (also called: AppCompatCache, Application Compatability -Cache, or AppCompat) are Registry entries that may* indicate -application execution. These entries are only written when the system is -shutdown or restarted. - -* While an entry in Shimcache often implies the application was -executed, Windows may pre-populate Shimcache with entries based on a user -browsing to a directory that contains an application. - -Other parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.registry.appcompatcache/) - -References: - -- [Shimcache](https://www.mandiant.com/resources/blog/caching-out-the-val) -- [Libyal](https://github.com/libyal/winreg-kb/blob/main/docs/sources/system-keys/Application-compatibility-cache.md) - -## TOML Collection - -```toml -[output] -name = "shimcache_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "shimcache" -[artifacts.shimcache] -# Optional -# alt_file = "C:\\Artifacts\\SYSTEM" -``` - -## Collection Options - -- `alt_file` Full path to alternative SYSTEM Registry file. This configuration - is **optional**. By default artemis will parse the SYSTEM Registry file at the - default location. - -## Output Structure - -An array of `Shimcache` entries - -```typescript -export interface Shimcache { - /**Entry number for shimcache. Entry zero (0) is most recent execution */ - entry: number; - /**Full path to application file */ - path: string; - /**Standard Information Modified timestamp */ - last_modified: string; - /**Full path to the Registry key */ - key_path: string; - /**Path to the Registry file */ - source_path: string; -} -``` +--- +description: Tracks execution* of applications +keywords: + - windows + - registry +--- + +# Shimcache + +Windows Shimcache (also called: AppCompatCache, Application Compatability +Cache, or AppCompat) are Registry entries that may* indicate +application execution. These entries are only written when the system is +shutdown or restarted. + +* While an entry in Shimcache often implies the application was +executed, Windows may pre-populate Shimcache with entries based on a user +browsing to a directory that contains an application. + +Other parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.registry.appcompatcache/) + +References: + +- [Shimcache](https://www.mandiant.com/resources/blog/caching-out-the-val) +- [Libyal](https://github.com/libyal/winreg-kb/blob/main/docs/sources/system-keys/Application-compatibility-cache.md) + +## TOML Collection + +```toml +[output] +name = "shimcache_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "shimcache" +[artifacts.shimcache] +# Optional +# alt_file = "C:\\Artifacts\\SYSTEM" +``` + +## Collection Options + +- `alt_file` Full path to alternative SYSTEM Registry file. This configuration + is **optional**. By default artemis will parse the SYSTEM Registry file at the + default location. + +## Output Structure + +An array of `Shimcache` entries + +```typescript +export interface Shimcache { + /**Entry number for shimcache. Entry zero (0) is most recent execution */ + entry: number; + /**Full path to application file */ + path: string; + /**Standard Information Modified timestamp */ + last_modified: string; + /**Full path to the Registry key */ + key_path: string; + /**Path to the Registry file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/shimdb.md b/artemis-docs/docs/Artifacts/Windows Artfacts/shimdb.md index 1c4da609..86daa235 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/shimdb.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/shimdb.md @@ -1,122 +1,122 @@ ---- -description: Contains shims used by applications -keywords: - - windows - - binary - - persistence ---- - -# ShimDB - -Windows Shimdatabase (ShimDB) can be used by Windows applications to provided -compatibility between Windows versions. - -It does this via shims that are inserted into the application that modifies -function calls. Malicious custom shims can be created as a form of persistence. - -OtherParsers: - -- N/A - -References: - -- [ShimDB](https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm) -- [ShimDB Persistence](https://www.mandiant.com/resources/blog/fin7-shim-databases-persistence) - -## TOML Collection - -```toml -[output] -name = "sdb_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "shimdb" -[artifacts.shimdb] -# Optional -# alt_file = "C:\\Artifacts\\bad.sdb" -``` - -## Collection Options - -- `alt_file` Full path to alternative ShimDB file. This configuration is - **optional**. By default artemis will ShimDB files at their default locations. - -## Output Structure - -An array of `ShimDB` entries - -````typescript -export interface Shimdb { - /**Array of `TAGS` associated with the index tag*/ - indexes: TagData[]; - /**Data associated with the Shimdb */ - db_data: DatabaseData; - /**Path to parsed sdb file */ - sdb_path: string; -} - -/** - * SDB files are composed of `TAGS`. There are multiple types of `TAGS` - * `data` have `TAGS` that can be represented via a JSON object - * `list_data` have `TAGS` that can be represented as an array of JSON objects - * - * Example: - * ``` - * "data": { - * "TAG_FIX_ID": "4aeea7ee-44f1-4085-abc2-6070eb2b6618", - * "TAG_RUNTIME_PLATFORM": "37", - * "TAG_NAME": "256Color" - * }, - * "list_data": [ - * { - * "TAG_NAME": "Force8BitColor", - * "TAG_SHIM_TAGID": "169608" - * }, - * { - * "TAG_SHIM_TAGID": "163700", - * "TAG_NAME": "DisableThemes" - * } - * ] - * ``` - * - * See https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm for complete list of `TAGS` - */ -export interface TagData { - /**TAGs represented as a JSON object */ - data: Record; - /**Array of TAGS represented as a JSON objects */ - list_data: Record[]; -} - -/** - * Metadata related to the SDB file - */ -export interface DatabaseData { - /**SDB version info */ - sdb_version: string; - /**Compile timestamp of the SDB file */ - compile_time: string; - /**Compiler version info */ - compiler_version: string; - /**Name of SDB */ - name: string; - /**Platform ID */ - platform: number; - /**ID associated with SDB */ - database_id: string; - /** - * The SDB file may contain additional metadata information - * May include additional `TAGS` - */ - additional_metadata: Record; - /**Array of `TAGS` associated with the SDB file */ - list_data: TagData[]; -} -```` +--- +description: Contains shims used by applications +keywords: + - windows + - binary + - persistence +--- + +# ShimDB + +Windows Shimdatabase (ShimDB) can be used by Windows applications to provided +compatibility between Windows versions. + +It does this via shims that are inserted into the application that modifies +function calls. Malicious custom shims can be created as a form of persistence. + +OtherParsers: + +- N/A + +References: + +- [ShimDB](https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm) +- [ShimDB Persistence](https://www.mandiant.com/resources/blog/fin7-shim-databases-persistence) + +## TOML Collection + +```toml +[output] +name = "sdb_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "shimdb" +[artifacts.shimdb] +# Optional +# alt_file = "C:\\Artifacts\\bad.sdb" +``` + +## Collection Options + +- `alt_file` Full path to alternative ShimDB file. This configuration is + **optional**. By default artemis will ShimDB files at their default locations. + +## Output Structure + +An array of `ShimDB` entries + +````typescript +export interface Shimdb { + /**Array of `TAGS` associated with the index tag*/ + indexes: TagData[]; + /**Data associated with the Shimdb */ + db_data: DatabaseData; + /**Path to parsed sdb file */ + evidence: string; +} + +/** + * SDB files are composed of `TAGS`. There are multiple types of `TAGS` + * `data` have `TAGS` that can be represented via a JSON object + * `list_data` have `TAGS` that can be represented as an array of JSON objects + * + * Example: + * ``` + * "data": { + * "TAG_FIX_ID": "4aeea7ee-44f1-4085-abc2-6070eb2b6618", + * "TAG_RUNTIME_PLATFORM": "37", + * "TAG_NAME": "256Color" + * }, + * "list_data": [ + * { + * "TAG_NAME": "Force8BitColor", + * "TAG_SHIM_TAGID": "169608" + * }, + * { + * "TAG_SHIM_TAGID": "163700", + * "TAG_NAME": "DisableThemes" + * } + * ] + * ``` + * + * See https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm for complete list of `TAGS` + */ +export interface TagData { + /**TAGs represented as a JSON object */ + data: Record; + /**Array of TAGS represented as a JSON objects */ + list_data: Record[]; +} + +/** + * Metadata related to the SDB file + */ +export interface DatabaseData { + /**SDB version info */ + sdb_version: string; + /**Compile timestamp of the SDB file */ + compile_time: string; + /**Compiler version info */ + compiler_version: string; + /**Name of SDB */ + name: string; + /**Platform ID */ + platform: number; + /**ID associated with SDB */ + database_id: string; + /** + * The SDB file may contain additional metadata information + * May include additional `TAGS` + */ + additional_metadata: Record; + /**Array of `TAGS` associated with the SDB file */ + list_data: TagData[]; +} +```` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/shortcuts.md b/artemis-docs/docs/Artifacts/Windows Artfacts/shortcuts.md index 1f318f2f..8b281d00 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/shortcuts.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/shortcuts.md @@ -1,186 +1,186 @@ ---- -description: Metadata about recently opened files -keywords: - - windows - - binary ---- - -# Shortcuts - -Windows Shortcut files (.lnk files) are files that point to another file. They -often contain a large amount of metadata related to the target file. Shortcut -files can be used to distribute malware and can also provide evidence of file -interaction. The directory at -**C:\Users\\*\AppData\Roaming\Microsoft\Windows\Recent** contains multiple -Shortcuts that point to files recently opened by the user. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.lnk/) - -References: - -- [Libyal](https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc) - -## TOML Collection - -```toml -[output] -name = "shortcuts_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "shortcuts" -[artifacts.shortcuts] -path = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup" -``` - -## Collection Options - -- `path` Target path where artemis should parse Shortcut files. This - configuration is **required** - -## Output Structure - -A `Shortcut` object structure - -```typescript -export interface Shortcut { - /**Path to `shortcut (lnk)` file */ - source_path: string; - /**Flags that specify what data structures are in the `lnk` file */ - data_flags: string[]; - /**File attributes of target file */ - attribute_flags: string[]; - /**Standard Information created timestamp of target file */ - created: string; - /**Standard Information accessed timestamp of target file */ - accessed: string; - /**Standard Information modified timestamp of target file */ - modified: string; - /**Size in bytes of target file */ - file_size: number; - /**Flag associated where target file is located. On volume or network share */ - location_flags: string; - /**Path to target file */ - path: string; - /**Serial associated with volume if target file is on drive */ - drive_serial: string; - /**Drive type associated with volume if target file is on drive */ - drive_type: string; - /**Name of volume if target file is on drive */ - volume_label: string; - /**Network type if target file is on network share */ - network_provider: string; - /**Network share name if target file is on network share */ - network_share_name: string; - /**Network share device name if target file is on network share */ - network_device_name: string; - /**Description of shortcut (lnk) file */ - description: string; - /**Relative path to target file */ - relative_path: string; - /**Directory of target file */ - working_directory: string; - /**Command args associated with target file */ - command_line_args: string; - /**Icon path associated with shortcut (lnk) file */ - icon_location: string; - /**Hostname of target file */ - hostname: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_file_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_file_id: string; - /**Shellitems associated with shortcut (lnk) file */ - shellitems: ShellItems[]; - /**Array of property stores */ - properties: Record[]; - /**Environmental variable data in shortcut */ - environment_variable: string; - /**Console metadata in shortcut */ - console: Console[]; - /**Windows Codepage in shortcut */ - codepage: number; - /**Special folder ID in shortcut */ - special_folder_id: number; - /**macOS Darwin ID in shortcut */ - darwin_id: string; - /**Shim layer entry in shortcut */ - shim_layer: string; - /**Known folder GUID in shortcut */ - known_folder: string; - /** - * If the Shortcut file strings exceed 260 bytes (520 if Unicode). Then the Shortcut is marked as "abnormal". - * Normally Shortcut strings can be 64KB in size. **However**, the Windows implementation of the Shortcut file limits string sizes to 260 bytes. - * If a string is larger than 260 bytes then it may be an indicator that it is malformed or was crafted malicious to try to avoid forensic tools. - * - * Reference: https://harfanglab.io/insidethelab/sadfuture-xdspy-latest-evolution/#tid_specifications_ignored - */ - is_abnormal: boolean; -} - -/** - * Console metadata embedded in Shortcut file - */ -interface Console { - /**Colors for Console */ - color_flags: string[]; - /**Additional colors for Console */ - pop_fill_attributes: string[]; - /**Console width buffer size */ - screen_width_buffer_size: number; - /**Console height buffer size */ - screen_height_buffer_size: number; - /**Console window width */ - window_width: number; - /**Console window height */ - window_height: number; - /**Console X coordinate */ - window_x_coordinate: number; - /**Console Y coordinate */ - window_y_coordinate: number; - /**Console font size */ - font_size: number; - /**Console font family */ - font_family: string; - /**Console font weight */ - font_weight: string; - /**Console font name */ - face_name: string; - /**Console cursor size */ - cursor_size: string; - /**Is full screen set (boolean) */ - full_screen: number; - /**Insert mode */ - insert_mode: number; - /**Automatic position set (boolean) */ - automatic_position: number; - /**Console history buffer size */ - history_buffer_size: number; - /**Console number of buffers */ - number_history_buffers: number; - /**Duplicates allowed in history */ - duplicates_allowed_history: number; - /**Base64 encoded color table. */ - color_table: string; -} -``` +--- +description: Metadata about recently opened files +keywords: + - windows + - binary +--- + +# Shortcuts + +Windows Shortcut files (.lnk files) are files that point to another file. They +often contain a large amount of metadata related to the target file. Shortcut +files can be used to distribute malware and can also provide evidence of file +interaction. The directory at +**C:\Users\\*\AppData\Roaming\Microsoft\Windows\Recent** contains multiple +Shortcuts that point to files recently opened by the user. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.lnk/) + +References: + +- [Libyal](https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc) + +## TOML Collection + +```toml +[output] +name = "shortcuts_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "shortcuts" +[artifacts.shortcuts] +dir = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\*" +``` + +## Collection Options + +- `dir` Glob to Shortcut files. This + configuration is **required** + +## Output Structure + +A `Shortcut` object structure + +```typescript +export interface Shortcut { + /**Path to `shortcut (lnk)` file */ + evidence: string; + /**Flags that specify what data structures are in the `lnk` file */ + data_flags: string[]; + /**File attributes of target file */ + attribute_flags: string[]; + /**Standard Information created timestamp of target file */ + created: string; + /**Standard Information accessed timestamp of target file */ + accessed: string; + /**Standard Information modified timestamp of target file */ + modified: string; + /**Size in bytes of target file */ + file_size: number; + /**Flag associated where target file is located. On volume or network share */ + location_flags: string; + /**Path to target file */ + path: string; + /**Serial associated with volume if target file is on drive */ + drive_serial: string; + /**Drive type associated with volume if target file is on drive */ + drive_type: string; + /**Name of volume if target file is on drive */ + volume_label: string; + /**Network type if target file is on network share */ + network_provider: string; + /**Network share name if target file is on network share */ + network_share_name: string; + /**Network share device name if target file is on network share */ + network_device_name: string; + /**Description of shortcut (lnk) file */ + description: string; + /**Relative path to target file */ + relative_path: string; + /**Directory of target file */ + working_directory: string; + /**Command args associated with target file */ + command_line_args: string; + /**Icon path associated with shortcut (lnk) file */ + icon_location: string; + /**Hostname of target file */ + hostname: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_file_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_file_id: string; + /**Shellitems associated with shortcut (lnk) file */ + shellitems: ShellItems[]; + /**Array of property stores */ + properties: Record[]; + /**Environmental variable data in shortcut */ + environment_variable: string; + /**Console metadata in shortcut */ + console: Console[]; + /**Windows Codepage in shortcut */ + codepage: number; + /**Special folder ID in shortcut */ + special_folder_id: number; + /**macOS Darwin ID in shortcut */ + darwin_id: string; + /**Shim layer entry in shortcut */ + shim_layer: string; + /**Known folder GUID in shortcut */ + known_folder: string; + /** + * If the Shortcut file strings exceed 260 bytes (520 if Unicode). Then the Shortcut is marked as "abnormal". + * Normally Shortcut strings can be 64KB in size. **However**, the Windows implementation of the Shortcut file limits string sizes to 260 bytes. + * If a string is larger than 260 bytes then it may be an indicator that it is malformed or was crafted malicious to try to avoid forensic tools. + * + * Reference: https://harfanglab.io/insidethelab/sadfuture-xdspy-latest-evolution/#tid_specifications_ignored + */ + is_abnormal: boolean; +} + +/** + * Console metadata embedded in Shortcut file + */ +interface Console { + /**Colors for Console */ + color_flags: string[]; + /**Additional colors for Console */ + pop_fill_attributes: string[]; + /**Console width buffer size */ + screen_width_buffer_size: number; + /**Console height buffer size */ + screen_height_buffer_size: number; + /**Console window width */ + window_width: number; + /**Console window height */ + window_height: number; + /**Console X coordinate */ + window_x_coordinate: number; + /**Console Y coordinate */ + window_y_coordinate: number; + /**Console font size */ + font_size: number; + /**Console font family */ + font_family: string; + /**Console font weight */ + font_weight: string; + /**Console font name */ + face_name: string; + /**Console cursor size */ + cursor_size: string; + /**Is full screen set (boolean) */ + full_screen: number; + /**Insert mode */ + insert_mode: number; + /**Automatic position set (boolean) */ + automatic_position: number; + /**Console history buffer size */ + history_buffer_size: number; + /**Console number of buffers */ + number_history_buffers: number; + /**Duplicates allowed in history */ + duplicates_allowed_history: number; + /**Base64 encoded color table. */ + color_table: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/srum.md b/artemis-docs/docs/Artifacts/Windows Artfacts/srum.md index f2f9ea5a..1bce04ef 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/srum.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/srum.md @@ -1,344 +1,360 @@ ---- -description: System Resource Utilization Monitor (SRUM) tracks application usage -keywords: - - windows - - ese ---- - -# SRUM - -Windows System Resource Utilization Monitor (SRUM) is a service that tracks -application resource usage. The service tracks application data such as time -running, bytes sent, bytes received, energy usage, and lots more. -This -service was introduced in Windows 8 and is stored in an ESE database at -**C:\Windows\System32\sru\SRUDB.dat**. On Windows 8 some of the data can be found -in the Registry too (temporary storage before writing to SRUDB.dat), but in -later versions of Windows the data is no longer in the Registry. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.srum/) - -References: - -- [Libyal](https://github.com/libyal/esedb-kb/blob/main/documentation/System%20Resource%20Usage%20Monitor%20(SRUM).asciidoc) -- [Velociraptor](https://velociraptor.velocidex.com/digging-into-the-system-resource-usage-monitor-srum-afbadb1a375) - -## TOML Collection - -```toml -[output] -name = "srum_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "srum" -[artifacts.srum] -# Optional -# alt_path = "C:\Windows\System32\srum\SRUDB.dat" -``` - -## Collection Options - -- `alt_path` An alternative path to the SRUM ESE database. This configuration - is **optional**. By default artemis will use - **%systemdrive%\Windows\System32\srum\SRUDB.dat** - -## Output Structure - -An array of entries based on each SRUM table - -```typescript -/** - * SRUM table associated with application executions `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}` - */ -export interface ApplicationInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Foreground Cycle time for application */ - foreground_cycle_time: number; - /**Background Cycle time for application */ - background_cycle_time: number; - /**Facetime for application */ - facetime: number; - /**Count of foreground context switches */ - foreground_context_switches: number; - /**Count of background context switches */ - background_context_switches: number; - /**Count of foreground bytes read */ - foreground_bytes_read: number; - /**Count of background bytes read */ - foreground_bytes_written: number; - /**Count of foreground read operations */ - foreground_num_read_operations: number; - /**Count of foreground write operations */ - foreground_num_write_options: number; - /**Count of foreground flushes */ - foreground_number_of_flushes: number; - /**Count of background bytes read */ - background_bytes_read: number; - /**Count of background write operations */ - background_bytes_written: number; - /**Count of background read operations */ - background_num_read_operations: number; - /**Count of background write operations */ - background_num_write_operations: number; - /**Count of background flushes */ - background_number_of_flushes: number; -} -``` - -```typescript -/** - * SRUM table associated with the timeline of an application's execution `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}` - */ -export interface ApplicationTimeline { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Flags associated with entry */ - flags: number; - /**End time of entry */ - end_time: string; - /**Duration of timeline in microseconds */ - duration_ms: number; - /**Span of timeline in microseconds */ - span_ms: number; - /**Timeline end for entry */ - timeline_end: number; - /**In focus value for entry */ - in_focus_timeline: number; - /**User input value for entry */ - user_input_timeline: number; - /**Comp rendered value for entry */ - comp_rendered_timeline: number; - /**Comp dirtied value for entry */ - comp_dirtied_timeline: number; - /**Comp propagated value for entry */ - comp_propagated_timeline: number; - /**Audio input value for entry */ - audio_in_timeline: number; - /**Audio output value for entry */ - audio_out_timeline: number; - /**CPU value for entry */ - cpu_timeline: number; - /**Disk value for entry */ - disk_timeline: number; - /**Network value for entry */ - network_timeline: number; - /**MBB value for entry */ - mbb_timeline: number; - /**In focus seconds count */ - in_focus_s: number; - /**PSM foreground seconds count */ - psm_foreground_s: number; - /**User input seconds count */ - user_input_s: number; - /**Comp rendered seconds count */ - comp_rendered_s: number; - /**Comp dirtied seconds count */ - comp_dirtied_s: number; - /**Comp propagated seconds count */ - comp_propagated_s: number; - /**Audio input seconds count */ - audio_in_s: number; - /**Audio output seconds count */ - audio_out_s: number; - /**Cycles value for entry */ - cycles: number; - /**Cycles breakdown value for entry */ - cycles_breakdown: number; - /**Cycles attribute value for entry */ - cycles_attr: number; - /**Cycles attribute breakdown for entry */ - cycles_attr_breakdown: number; - /**Cycles WOB value for entry */ - cycles_wob: number; - /**Cycles WOB breakdown value for entry */ - cycles_wob_breakdown: number; - /**Disk raw value for entry */ - disk_raw: number; - /**Network tail raw value for entry */ - network_tail_raw: number; - /**Network bytes associated with entry*/ - network_bytes_raw: number; - /**MBB tail raw value for entry */ - mbb_tail_raw: number; - /**MBB bytes associated with entry */ - mbb_bytes_raw: number; - /**Display required seconds count */ - display_required_s: number; - /**Display required timeline value for entry */ - display_required_timeline: number; - /**Keyboard input timeline value for entry */ - keyboard_input_timeline: number; - /**Keyboard input seconds count */ - keyboard_input_s: number; - /**Mouse input seconds count */ - mouse_input_s: number; -} -``` - -```typescript -/** - * SRUM table associated with VFU `{7ACBBAA3-D029-4BE4-9A7A-0885927F1D8F}`. Unsure what this tracks. - */ -export interface AppVfu { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Flags associated with VFU entry */ - flags: number; - /**Start time associated with VFU entry */ - start_time: string; - /**End time associated with VFU entry */ - end_time: string; - /**Base64 encoded usage data associated with VFU entry */ - usage: string; -} -``` - -```typescript -/** - * SRUM table associated with EnergyInfo `{DA73FB89-2BEA-4DDC-86B8-6E048C6DA477}` - */ -export interface EnergyInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Base64 encoded binary data associated with EnergyInfo entry */ - binary_data: string; -} -``` - -```typescript -/** - * SRUM table associated with EnergyUsage `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}` and `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}LT` - */ -export interface EnergyUsage { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Event Timestamp */ - event_timestamp: string; - /**State transition associated with entry */ - state_transition: number; - /**Full charged capacity associated with entry */ - full_charged_capacity: number; - /**Designed capacity associated with entry */ - designed_capacity: number; - /** Charge level associated with entry */ - charge_level: number; - /**Cycle count associated with entry */ - cycle_count: number; - /**Configuration hash associated with entry */ - configuration_hash: number; -} -``` - -```typescript -/** - * SRUM table associated with NetworkInfo `{973F5D5C-1D90-4944-BE8E-24B94231A174}` - */ -export interface NetworkInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Interface luid associated with entry */ - interface_luid: number; - /**L2 profile ID associated with entry */ - l2_profile_id: number; - /**L2 profile flags associated with entry */ - l2_profile_flags: number; - /**Bytes sent associated with entry */ - bytes_sent: number; - /**Bytes received associated with entry */ - bytes_recvd: number; -} -``` - -```typescript -/** - * SRUM table associated with NetworkConnectivityInfo `{DD6636C4-8929-4683-974E-22C046A43763}` - */ -export interface NetworkConnectivityInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Interface luid associated with entry */ - interface_luid: number; - /**L2 profile ID associated with entry */ - l2_profile_id: number; - /**Connected time associated with entry */ - connected_time: number; - /*Connect start time associated with entry*/ - connect_start_time: string; - /**L2 profile flags associated with entry */ - l2_profile_flags: number; -} -``` - -```typescript -/** - * SRUM table associated with NotificationInfo `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}` - */ -export interface NotificationInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Notification type associated with entry */ - notification_type: number; - /**Size of payload associated with entry */ - payload_size: number; - /**Network type associated with entry */ - network_type: number; -} -``` +--- +description: System Resource Utilization Monitor (SRUM) tracks application usage +keywords: + - windows + - ese +--- + +# SRUM + +Windows System Resource Utilization Monitor (SRUM) is a service that tracks +application resource usage. The service tracks application data such as time +running, bytes sent, bytes received, energy usage, and lots more. +This +service was introduced in Windows 8 and is stored in an ESE database at +**C:\Windows\System32\sru\SRUDB.dat**. On Windows 8 some of the data can be found +in the Registry too (temporary storage before writing to SRUDB.dat), but in +later versions of Windows the data is no longer in the Registry. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.srum/) + +References: + +- [Libyal](https://github.com/libyal/esedb-kb/blob/main/documentation/System%20Resource%20Usage%20Monitor%20(SRUM).asciidoc) +- [Velociraptor](https://velociraptor.velocidex.com/digging-into-the-system-resource-usage-monitor-srum-afbadb1a375) + +## TOML Collection + +```toml +[output] +name = "srum_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "srum" +[artifacts.srum] +# Optional +# alt_file = "C:\Windows\System32\srum\SRUDB.dat" +``` + +## Collection Options + +- `alt_file` An alternative path to the SRUM ESE database. This configuration + is **optional**. By default artemis will use + **%systemdrive%\Windows\System32\srum\SRUDB.dat** + +## Output Structure + +An array of entries based on each SRUM table + +```typescript +/** + * SRUM table associated with application executions `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}` + */ +export interface ApplicationInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Foreground Cycle time for application */ + foreground_cycle_time: number; + /**Background Cycle time for application */ + background_cycle_time: number; + /**Facetime for application */ + facetime: number; + /**Count of foreground context switches */ + foreground_context_switches: number; + /**Count of background context switches */ + background_context_switches: number; + /**Count of foreground bytes read */ + foreground_bytes_read: number; + /**Count of background bytes read */ + foreground_bytes_written: number; + /**Count of foreground read operations */ + foreground_num_read_operations: number; + /**Count of foreground write operations */ + foreground_num_write_options: number; + /**Count of foreground flushes */ + foreground_number_of_flushes: number; + /**Count of background bytes read */ + background_bytes_read: number; + /**Count of background write operations */ + background_bytes_written: number; + /**Count of background read operations */ + background_num_read_operations: number; + /**Count of background write operations */ + background_num_write_operations: number; + /**Count of background flushes */ + background_number_of_flushes: number; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with the timeline of an application's execution `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}` + */ +export interface ApplicationTimeline { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Flags associated with entry */ + flags: number; + /**End time of entry */ + end_time: string; + /**Duration of timeline in microseconds */ + duration_ms: number; + /**Span of timeline in microseconds */ + span_ms: number; + /**Timeline end for entry */ + timeline_end: number; + /**In focus value for entry */ + in_focus_timeline: number; + /**User input value for entry */ + user_input_timeline: number; + /**Comp rendered value for entry */ + comp_rendered_timeline: number; + /**Comp dirtied value for entry */ + comp_dirtied_timeline: number; + /**Comp propagated value for entry */ + comp_propagated_timeline: number; + /**Audio input value for entry */ + audio_in_timeline: number; + /**Audio output value for entry */ + audio_out_timeline: number; + /**CPU value for entry */ + cpu_timeline: number; + /**Disk value for entry */ + disk_timeline: number; + /**Network value for entry */ + network_timeline: number; + /**MBB value for entry */ + mbb_timeline: number; + /**In focus seconds count */ + in_focus_s: number; + /**PSM foreground seconds count */ + psm_foreground_s: number; + /**User input seconds count */ + user_input_s: number; + /**Comp rendered seconds count */ + comp_rendered_s: number; + /**Comp dirtied seconds count */ + comp_dirtied_s: number; + /**Comp propagated seconds count */ + comp_propagated_s: number; + /**Audio input seconds count */ + audio_in_s: number; + /**Audio output seconds count */ + audio_out_s: number; + /**Cycles value for entry */ + cycles: number; + /**Cycles breakdown value for entry */ + cycles_breakdown: number; + /**Cycles attribute value for entry */ + cycles_attr: number; + /**Cycles attribute breakdown for entry */ + cycles_attr_breakdown: number; + /**Cycles WOB value for entry */ + cycles_wob: number; + /**Cycles WOB breakdown value for entry */ + cycles_wob_breakdown: number; + /**Disk raw value for entry */ + disk_raw: number; + /**Network tail raw value for entry */ + network_tail_raw: number; + /**Network bytes associated with entry*/ + network_bytes_raw: number; + /**MBB tail raw value for entry */ + mbb_tail_raw: number; + /**MBB bytes associated with entry */ + mbb_bytes_raw: number; + /**Display required seconds count */ + display_required_s: number; + /**Display required timeline value for entry */ + display_required_timeline: number; + /**Keyboard input timeline value for entry */ + keyboard_input_timeline: number; + /**Keyboard input seconds count */ + keyboard_input_s: number; + /**Mouse input seconds count */ + mouse_input_s: number; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with VFU `{7ACBBAA3-D029-4BE4-9A7A-0885927F1D8F}`. Unsure what this tracks. + */ +export interface AppVfu { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Flags associated with VFU entry */ + flags: number; + /**Start time associated with VFU entry */ + start_time: string; + /**End time associated with VFU entry */ + end_time: string; + /**Base64 encoded usage data associated with VFU entry */ + usage: string; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with EnergyInfo `{DA73FB89-2BEA-4DDC-86B8-6E048C6DA477}` + */ +export interface EnergyInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Base64 encoded binary data associated with EnergyInfo entry */ + binary_data: string; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with EnergyUsage `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}` and `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}LT` + */ +export interface EnergyUsage { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Event Timestamp */ + event_timestamp: string; + /**State transition associated with entry */ + state_transition: number; + /**Full charged capacity associated with entry */ + full_charged_capacity: number; + /**Designed capacity associated with entry */ + designed_capacity: number; + /** Charge level associated with entry */ + charge_level: number; + /**Cycle count associated with entry */ + cycle_count: number; + /**Configuration hash associated with entry */ + configuration_hash: number; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with NetworkInfo `{973F5D5C-1D90-4944-BE8E-24B94231A174}` + */ +export interface NetworkInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Interface luid associated with entry */ + interface_luid: number; + /**L2 profile ID associated with entry */ + l2_profile_id: number; + /**L2 profile flags associated with entry */ + l2_profile_flags: number; + /**Bytes sent associated with entry */ + bytes_sent: number; + /**Bytes received associated with entry */ + bytes_recvd: number; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with NetworkConnectivityInfo `{DD6636C4-8929-4683-974E-22C046A43763}` + */ +export interface NetworkConnectivityInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Interface luid associated with entry */ + interface_luid: number; + /**L2 profile ID associated with entry */ + l2_profile_id: number; + /**Connected time associated with entry */ + connected_time: number; + /*Connect start time associated with entry*/ + connect_start_time: string; + /**L2 profile flags associated with entry */ + l2_profile_flags: number; + /**Path to the SRUM file */ + evidence: string; +} +``` + +```typescript +/** + * SRUM table associated with NotificationInfo `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}` + */ +export interface NotificationInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Notification type associated with entry */ + notification_type: number; + /**Size of payload associated with entry */ + payload_size: number; + /**Network type associated with entry */ + network_type: number; + /**Path to the SRUM file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/tasks.md b/artemis-docs/docs/Artifacts/Windows Artfacts/tasks.md index d96b23d3..0bcf4fd4 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/tasks.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/tasks.md @@ -1,562 +1,562 @@ ---- -description: Scheduled Tasks setup on Windows -keywords: - - windows - - binary - - plaintext - - persistence ---- - -# Scheduled Tasks - -Windows Scheduled Tasks are a common form of persistence on Windows systems. -There are two (2) types of Scheduled Task files: - -- XML based files -- Job based files - -artemis supports both formats. Starting on Windows Vista and higher XML files -are used for Scheduled Tasks. - -Other Parsers: - -- Any XML reader -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.system.taskscheduler/) - -References: - -- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/Job%20file%20format.asciidoc) -- [Microsoft](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f) - -## TOML Collection - -```toml -[output] -name = "tasks_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "tasks" -[artifacts.tasks] -# Optional -# alt_file = "C:\\Artifacts\\At1.job" -``` - -## Collection Options - -- `alt_file` Full path to alternative Schedule Task file. This configuration is - **optional**. By default artemis will parse all Schedule Task files at their - default location. - -## Output Structure - -Array of `TaskXml` and `TaskJob` - -```typescript -/** - * JSON representation of the Task XML schema. - * Most of the schema is Optional. Only `Actions` is required - */ -export interface TaskXml { - /**Registration Info about the Task */ - registrationInfo?: RegistrationInfo; - /**Triggers that start the Task */ - triggers?: Triggers; - /**Settings for the Task */ - settings?: Settings; - /**Base64 encoded raw binary data associated with the Task */ - data?: string; - /**Principal user information related to the Task */ - principals?: Principals; - /**Actions executed by the Task */ - actions: Actions; - /**Path to the XML file */ - path: string; -} - -/** - * Parsed information about the Job file - */ -export interface TaskJob { - /**ID associated with the Task */ - job_id: string; - /**Error retry count for the Task */ - error_retry_count: number; - /**Error retry interval for the Task */ - error_retry_interval: number; - /**Idle deadline for Task */ - idle_deadline: number; - /**Idle wait for Task */ - idle_wait: number; - /**Task Priority */ - priority: string; - /**Max run time for Task */ - max_run_time: number; - /**Task Exit code */ - exit_code: number; - /**Task Status */ - status: string; - /**Flags associated with Task */ - flags: string[]; - /**Last run time for Task in LOCALTIME */ - system_time: string; - /**Running count for Task */ - running_instance_count: number; - /**Application name associated with Task */ - application_name: string; - /**Parameters for application */ - parameters: string; - /**Working directory associated with Task */ - working_directory: string; - /**Creator of Task */ - author: string; - /**Comments associated with Task */ - comments: string; - /**Base64 encoded User data associated with Task */ - user_data: string; - /**Start Error associated with Task */ - start_error: number; - /**Triggers that start the Task */ - triggers: JobTriggers[]; - /**Path to Job file */ - path: string; -} - -/** - * Triggers associated with Job file - */ -interface JobTriggers { - /**Task start date */ - start_date: string; - /**Task end date */ - end_date: string; - /**Task start time */ - start_time: string; - /**Task duration */ - duration: number; - /**Task interval */ - interval_mins: number; - /**Array of trigger flags */ - flags: string[]; - /**Array of trigger types */ - types: string[]; -} - -/** - * Registration Info related to Task XML - */ -interface RegistrationInfo { - /**URI associated with */ - uri?: string; - /**SID associated with Task */ - sid?: string; - /**Source of Task */ - source?: string; - /**Creation OR Modification of Task */ - date?: string; - /**Creator of Task */ - author?: string; - /**Version level of Task */ - version?: string; - /**User-friendly description of Task */ - description?: string; - /**URI of external documentation for Task */ - documentation?: string; -} - -/** - * Triggers that active the Task - */ -interface Triggers { - /**Boot triggers for Task */ - boot: BootTrigger[]; - /**Registration triggers for Task. Format is exactly same as BootTrigger*/ - registration: BootTrigger[]; - /**Idle triggers for Task */ - idle: IdleTrigger[]; - /**Time triggers for Task */ - time: TimeTrigger[]; - /**Event triggers for Task */ - event: EventTrigger[]; - /**Logon triggers for Task */ - logon: LogonTrigger[]; - /**Session triggers for Task */ - session: SessionTrigger[]; - /**Calendar triggers for Task */ - calendar: CalendarTrigger[]; - /**Windows Notifications triggers for Task */ - wnf: WnfTrigger[]; -} - -/** - * Most Triggers have a collection of common options - */ -interface BaseTriggers { - /**ID for trigger */ - id?: string; - /**Start date for Task */ - start_boundary?: string; - /**End date for Task */ - end_boundary?: string; - /**Bool value to activate Trigger */ - enabled?: boolean; - /**Time limit for Task */ - execution_time_limit?: string; - /**Repetition for Task */ - repetition?: Repetition; -} - -/** - * Repetition Options for Triggers - */ -interface Repetition { - /**Trigger restart intervals */ - interval: string; - /**Repetition can stop after duration has elapsed */ - duration?: string; - /**Task can stop at end of duration */ - stop_at_duration_end?: boolean; -} - -/** - * Boot options to Trigger Task - */ -interface BootTrigger { - /**Base Triggers associated with Boot */ - common?: BaseTriggers; - /**Task delayed after boot */ - delay?: string; -} - -/** - * Idle options to Trigger Task - */ -interface IdleTrigger { - /**Base Triggers associated with Idle */ - common?: BaseTriggers; -} - -/** - * Time options to Trigger Task - */ -interface TimeTrigger { - /**Base Triggers associated with Time */ - common?: BaseTriggers; - /**Delay time for `start_boundary` */ - random_delay?: string; -} - -/** - * Event options to Trigger Task - */ -interface EventTrigger { - /**Base Triggers associated with Event */ - common?: BaseTriggers; - /**Array of subscriptions that can Trigger the Task */ - subscription: string[]; - /**Delay to Trigger the Task */ - delay?: string; - /**Trigger can start Task after `number_of_occurrences` */ - number_of_occurrences?: number; - /**Trigger can start Task after `period_of_occurrence` */ - period_of_occurrence?: string; - /**Specifies XML field name */ - matching_element?: string; - /**Specifies set of XML elements */ - value_queries?: string[]; -} - -/** - * Logon options to Trigger Task - */ -interface LogonTrigger { - /**Base Triggers associated with Logon */ - common?: BaseTriggers; - /**Account name associated with Logon Trigger */ - user_id?: string; - /**Delay Logon Task Trigger */ - delay?: string; -} - -/** - * Session options to Trigger Task - */ -interface SessionTrigger { - /**Base Triggers associated with Session */ - common?: BaseTriggers; - /**Account name associated with Session Trigger */ - user_id?: string; - /**Delay Session Task Trigger */ - delay?: string; - /**Session change that Triggers Task */ - state_change?: string; -} - -/** - * Windows Notification options to Trigger Task - */ -interface WnfTrigger { - /**Base Triggers associated with Windows Notification */ - common?: BaseTriggers; - /**Notification State name */ - state_name: string; - /**Delay Notification Trigger Task */ - delay?: string; - /**Data associated with Notification Trigger */ - data?: string; - /**Offset associated with Notification Trigger */ - data_offset?: string; -} - -/** - * Calendar Options to Trigger Task - */ -interface CalendarTrigger { - /**Base Triggers associated with Calendar */ - common?: BaseTriggers; - /**Delay Calendar Trigger Task */ - random_delay?: string; - /**Run Task on every X number of days */ - schedule_by_day?: ByDay; - /**Run Task on every X number of weeks */ - schedule_by_week?: ByWeek; - /**Run Task on specific days of month */ - schedule_by_month?: ByMonth; - /**Run Task on specific weeks on specific days */ - schedule_by_month_day_of_week?: ByMonthDayWeek; -} - -/** - * How often to run Task by days - */ -interface ByDay { - /**Run Task on X number of days. Ex: Two (2) means every other day */ - days_interval?: number; -} - -/** - * How often to run Task by Weeks - */ -interface ByWeek { - /**Run Task on X number of weeks. Ex: Two (2) means every other week */ - weeks_interval?: number; - /**Runs on specified days of the week. Ex: Monday, Tuesday */ - days_of_week?: string[]; -} - -/** - * How often to run Task by Months - */ -interface ByMonth { - /**Days of month to run Task */ - days_of_month?: string[]; - /**Months to run Task. Ex: July, August */ - months?: string[]; -} - -/**How often to run Tasks by Months and Weeks */ -interface ByMonthDayWeek { - /**Weeks of month to run Task */ - weeks?: string[]; - /**Days of month to run Task */ - days_of_week?: string[]; - /**Months to run Task */ - months?: string[]; -} - -/** - * Settings determine how to run Task Actions - */ -interface Settings { - /**Start Task on demand */ - allow_start_on_demand?: boolean; - /**Restart if fails */ - restart_on_failure?: RestartType; - /**Determines how Windows handles multiple Task executions */ - multiple_instances_policy?: string; - /**Disable Task on battery power */ - disallow_start_if_on_batteries?: boolean; - /**Stop Task if going on battery power */ - stop_if_going_on_batteries?: boolean; - /**Task can be terminated if time limits exceeded */ - allow_hard_terminate?: boolean; - /**If scheduled time is missed, Task may be started */ - start_when_available?: boolean; - /**Run based on network profile name */ - network_profile_name?: string; - /**Run only if network connection available */ - run_only_if_network_available?: boolean; - /**Wake system from standby or hibernate to run */ - wake_to_run?: boolean; - /**Task is enabled */ - enabled?: boolean; - /**Task is hidden from console or GUI */ - hidden?: boolean; - /**Delete Task after specified duration and no future run times */ - delete_expired_tasks_after?: string; - /**Options to run when Idle */ - idle_settings?: IdleSettings; - /**Network settings to run */ - network_settings?: NetworkSettings; - /**Task execution time limit */ - execution_time_limit?: string; - /**Task Priority. Lowest is 1. Highest is 10 */ - priority?: number; - /**Only run if system is Idle */ - run_only_if_idle?: boolean; - /**Use unified scheduling engine to handle Task execution */ - use_unified_scheduling_engine?: boolean; - /**Task is disabled on Remote App Sessions */ - disallow_start_on_remote_app_session?: boolean; - /**Options to run Task during system maintenance periods */ - maintenance?: MaintenanceSettings; - /**Task disabled on next OS startup */ - volatile?: boolean; -} - -/** - * Restart on failure options - */ -interface RestartType { - /**Duration between restarts */ - interval: string; - /**Number of restart attempts */ - count: number; -} - -/** - * Idle options - */ -interface IdleSettings { - /**Task may be delayed up until specified duration */ - duration?: string; - /**Task will wait for system to become idle */ - wait_timeout?: string; - /**Task stops if system is no longer Idle */ - stop_on_idle_end?: boolean; - /**Task restarts when system returns to Idle */ - restart_on_idle?: boolean; -} - -/** - * Network options - */ -interface NetworkSettings { - /**Task runs only on specified network name */ - name?: string; - /**GUID associated with `NetworkSettings` */ - id?: string; -} - -/** - * Maintenance options - */ -interface MaintenanceSettings { - /**Duration of maintenance */ - period: string; - /**Deadline for Task to run */ - deadline?: string; - /**Task can run independently of other Tasks with `MaintenanceSettings` */ - exclusive?: boolean; -} - -/** - * SID data associated with Task - */ -interface Principals { - /**Principal name for running the Task */ - user_id?: string; - /**Determines if Task run on logon */ - logon_type?: string; - /**Group ID associated with Task. Task can be triggered by anyone in Group ID */ - group_id?: string; - /**Friendly name of the principal */ - display_name?: string; - /**Privilege level of Task */ - run_level?: string; - /**Process Token SID associated with Task */ - process_token_sid_type?: string; - /**Array of privileges value */ - required_privileges?: string[]; - /**Unique user selected ID */ - id_attribute?: string; -} - -/** - * Actions run by the Task - */ -interface Actions { - /**Executes one or more commands */ - exec: ExecType[]; - /**COM handler to execute */ - com_handler: ComHandlerType[]; - /**Send an email */ - send_email: SendEmail[]; - /**Display a message */ - show_message: Message[]; -} - -/** - * Command options - */ -interface ExecType { - /**Command to execute */ - command: string; - /**Arguments for command */ - arguments?: string; - /**Path to a directory */ - working_directory?: string; -} - -/** - * COM options - */ -interface ComHandlerType { - /**COM GUID */ - class_id: string; - /**XML data for COM */ - data?: string; -} - -/** - * SendEmail options - */ -interface SendEmail { - /**Email server domain */ - server?: string; - /**Subject of email */ - subject?: string; - /**Who should received email */ - to?: string; - /**Who should be CC'd */ - cc?: string; - /**Who should be BCC'd */ - bcc?: string; - /**Reply to email address */ - reply_to?: string; - /**The sender email address */ - from: string; - /**Custom header fields to include in email */ - header_fields?: Record; - /**Email message body */ - body?: string; - /**List of files to be attached */ - attachment?: string[]; -} - -/** - * Message options - */ -interface Message { - /**Title of message */ - title?: string; - /**Message body */ - body: string; -} -``` +--- +description: Scheduled Tasks setup on Windows +keywords: + - windows + - binary + - plaintext + - persistence +--- + +# Scheduled Tasks + +Windows Scheduled Tasks are a common form of persistence on Windows systems. +There are two (2) types of Scheduled Task files: + +- XML based files +- Job based files + +artemis supports both formats. Starting on Windows Vista and higher XML files +are used for Scheduled Tasks. + +Other Parsers: + +- Any XML reader +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.system.taskscheduler/) + +References: + +- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/Job%20file%20format.asciidoc) +- [Microsoft](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f) + +## TOML Collection + +```toml +[output] +name = "tasks_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "tasks" +[artifacts.tasks] +# Optional +# alt_file = "C:\\Artifacts\\At1.job" +``` + +## Collection Options + +- `alt_file` Full path to alternative Schedule Task file. This configuration is + **optional**. By default artemis will parse all Schedule Task files at their + default location. + +## Output Structure + +Array of `TaskXml` and `TaskJob` + +```typescript +/** + * JSON representation of the Task XML schema. + * Most of the schema is Optional. Only `Actions` is required + */ +export interface TaskXml { + /**Registration Info about the Task */ + registrationInfo?: RegistrationInfo; + /**Triggers that start the Task */ + triggers?: Triggers; + /**Settings for the Task */ + settings?: Settings; + /**Base64 encoded raw binary data associated with the Task */ + data?: string; + /**Principal user information related to the Task */ + principals?: Principals; + /**Actions executed by the Task */ + actions: Actions; + /**Path to the XML file */ + evidence: string; +} + +/** + * Parsed information about the Job file + */ +export interface TaskJob { + /**ID associated with the Task */ + job_id: string; + /**Error retry count for the Task */ + error_retry_count: number; + /**Error retry interval for the Task */ + error_retry_interval: number; + /**Idle deadline for Task */ + idle_deadline: number; + /**Idle wait for Task */ + idle_wait: number; + /**Task Priority */ + priority: string; + /**Max run time for Task */ + max_run_time: number; + /**Task Exit code */ + exit_code: number; + /**Task Status */ + status: string; + /**Flags associated with Task */ + flags: string[]; + /**Last run time for Task in LOCALTIME */ + system_time: string; + /**Running count for Task */ + running_instance_count: number; + /**Application name associated with Task */ + application_name: string; + /**Parameters for application */ + parameters: string; + /**Working directory associated with Task */ + working_directory: string; + /**Creator of Task */ + author: string; + /**Comments associated with Task */ + comments: string; + /**Base64 encoded User data associated with Task */ + user_data: string; + /**Start Error associated with Task */ + start_error: number; + /**Triggers that start the Task */ + triggers: JobTriggers[]; + /**Path to Job file */ + evidence: string; +} + +/** + * Triggers associated with Job file + */ +interface JobTriggers { + /**Task start date */ + start_date: string; + /**Task end date */ + end_date: string; + /**Task start time */ + start_time: string; + /**Task duration */ + duration: number; + /**Task interval */ + interval_mins: number; + /**Array of trigger flags */ + flags: string[]; + /**Array of trigger types */ + types: string[]; +} + +/** + * Registration Info related to Task XML + */ +interface RegistrationInfo { + /**URI associated with */ + uri?: string; + /**SID associated with Task */ + sid?: string; + /**Source of Task */ + source?: string; + /**Creation OR Modification of Task */ + date?: string; + /**Creator of Task */ + author?: string; + /**Version level of Task */ + version?: string; + /**User-friendly description of Task */ + description?: string; + /**URI of external documentation for Task */ + documentation?: string; +} + +/** + * Triggers that active the Task + */ +interface Triggers { + /**Boot triggers for Task */ + boot: BootTrigger[]; + /**Registration triggers for Task. Format is exactly same as BootTrigger*/ + registration: BootTrigger[]; + /**Idle triggers for Task */ + idle: IdleTrigger[]; + /**Time triggers for Task */ + time: TimeTrigger[]; + /**Event triggers for Task */ + event: EventTrigger[]; + /**Logon triggers for Task */ + logon: LogonTrigger[]; + /**Session triggers for Task */ + session: SessionTrigger[]; + /**Calendar triggers for Task */ + calendar: CalendarTrigger[]; + /**Windows Notifications triggers for Task */ + wnf: WnfTrigger[]; +} + +/** + * Most Triggers have a collection of common options + */ +interface BaseTriggers { + /**ID for trigger */ + id?: string; + /**Start date for Task */ + start_boundary?: string; + /**End date for Task */ + end_boundary?: string; + /**Bool value to activate Trigger */ + enabled?: boolean; + /**Time limit for Task */ + execution_time_limit?: string; + /**Repetition for Task */ + repetition?: Repetition; +} + +/** + * Repetition Options for Triggers + */ +interface Repetition { + /**Trigger restart intervals */ + interval: string; + /**Repetition can stop after duration has elapsed */ + duration?: string; + /**Task can stop at end of duration */ + stop_at_duration_end?: boolean; +} + +/** + * Boot options to Trigger Task + */ +interface BootTrigger { + /**Base Triggers associated with Boot */ + common?: BaseTriggers; + /**Task delayed after boot */ + delay?: string; +} + +/** + * Idle options to Trigger Task + */ +interface IdleTrigger { + /**Base Triggers associated with Idle */ + common?: BaseTriggers; +} + +/** + * Time options to Trigger Task + */ +interface TimeTrigger { + /**Base Triggers associated with Time */ + common?: BaseTriggers; + /**Delay time for `start_boundary` */ + random_delay?: string; +} + +/** + * Event options to Trigger Task + */ +interface EventTrigger { + /**Base Triggers associated with Event */ + common?: BaseTriggers; + /**Array of subscriptions that can Trigger the Task */ + subscription: string[]; + /**Delay to Trigger the Task */ + delay?: string; + /**Trigger can start Task after `number_of_occurrences` */ + number_of_occurrences?: number; + /**Trigger can start Task after `period_of_occurrence` */ + period_of_occurrence?: string; + /**Specifies XML field name */ + matching_element?: string; + /**Specifies set of XML elements */ + value_queries?: string[]; +} + +/** + * Logon options to Trigger Task + */ +interface LogonTrigger { + /**Base Triggers associated with Logon */ + common?: BaseTriggers; + /**Account name associated with Logon Trigger */ + user_id?: string; + /**Delay Logon Task Trigger */ + delay?: string; +} + +/** + * Session options to Trigger Task + */ +interface SessionTrigger { + /**Base Triggers associated with Session */ + common?: BaseTriggers; + /**Account name associated with Session Trigger */ + user_id?: string; + /**Delay Session Task Trigger */ + delay?: string; + /**Session change that Triggers Task */ + state_change?: string; +} + +/** + * Windows Notification options to Trigger Task + */ +interface WnfTrigger { + /**Base Triggers associated with Windows Notification */ + common?: BaseTriggers; + /**Notification State name */ + state_name: string; + /**Delay Notification Trigger Task */ + delay?: string; + /**Data associated with Notification Trigger */ + data?: string; + /**Offset associated with Notification Trigger */ + data_offset?: string; +} + +/** + * Calendar Options to Trigger Task + */ +interface CalendarTrigger { + /**Base Triggers associated with Calendar */ + common?: BaseTriggers; + /**Delay Calendar Trigger Task */ + random_delay?: string; + /**Run Task on every X number of days */ + schedule_by_day?: ByDay; + /**Run Task on every X number of weeks */ + schedule_by_week?: ByWeek; + /**Run Task on specific days of month */ + schedule_by_month?: ByMonth; + /**Run Task on specific weeks on specific days */ + schedule_by_month_day_of_week?: ByMonthDayWeek; +} + +/** + * How often to run Task by days + */ +interface ByDay { + /**Run Task on X number of days. Ex: Two (2) means every other day */ + days_interval?: number; +} + +/** + * How often to run Task by Weeks + */ +interface ByWeek { + /**Run Task on X number of weeks. Ex: Two (2) means every other week */ + weeks_interval?: number; + /**Runs on specified days of the week. Ex: Monday, Tuesday */ + days_of_week?: string[]; +} + +/** + * How often to run Task by Months + */ +interface ByMonth { + /**Days of month to run Task */ + days_of_month?: string[]; + /**Months to run Task. Ex: July, August */ + months?: string[]; +} + +/**How often to run Tasks by Months and Weeks */ +interface ByMonthDayWeek { + /**Weeks of month to run Task */ + weeks?: string[]; + /**Days of month to run Task */ + days_of_week?: string[]; + /**Months to run Task */ + months?: string[]; +} + +/** + * Settings determine how to run Task Actions + */ +interface Settings { + /**Start Task on demand */ + allow_start_on_demand?: boolean; + /**Restart if fails */ + restart_on_failure?: RestartType; + /**Determines how Windows handles multiple Task executions */ + multiple_instances_policy?: string; + /**Disable Task on battery power */ + disallow_start_if_on_batteries?: boolean; + /**Stop Task if going on battery power */ + stop_if_going_on_batteries?: boolean; + /**Task can be terminated if time limits exceeded */ + allow_hard_terminate?: boolean; + /**If scheduled time is missed, Task may be started */ + start_when_available?: boolean; + /**Run based on network profile name */ + network_profile_name?: string; + /**Run only if network connection available */ + run_only_if_network_available?: boolean; + /**Wake system from standby or hibernate to run */ + wake_to_run?: boolean; + /**Task is enabled */ + enabled?: boolean; + /**Task is hidden from console or GUI */ + hidden?: boolean; + /**Delete Task after specified duration and no future run times */ + delete_expired_tasks_after?: string; + /**Options to run when Idle */ + idle_settings?: IdleSettings; + /**Network settings to run */ + network_settings?: NetworkSettings; + /**Task execution time limit */ + execution_time_limit?: string; + /**Task Priority. Lowest is 1. Highest is 10 */ + priority?: number; + /**Only run if system is Idle */ + run_only_if_idle?: boolean; + /**Use unified scheduling engine to handle Task execution */ + use_unified_scheduling_engine?: boolean; + /**Task is disabled on Remote App Sessions */ + disallow_start_on_remote_app_session?: boolean; + /**Options to run Task during system maintenance periods */ + maintenance?: MaintenanceSettings; + /**Task disabled on next OS startup */ + volatile?: boolean; +} + +/** + * Restart on failure options + */ +interface RestartType { + /**Duration between restarts */ + interval: string; + /**Number of restart attempts */ + count: number; +} + +/** + * Idle options + */ +interface IdleSettings { + /**Task may be delayed up until specified duration */ + duration?: string; + /**Task will wait for system to become idle */ + wait_timeout?: string; + /**Task stops if system is no longer Idle */ + stop_on_idle_end?: boolean; + /**Task restarts when system returns to Idle */ + restart_on_idle?: boolean; +} + +/** + * Network options + */ +interface NetworkSettings { + /**Task runs only on specified network name */ + name?: string; + /**GUID associated with `NetworkSettings` */ + id?: string; +} + +/** + * Maintenance options + */ +interface MaintenanceSettings { + /**Duration of maintenance */ + period: string; + /**Deadline for Task to run */ + deadline?: string; + /**Task can run independently of other Tasks with `MaintenanceSettings` */ + exclusive?: boolean; +} + +/** + * SID data associated with Task + */ +interface Principals { + /**Principal name for running the Task */ + user_id?: string; + /**Determines if Task run on logon */ + logon_type?: string; + /**Group ID associated with Task. Task can be triggered by anyone in Group ID */ + group_id?: string; + /**Friendly name of the principal */ + display_name?: string; + /**Privilege level of Task */ + run_level?: string; + /**Process Token SID associated with Task */ + process_token_sid_type?: string; + /**Array of privileges value */ + required_privileges?: string[]; + /**Unique user selected ID */ + id_attribute?: string; +} + +/** + * Actions run by the Task + */ +interface Actions { + /**Executes one or more commands */ + exec: ExecType[]; + /**COM handler to execute */ + com_handler: ComHandlerType[]; + /**Send an email */ + send_email: SendEmail[]; + /**Display a message */ + show_message: Message[]; +} + +/** + * Command options + */ +interface ExecType { + /**Command to execute */ + command: string; + /**Arguments for command */ + arguments?: string; + /**Path to a directory */ + working_directory?: string; +} + +/** + * COM options + */ +interface ComHandlerType { + /**COM GUID */ + class_id: string; + /**XML data for COM */ + data?: string; +} + +/** + * SendEmail options + */ +interface SendEmail { + /**Email server domain */ + server?: string; + /**Subject of email */ + subject?: string; + /**Who should received email */ + to?: string; + /**Who should be CC'd */ + cc?: string; + /**Who should be BCC'd */ + bcc?: string; + /**Reply to email address */ + reply_to?: string; + /**The sender email address */ + from: string; + /**Custom header fields to include in email */ + header_fields?: Record; + /**Email message body */ + body?: string; + /**List of files to be attached */ + attachment?: string[]; +} + +/** + * Message options + */ +interface Message { + /**Title of message */ + title?: string; + /**Message body */ + body: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/usbs.md b/artemis-docs/docs/Artifacts/Windows Artfacts/usbs.md index 7944ee8d..06027b7d 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/usbs.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/usbs.md @@ -1,61 +1,62 @@ ---- -description: Connected USB devices -keywords: - - windows - - registry ---- - -# USBs - -Artemis support attempting to extract USB devices that have been connected to -the Windows system. It will parse the SYSTEM Registry file to look for USB -devices that have been connected. - -References: - -- [Truth about USBs](https://www.sans.org/blog/the-truth-about-usb-device-serial-numbers/) - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -USBs keys. - -## Sample API Script - -```typescript -import { listUsbDevices } from "./artemis-api/src/windows/registry/usb"; - -function main() { - const results = listUsbDevices(); - console.log(results); -} -``` - -## Output Structure - -An array of `UsbDevices` - -```typescript -export interface UsbDevices { - device_class_id: string; - friendly_name: string; - /**Last drive letter assigned to the USB */ - drive_letter: string; - last_connected: string; - last_insertion: string; - last_removal: string; - install: string; - first_install: string; - usb_type: string; - vendor: string; - product: string; - revision: string; - tracking_id: string; - disk_id: string; - message: string; - datetime: string; - timestamp_desc: "USB Last Connected"; - artifact: "Windows USB Device"; - data_type: "windows:registry:usb:entry"; -} -``` +--- +description: Connected USB devices +keywords: + - windows + - registry +--- + +# USBs + +Artemis support attempting to extract USB devices that have been connected to +the Windows system. It will parse the SYSTEM Registry file to look for USB +devices that have been connected. + +References: + +- [Truth about USBs](https://www.sans.org/blog/the-truth-about-usb-device-serial-numbers/) + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +USBs keys. + +## Sample API Script + +```typescript +import { listUsbDevices } from "./artemis-api/src/windows/registry/usb"; + +function main() { + const results = listUsbDevices(); + console.log(results); +} +``` + +## Output Structure + +An array of `UsbDevices` + +```typescript +export interface UsbDevices { + device_class_id: string; + friendly_name: string; + /**Last drive letter assigned to the USB */ + drive_letter: string; + last_connected: string; + last_insertion: string; + last_removal: string; + install: string; + first_install: string; + usb_type: string; + vendor: string; + product: string; + revision: string; + tracking_id: string; + disk_id: string; + message: string; + datetime: string; + timestamp_desc: "USB Last Connected"; + artifact: "Windows USB Device"; + data_type: "windows:registry:usb:entry"; + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/userassist.md b/artemis-docs/docs/Artifacts/Windows Artfacts/userassist.md index b8032755..d9df16e4 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/userassist.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/userassist.md @@ -1,71 +1,73 @@ ---- -description: Tracks applications executed in Windows Explorer -keywords: - - windows - - registry ---- - -# UserAssist - -Windows UserAssist is a Registry artifact that records applications executed -via Windows Explorer. These entries are typically ROT13 encoded (though this can -be disabled). - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.registry.userassist/) - -References: - -- [Libyal](https://winreg-kb.readthedocs.io/en/latest/sources/explorer-keys/User-assist.html) - -## TOML Collection - -```toml -[output] -name = "userassist_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "userassist" -[artifacts.userassist] -# Optional -# alt_file = "C:\\Artifacts\\NTUSER.DAT -# resolve_descriptions = true -``` - -## Collection Options - -- `alt_file` Full path to alternative NTUSER.DAT Registry file. This - configuration is **optional**. By default artemis will parse UserAssist for - all users -- `resolve_descriptions` Enable folder description GUID lookups. Artemis will - attempt to parse the SYSTEM hive to lookup folder descriptions. This - configuration is **optional**. Default is **false**. - -## Output Structure - -An array of `UserAssist` entries - -```typescript -export interface UserAssist { - /**Path of executed application */ - path: string; - /**Last execution time of application */ - last_execution: string; - /**Number of times executed */ - count: number; - /**Registry path to UserAssist entry */ - reg_path: string; - /**ROT13 encoded path */ - rot_path: string; - /**Path of executed application with folder description GUIDs resolved */ - folder_path: string; -} -``` +--- +description: Tracks applications executed in Windows Explorer +keywords: + - windows + - registry +--- + +# UserAssist + +Windows UserAssist is a Registry artifact that records applications executed +via Windows Explorer. These entries are typically ROT13 encoded (though this can +be disabled). + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.registry.userassist/) + +References: + +- [Libyal](https://winreg-kb.readthedocs.io/en/latest/sources/explorer-keys/User-assist.html) + +## TOML Collection + +```toml +[output] +name = "userassist_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "userassist" +[artifacts.userassist] +# Optional +# alt_file = "C:\\Artifacts\\NTUSER.DAT +# resolve_descriptions = true +``` + +## Collection Options + +- `alt_file` Full path to alternative NTUSER.DAT Registry file. This + configuration is **optional**. By default artemis will parse UserAssist for + all users +- `resolve_descriptions` Enable folder description GUID lookups. Artemis will + attempt to parse the SYSTEM hive to lookup folder descriptions. This + configuration is **optional**. Default is **false**. + +## Output Structure + +An array of `UserAssist` entries + +```typescript +export interface UserAssist { + /**Path of executed application */ + path: string; + /**Last execution time of application */ + last_execution: string; + /**Number of times executed */ + count: number; + /**Registry path to UserAssist entry */ + reg_path: string; + /**ROT13 encoded path */ + rot_path: string; + /**Path of executed application with folder description GUIDs resolved */ + folder_path: string; + /**Path to the Registry file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/users.md b/artemis-docs/docs/Artifacts/Windows Artfacts/users.md index 29c42b90..a5f63994 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/users.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/users.md @@ -1,78 +1,80 @@ ---- -description: Users in the SAM Registry file -keywords: - - windows - - registry - - accounts ---- - -# Users - -Gets user info from SAM Registry file - -Other Parsers: - -- Any tool that queries user info - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "users_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "users-windows" -[artifacts.users_windows] -# Optional -# alt_file = "C:\\Artifacts\\SAM" -``` - -## Collection Options - -- `alt_file` Full path to alternative SAM Registry file. This configuration is - **optional**. By default artemis will parse the SAM Registry file at its - default location. - -## Output Structure - -An array of `UserInfo` entries - -```typescript -export interface UserInfo { - /**Last logon for account */ - last_logon: string; - /**Time when password last set */ - password_last_set: string; - /**Last password failure */ - last_password_failure: string; - /**Relative ID for account. Typically last number of SID */ - relative_id: number; - /**Primary group ID for account */ - primary_group_id: number; - /**UAC flags associated with account */ - user_account_control_flags: string[]; - /**Country code for account */ - country_code: number; - /**Code page for account */ - code_page: number; - /**Number of password failures associated with account */ - number_password_failures: number; - /**Number of logons for account */ - number_logons: number; - /**Username for account */ - username: string; - /**SID for account */ - sid: string; -} -``` +--- +description: Users in the SAM Registry file +keywords: + - windows + - registry + - accounts +--- + +# Users + +Gets user info from SAM Registry file + +Other Parsers: + +- Any tool that queries user info + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "users_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "users-windows" +[artifacts.users_windows] +# Optional +# alt_file = "C:\\Artifacts\\SAM" +``` + +## Collection Options + +- `alt_file` Full path to alternative SAM Registry file. This configuration is + **optional**. By default artemis will parse the SAM Registry file at its + default location. + +## Output Structure + +An array of `UserInfo` entries + +```typescript +export interface UserInfo { + /**Last logon for account */ + last_logon: string; + /**Time when password last set */ + password_last_set: string; + /**Last password failure */ + last_password_failure: string; + /**Relative ID for account. Typically last number of SID */ + relative_id: number; + /**Primary group ID for account */ + primary_group_id: number; + /**UAC flags associated with account */ + user_account_control_flags: string[]; + /**Country code for account */ + country_code: number; + /**Code page for account */ + code_page: number; + /**Number of password failures associated with account */ + number_password_failures: number; + /**Number of logons for account */ + number_logons: number; + /**Username for account */ + username: string; + /**SID for account */ + sid: string; + /**Path to the Registry file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/usnjrnl.md b/artemis-docs/docs/Artifacts/Windows Artfacts/usnjrnl.md index efbff2df..68499c4b 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/usnjrnl.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/usnjrnl.md @@ -1,86 +1,91 @@ ---- -description: Tracks file changes -keywords: - - windows - - file meta ---- - -# UsnJrnl - -Windows UsnJrnl is a sparse binary file that tracks changes to files and -directories. Located at the alternative data stream (ADS) -**C:\$Extend\$UsnJrnl:$J**. Parsing this data can sometimes show files that have -been deleted. However, depending on the file activity on the system entries in -the UsnJrnl may get overwritten quickly. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.usn/) - -References: - -- [UsnJrnl](https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-usn_record_v2?redirectedfrom=MSDN) -- [Libyal](https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#usn_change_journal) - -## TOML Collection - -```toml -[output] -name = "usnjrnl_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "usnjrnl" -[artifacts.usnjrnl] -# Optional -# alt_drive = 'D' -# alt_path = "" -``` - -## Collection Options - -- `alt_drive` Expects a single character value. Will use an alternative drive - letter when parsing UsnJrnl. This configuration is **optional**. By default - artemis will use the **%systemdrive%** value (typically **C**) -- `alt_path` Full path to $J file. This configuration is **optional**. - -## Output Structure - -An array of `UsnJrnl` entries - -```typescript -export interface UsnJrnl { - /**Entry number in the MFT */ - mft_entry: number; - /**Sequence number in the MFT */ - mft_sequence: number; - /**Parent entry number in the MFT */ - parent_mft_entry: number; - /**Parent sequence number in the MFT */ - parent_mft_sequence: number; - /**ID number in the Update Sequence Number Journal (UsnJrnl) */ - update_sequence_number: number; - /**Timestamp of of entry update */ - update_time: string; - /**Reason for update action */ - update_reason: string; - /**Source information of the update */ - update_source_flags: string; - /**Security ID associated with entry */ - security_descriptor_id: number; - /**Attributes associate with entry */ - file_attributes: string[]; - /**Name associated with entry. Can be file or directory */ - filename: string; - /**Extension if available for filename */ - extension: string; - /**Full path for the UsnJrnl entry. Obtained by parsing `$MFT` and referencing the `parent_mft_entry` */ - full_path: string; -} -``` +--- +description: Tracks file changes +keywords: + - windows + - file meta +--- + +# UsnJrnl + +Windows UsnJrnl is a sparse binary file that tracks changes to files and +directories. Located at the alternative data stream (ADS) +**C:\\$Extend\\$UsnJrnl:$J**. Parsing this data can sometimes show files that have +been deleted. However, depending on the file activity on the system entries in +the UsnJrnl may get overwritten quickly. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/windows.forensics.usn/) + +References: + +- [UsnJrnl](https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-usn_record_v2?redirectedfrom=MSDN) +- [Libyal](https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#usn_change_journal) + +## TOML Collection + +```toml +[output] +name = "usnjrnl_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "usnjrnl" +[artifacts.usnjrnl] +# Optional +# alt_drive = 'D' +# alt_file = "" +# alt_mft = "" +``` + +## Collection Options + +- `alt_drive` Expects a single character value. Will use an alternative drive + letter when parsing UsnJrnl. This configuration is **optional**. By default + artemis will use the **%systemdrive%** value (typically **C**) +- `alt_file` Full path to alternative $J file. This configuration is **optional**. +- `alt_mft` Full path to alternative MFT file. This configuration is **optional**. + + +## Output Structure + +An array of `UsnJrnl` entries + +```typescript +export interface UsnJrnl { + /**Entry number in the MFT */ + mft_entry: number; + /**Sequence number in the MFT */ + mft_sequence: number; + /**Parent entry number in the MFT */ + parent_mft_entry: number; + /**Parent sequence number in the MFT */ + parent_mft_sequence: number; + /**ID number in the Update Sequence Number Journal (UsnJrnl) */ + update_sequence_number: number; + /**Timestamp of of entry update */ + update_time: string; + /**Reason for update action */ + update_reason: string; + /**Source information of the update */ + update_source_flags: string; + /**Security ID associated with entry */ + security_descriptor_id: number; + /**Attributes associate with entry */ + file_attributes: string[]; + /**Name associated with entry. Can be file or directory */ + filename: string; + /**Extension if available for filename */ + extension: string; + /**Full path for the UsnJrnl entry. Obtained by parsing `$MFT` and referencing the `parent_mft_entry` */ + full_path: string; + /**Path to the UsnJrnl file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/velociraptor.md b/artemis-docs/docs/Artifacts/Windows Artfacts/velociraptor.md new file mode 100644 index 00000000..b7254f0c --- /dev/null +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/velociraptor.md @@ -0,0 +1,52 @@ +--- +description: Velociraptor execution events +keywords: + - windows + - eventlogs +--- + +# Velociraptor Execution + +Artemis supports extracting [Velociraptor](https://docs.velociraptor.app/) execution events from the Windows EventLog +Application.evtx file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +MSI installed entries. + +## Sample API Script + +```typescript +import { veloCommands } from "./artemis-api/mod"; + +function main() { + const results = veloCommands(); + console.log(JSON.stringify(results)); + +} + +main(); +``` + +## Output Structure + +An array of `VeloExecution` objects + +```typescript +export interface VeloExecution { + evidence: string; + pid: number; + message: string; + datetime: string; + provider: string; + event_id: number; + thread_id: number; + event: string; + path: string; + arguments: string[]; + timestamp_desc: "Velociraptor Executed"; + artifact: "Velociraptor EventLog"; + data_type: "windows:eventlogs:velociraptor:entry"; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/wifi.md b/artemis-docs/docs/Artifacts/Windows Artfacts/wifi.md index e39bd2cd..51fda979 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/wifi.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/wifi.md @@ -1,72 +1,73 @@ ---- -description: Windows WiFi connections -keywords: - - windows - - registry ---- - -# WiFi - -Artemis supports extracting WiFi access points that the Windows system has connected -to. By default it will try to parse WiFi networks at SOFTWARE Registry file. - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to get WiFi -network. - -## Sample API Script - -```typescript -import { wifiNetworksWindows } from "./artemis-api/mod"; - -function main() { - const results = wifiNetworksWindows(); - console.log(JSON.stringify(results)); -} - -main(); -``` - -## Output Structure - -An array of `Wifi` - -```typescript -export interface Wifi { - name: string; - description: string; - managed: boolean; - category: WifiCategory; - created_local_time: string; - name_type: NameType; - id: string; - last_connected_local_time: string; - registry_path: string; - registry_file: string; - message: string; - datetime: string; - timestamp_desc: "Registry Key Modified"; - artifact: "WiFi Network"; - data_type: "windows:registry:wifi:entry"; -} - -export enum WifiCategory { - Public = "Public", - Private = "Private", - Domain = "Domain", - Unknown = "Unknown", -} - -/** - * From: https://community.spiceworks.com/t/what-are-the-nametype-values-for-the-networklist-registry-keys/526112/6 - */ -export enum NameType { - Wired = "Wired", - Vpn = "VPN", - Wireless = "Wireless", - Mobile = "Mobile", - Unknown = "Unknown", - -} -``` +--- +description: Windows WiFi connections +keywords: + - windows + - registry +--- + +# WiFi + +Artemis supports extracting WiFi access points that the Windows system has connected +to. By default it will try to parse WiFi networks at SOFTWARE Registry file. + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to get WiFi +network. + +## Sample API Script + +```typescript +import { wifiNetworksWindows } from "./artemis-api/mod"; + +function main() { + const results = wifiNetworksWindows(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `Wifi` + +```typescript +export interface Wifi { + name: string; + description: string; + managed: boolean; + category: WifiCategory; + created_local_time: string; + name_type: NameType; + id: string; + last_connected_local_time: string; + registry_path: string; + registry_file: string; + message: string; + datetime: string; + timestamp_desc: "Registry Key Modified"; + artifact: "WiFi Network"; + data_type: "windows:registry:wifi:entry"; + evidence: string; +} + +export enum WifiCategory { + Public = "Public", + Private = "Private", + Domain = "Domain", + Unknown = "Unknown", +} + +/** + * From: https://community.spiceworks.com/t/what-are-the-nametype-values-for-the-networklist-registry-keys/526112/6 + */ +export enum NameType { + Wired = "Wired", + Vpn = "VPN", + Wireless = "Wireless", + Mobile = "Mobile", + Unknown = "Unknown", + +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/wmipersist.md b/artemis-docs/docs/Artifacts/Windows Artfacts/wmipersist.md index 51d9bd75..f236fbd6 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/wmipersist.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/wmipersist.md @@ -1,253 +1,255 @@ ---- -description: The Windows Management Instrumentation (WMI) Repository -keywords: - - windows - - wmi ---- - -# WMI - -The Windows Management Instrumentation (WMI) Repository is a collection of tools -that allow users to interact and manage Windows systems. Malware can also use -WMI to persist on a Windows system. Malware the persist via WMI is typically -located in the WMI Repository. - -Default location: C:\\Windows\\System32\\wbem\\Repository - -Artemis supports parsing WMI Repository on Windows 7+ and will parse all WMI -Namespaces to look for evidence of persistence. - -Other Parsers: - -- [velociraptor](https://github.com/Velocidex/velociraptor) -- [dissect](https://github.com/fox-it/dissect.cim) - -References: - -- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/WMI%20repository%20file%20format.asciidoc) -- [velociraptor blog](https://docs.velociraptor.app/blog/2022/2022-01-12-wmi-eventing) -- [WMI hunting blog](https://redcanary.com/threat-detection-report/techniques/windows-management-instrumentation) - -## TOML Collection - -```toml -[output] -name = "wmipersist_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "wmipersist" -[artifacts.wmipersist] -# Optional -# alt_dir = "D:\\Evidence\\WMI" -``` - -## Collection Options - -- `alt_dir` An alternative directory to use containing the WMI Repository. This - directory needs to contain MAPPING\*.MAP, OBJECTS.DATA, INDEX.BTR files - -## Output Structure - -An array of `WmiPersist` entries - -```typescript -export interface WmiPersist { - /**SID associated with the WMI entry */ - sid: string; - /**Name of WMI class */ - class: string; - /**Query that triggers the WMI entry */ - query: string; - /**Filter associated with WMI entry */ - filter: string; - /**Consumer associated with WMI entry */ - consumer: string; - /**Name of the Consumer */ - consumer_name: string; - /** Data associated with the WMI entry. Can use `class` to determine what the type is. - * Most common ones are defined, however users can create their own class - */ - values: - | EventLogConsumer - | ActiveScriptConsumer - | CommandLineConsumer - | LogFileConsumer - | SmtpConsumer - | Record; -} - -/** - * Consumer that logs a message to the Windows EventLogs when an event is triggered - */ -export interface EventLogConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Event category */ - Category: number; - /**Name of the event property that contains data */ - NameOfRawDataProperty: string; - /**Event message in the message DLL */ - EvenID: number; - /**Type of event */ - EventType: number; - /**Array of strings to insert for an event log entry */ - InsertionStringTemplates: string[]; - /**Number of strings in `InsertionStringTemplates` */ - NumberOfInsertionStrings: number; - /**SID associated with event */ - NameOfUserSidProperty: string | Uint8Array; - /**Source name where message is located */ - SourceName: string; - /**Name of system on which to log an event */ - UNCServerName: string; -} - -/** - * Consumer to execute a script when an event is triggered - */ -export interface ActiveScriptConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ - KillTimeOut: number; - /**Name of scripting engine to use */ - ScriptingEngine: string; - /**Name of file to execute script. Must be NULL if `ScriptText` is NOT NULL */ - ScriptFileName: string; - /**Contents of script to execute. Must be NULL if `ScriptFileName` is NOT NULL */ - ScriptText: string; -} - -/** - * Consumer to start a process when an event is triggered - */ -export interface CommandLineConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Specifies command to execute */ - CommandLineTemplate: string; - /**Unused */ - CreateNewConsole: boolean; - /**Will create a new process group */ - CreateNewProcessGroup: boolean; - /**New process will run in Virtual DOS Machine (VDM) */ - CreateSeparateWowVdm: boolean; - /**New process will run in shared Virtual DOS Machine (VDM) */ - CreateSharedWowVdm: boolean; - /**Unused */ - DesktopName: string; - /**Specifies the file to execute */ - ExecutablePath: string; - /**Color to use if new console is window is created */ - FillAttributes: number; - /**Cursor feedback is disabled */ - ForceOffFeedback: boolean; - /**Cursor feedback is enabled */ - ForceOnFeedback: boolean; - /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ - KillTimeout: number; - /**Priority of process threads */ - Priority: number; - /**Determines if process is launched with interactive WinStation or default WinStation */ - RunInteractively: boolean; - /**Determines Window show state */ - ShowWindowCommand: number; - /**Whether to use default error mode */ - UseDefaultErrorMode: boolean; - /**Title to use for process */ - WindowTitle: string; - /**Working directory for the process */ - WorkingDirectory: string; - /**X-offset, in pixels, from the left edge of the screen to the left edge of the window, if a new window is created. */ - XCoordinate: number; - /**Screen buffer width, in character columns, if a new console window is created. This property is ignored in a GUI process. */ - XNumCharacters: number; - /**Width, in pixels, of a new window, if a new window is created. */ - XSize: number; - /**Y-offset, in pixels, from the top edge of the screen to the top edge of the window, if a new window is created. */ - YCoordinate: number; - /**Screen buffer height, in character rows, if a new console window is created. This property is ignored in a GUI process. */ - YNumCharacters: number; - /**Height, in pixels, of the new window, if a new window is created. */ - YSize: number; - /**Specifies the initial text and background colors if a new console window is created in a console application */ - FillAttribute: number; -} - -/** - * Consumer to write customer strings to text file (log) when an event is triggered - */ -export interface LogFileConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Whether log file is Unicode or multibyte code file */ - IsUnicode: boolean; - /**Max log file size */ - MaximumFileSize: bigint; - /**String to write to log file */ - Text: string; -} - -/** - * Consumer that sends an email when an event is triggered - */ -export interface SmtpConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Addresses to send email (BCC) */ - BccLine: string; - /**Addresses to send email (CC) */ - CcLine: string; - /**From address to use to send email. Default is: `WinMgmt@MachineName` */ - FromLine: string; - /**Headers to insert into email */ - HeaderFields: string[]; - /**Body of email */ - Message: string; - /**Reply-to line of an email message */ - ReplyToLine: string; - /**SMTP server to use to send emails */ - SMTPServer: string; - /**Subject line for email */ - Subject: string; - /**Addresses to send email to */ - ToLine: string; -} -``` +--- +description: The Windows Management Instrumentation (WMI) Repository +keywords: + - windows + - wmi +--- + +# WMI + +The Windows Management Instrumentation (WMI) Repository is a collection of tools +that allow users to interact and manage Windows systems. Malware can also use +WMI to persist on a Windows system. Malware the persist via WMI is typically +located in the WMI Repository. + +Default location: C:\\Windows\\System32\\wbem\\Repository + +Artemis supports parsing WMI Repository on Windows 7+ and will parse all WMI +Namespaces to look for evidence of persistence. + +Other Parsers: + +- [velociraptor](https://github.com/Velocidex/velociraptor) +- [dissect](https://github.com/fox-it/dissect.cim) + +References: + +- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/WMI%20repository%20file%20format.asciidoc) +- [velociraptor blog](https://docs.velociraptor.app/blog/2022/2022-01-12-wmi-eventing) +- [WMI hunting blog](https://redcanary.com/threat-detection-report/techniques/windows-management-instrumentation) + +## TOML Collection + +```toml +[output] +name = "wmipersist_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "wmipersist" +[artifacts.wmipersist] +# Optional +# alt_dir = "D:\\Evidence\\WMI" +``` + +## Collection Options + +- `alt_dir` An alternative directory to use containing the WMI Repository. This + directory needs to contain MAPPING\*.MAP, OBJECTS.DATA, INDEX.BTR files + +## Output Structure + +An array of `WmiPersist` entries + +```typescript +export interface WmiPersist { + /**SID associated with the WMI entry */ + sid: string; + /**Name of WMI class */ + class: string; + /**Query that triggers the WMI entry */ + query: string; + /**Filter associated with WMI entry */ + filter: string; + /**Consumer associated with WMI entry */ + consumer: string; + /**Name of the Consumer */ + consumer_name: string; + /** Data associated with the WMI entry. Can use `class` to determine what the type is. + * Most common ones are defined, however users can create their own class + */ + values: + | EventLogConsumer + | ActiveScriptConsumer + | CommandLineConsumer + | LogFileConsumer + | SmtpConsumer + | Record; + /**Path to the WMI Repository directory */ + evidence: string; +} + +/** + * Consumer that logs a message to the Windows EventLogs when an event is triggered + */ +export interface EventLogConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Event category */ + Category: number; + /**Name of the event property that contains data */ + NameOfRawDataProperty: string; + /**Event message in the message DLL */ + EvenID: number; + /**Type of event */ + EventType: number; + /**Array of strings to insert for an event log entry */ + InsertionStringTemplates: string[]; + /**Number of strings in `InsertionStringTemplates` */ + NumberOfInsertionStrings: number; + /**SID associated with event */ + NameOfUserSidProperty: string | Uint8Array; + /**Source name where message is located */ + SourceName: string; + /**Name of system on which to log an event */ + UNCServerName: string; +} + +/** + * Consumer to execute a script when an event is triggered + */ +export interface ActiveScriptConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ + KillTimeOut: number; + /**Name of scripting engine to use */ + ScriptingEngine: string; + /**Name of file to execute script. Must be NULL if `ScriptText` is NOT NULL */ + ScriptFileName: string; + /**Contents of script to execute. Must be NULL if `ScriptFileName` is NOT NULL */ + ScriptText: string; +} + +/** + * Consumer to start a process when an event is triggered + */ +export interface CommandLineConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Specifies command to execute */ + CommandLineTemplate: string; + /**Unused */ + CreateNewConsole: boolean; + /**Will create a new process group */ + CreateNewProcessGroup: boolean; + /**New process will run in Virtual DOS Machine (VDM) */ + CreateSeparateWowVdm: boolean; + /**New process will run in shared Virtual DOS Machine (VDM) */ + CreateSharedWowVdm: boolean; + /**Unused */ + DesktopName: string; + /**Specifies the file to execute */ + ExecutablePath: string; + /**Color to use if new console is window is created */ + FillAttributes: number; + /**Cursor feedback is disabled */ + ForceOffFeedback: boolean; + /**Cursor feedback is enabled */ + ForceOnFeedback: boolean; + /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ + KillTimeout: number; + /**Priority of process threads */ + Priority: number; + /**Determines if process is launched with interactive WinStation or default WinStation */ + RunInteractively: boolean; + /**Determines Window show state */ + ShowWindowCommand: number; + /**Whether to use default error mode */ + UseDefaultErrorMode: boolean; + /**Title to use for process */ + WindowTitle: string; + /**Working directory for the process */ + WorkingDirectory: string; + /**X-offset, in pixels, from the left edge of the screen to the left edge of the window, if a new window is created. */ + XCoordinate: number; + /**Screen buffer width, in character columns, if a new console window is created. This property is ignored in a GUI process. */ + XNumCharacters: number; + /**Width, in pixels, of a new window, if a new window is created. */ + XSize: number; + /**Y-offset, in pixels, from the top edge of the screen to the top edge of the window, if a new window is created. */ + YCoordinate: number; + /**Screen buffer height, in character rows, if a new console window is created. This property is ignored in a GUI process. */ + YNumCharacters: number; + /**Height, in pixels, of the new window, if a new window is created. */ + YSize: number; + /**Specifies the initial text and background colors if a new console window is created in a console application */ + FillAttribute: number; +} + +/** + * Consumer to write customer strings to text file (log) when an event is triggered + */ +export interface LogFileConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Whether log file is Unicode or multibyte code file */ + IsUnicode: boolean; + /**Max log file size */ + MaximumFileSize: bigint; + /**String to write to log file */ + Text: string; +} + +/** + * Consumer that sends an email when an event is triggered + */ +export interface SmtpConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Addresses to send email (BCC) */ + BccLine: string; + /**Addresses to send email (CC) */ + CcLine: string; + /**From address to use to send email. Default is: `WinMgmt@MachineName` */ + FromLine: string; + /**Headers to insert into email */ + HeaderFields: string[]; + /**Body of email */ + Message: string; + /**Reply-to line of an email message */ + ReplyToLine: string; + /**SMTP server to use to send emails */ + SMTPServer: string; + /**Subject line for email */ + Subject: string; + /**Addresses to send email to */ + ToLine: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/Windows Artfacts/wordwheel.md b/artemis-docs/docs/Artifacts/Windows Artfacts/wordwheel.md index fe9477bb..fd268bc9 100644 --- a/artemis-docs/docs/Artifacts/Windows Artfacts/wordwheel.md +++ b/artemis-docs/docs/Artifacts/Windows Artfacts/wordwheel.md @@ -1,51 +1,51 @@ ---- -description: Windows Explorer Search terms -keywords: - - windows - - registry ---- - -# WordWheel - -Artemis supports extracting WordWheel entries from the NTUSER.DAT Registry file. -When a user executes a search via Windows Explorer, the search term may get -saved to the Windows Registry. - -(However, starting with Windows 11 23H2 WordWheel has been removed). - -## Collection - -You have to use the artemis [api](../../API/overview.md) in order to collect -WordWheel keys. - -## Sample API Script - -```typescript -import { - parseWordWheel, -} from "./artemis-api/src/windows/registry/wordwheel"; - -async function main() { - const path = "glob to NTUSER.DAT files"; - const results = parseWordWheel(path); - - console.log(results); -} -``` - -## Output Structure - -An array of `WordWheelEntry` - -```typescript -export interface WordWheelEntry { - /**Searched term entered in Windows Explorer*/ - search_term: string; - /**Last modified tiemstamp for Registry key */ - last_modified: string; - /**Registry file path */ - source_path: string; - /**Registry key path*/ - reg_path: string; -} -``` +--- +description: Windows Explorer Search terms +keywords: + - windows + - registry +--- + +# WordWheel + +Artemis supports extracting WordWheel entries from the NTUSER.DAT Registry file. +When a user executes a search via Windows Explorer, the search term may get +saved to the Windows Registry. + +(However, starting with Windows 11 23H2 WordWheel has been removed). + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to collect +WordWheel keys. + +## Sample API Script + +```typescript +import { + parseWordWheel, +} from "./artemis-api/src/windows/registry/wordwheel"; + +async function main() { + const path = "glob to NTUSER.DAT files"; + const results = parseWordWheel(path); + + console.log(results); +} +``` + +## Output Structure + +An array of `WordWheelEntry` + +```typescript +export interface WordWheelEntry { + /**Searched term entered in Windows Explorer*/ + search_term: string; + /**Last modified tiemstamp for Registry key */ + last_modified: string; + /**Registry file path */ + evidence: string; + /**Registry key path*/ + reg_path: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/esxi.md b/artemis-docs/docs/Artifacts/esxi.md new file mode 100644 index 00000000..cf5ecd67 --- /dev/null +++ b/artemis-docs/docs/Artifacts/esxi.md @@ -0,0 +1,18 @@ +--- +sidebar_position: 8 +--- + +# ESXi + +Artemis has basic support for parsing forensic artifacts on ESXi systems. + +A main focus point of artemis is to make a best effort to not rely on the +ESXi APIs. Since artemis is a forensic focused tool, we do not want to rely +on APIs from a potentially compromised system. + +Currently artemis is unable to extract volatile artifacts from memory such as: + +- Processes +- System info +- Network connections + diff --git a/artemis-docs/docs/Artifacts/iOS Artifacts/_category_.json b/artemis-docs/docs/Artifacts/iOS Artifacts/_category_.json index 61b555e0..ac96d2f8 100644 --- a/artemis-docs/docs/Artifacts/iOS Artifacts/_category_.json +++ b/artemis-docs/docs/Artifacts/iOS Artifacts/_category_.json @@ -1,6 +1,6 @@ { "label": "iOS apps and artifacts", - "position": 12, + "position": 13, "link": { "type": "generated-index", "description": "Apps and artifacts from iOS and iTunes" diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/_category_.json b/artemis-docs/docs/Artifacts/macOS Artifacts/_category_.json index 28bdf46a..c277c646 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/_category_.json +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/_category_.json @@ -1,6 +1,6 @@ { "label": "macOS Artifacts", - "position": 10, + "position": 11, "link": { "type": "generated-index", "description": "Forensic artifacts for macOS systems" diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/bookmarks.md b/artemis-docs/docs/Artifacts/macOS Artifacts/bookmarks.md index 830d1022..9b107fbc 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/bookmarks.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/bookmarks.md @@ -78,6 +78,12 @@ export interface BookmarkData { is_executable: boolean; /**Does target file have file reference flag */ file_ref_flag: boolean; + /**URL string to target */ + url_string: string; + /**Target filename */ + target_filename: string; + /**Volume depth associated with target */ + volume_depth: number; } export enum TargetFlags { diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/cron.md b/artemis-docs/docs/Artifacts/macOS Artifacts/cron.md index aab53277..a8d7cf45 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/cron.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/cron.md @@ -14,7 +14,7 @@ supported systems. Other parsers: -- Any program that read a text file +- Any program that can read a text file References: diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/emond.md b/artemis-docs/docs/Artifacts/macOS Artifacts/emond.md index de5b3b3a..634dfd9a 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/emond.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/emond.md @@ -1,138 +1,140 @@ ---- -description: Emond jobs on macOS -keywords: - - macOS - - persistence - - plaintext ---- - -# Emond - -macOS Event Monitor Daemon (Emond) is a service that allows users to register -rules to perform actions when specific events are triggered, for example "system -startup". Emond can be leveraged to achieve persistence on macOS. Starting on -macOS Ventura (13) emond has been removed. - -Other Parsers: - -- None - -References: - -- [What is emond](https://magnusviri.com/what-is-emond.html) -- [Emond for Persistence](https://www.xorrior.com/emond-persistence/) - -## TOML Collection - -```toml -[output] -name = "emond_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "emond" -[artifacts.emond] -# Optional -# alt_path = "" -``` - -## Collection Options - -- `alt_path`Use an alternative path to the Emond files. This configuration is - **optional**. By default artemis will read the Emond config file to determine - file paths - -## Output Structure - -An array of `Emond` entries - -```typescript -export interface Emond { - /**Name of `Emond` rule */ - name: string; - /**Is rule enabled */ - enabled: boolean; - /**Event types associated with the rule */ - event_types: string[]; - /**Start time of the rule */ - start_tiem: string; - /**If partial criteria match should trigger the rule */ - allow_partial_criterion_match: boolean; - /**Array of commad actions if rule is triggered */ - command_actions: Command[]; - /**Array of log actions if rule is triggered */ - log_actions: Log[]; - /**Array of send email actions if rule is triggered */ - send_email_actions: SendEmailSms[]; - /**Array of send sms actions if rule is triggered. Has same structure as send email */ - send_sms_actions: SendEmailSms[]; - /**Criteria for the `Emond` rule */ - criterion: Record[]; - /**Variables associated with the criterion */ - variables: Record[]; - /**If the emond client is enabled */ - emond_clients_enabled: boolean; - /**Emond plist created */ - plist_created: string; - /**Emond plist modified */ - plist_modifed: string; - /**Emond plist changed */ - plist_changed: string; - /**Emond plist accessed */ - plist_accessed: string; -} - -/** - * Commands to execute if rule is triggered - */ -interface Command { - /**Command name */ - command: string; - /**User associated with command */ - user: string; - /**Group associated with command */ - group: string; - /**Arguments associated with command */ - arguments: string[]; -} - -/** - * Log settings if rule is triggered - */ -interface Log { - /**Log message content */ - message: string; - /**Facility associated with log action */ - facility: string; - /**Level of log */ - log_level: string; - /**Log type */ - log_type: string; - /**Parameters associated with log action */ - parameters: Record; -} - -/** - * Email or SMS to send if rule is triggered - */ -interface SendEmailSms { - /**Content of the email/sms */ - message: string; - /**Subject of the email/sms */ - subject: string; - /**Path to local binary */ - localization_bundle_path: string; - /**Remote URL to send the message */ - relay_host: string; - /**Email associated with email/sms action */ - admin_email: string; - /**Targerts to receive email/sms */ - recipient_addresses: string[]; -} -``` +--- +description: Emond jobs on macOS +keywords: + - macOS + - persistence + - plaintext +--- + +# Emond + +macOS Event Monitor Daemon (Emond) is a service that allows users to register +rules to perform actions when specific events are triggered, for example "system +startup". Emond can be leveraged to achieve persistence on macOS. Starting on +macOS Ventura (13) emond has been removed. + +Other Parsers: + +- None + +References: + +- [What is emond](https://magnusviri.com/what-is-emond.html) +- [Emond for Persistence](https://www.xorrior.com/emond-persistence/) + +## TOML Collection + +```toml +[output] +name = "emond_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "emond" +[artifacts.emond] +# Optional +# alt_dir = "" +``` + +## Collection Options + +- `alt_dir`Use an alternative path to the Emond files. This configuration is + **optional**. By default artemis will read the Emond config file to determine + file paths + +## Output Structure + +An array of `Emond` entries + +```typescript +export interface Emond { + /**Name of `Emond` rule */ + name: string; + /**Is rule enabled */ + enabled: boolean; + /**Event types associated with the rule */ + event_types: string[]; + /**Start time of the rule */ + start_tiem: string; + /**If partial criteria match should trigger the rule */ + allow_partial_criterion_match: boolean; + /**Array of commad actions if rule is triggered */ + command_actions: Command[]; + /**Array of log actions if rule is triggered */ + log_actions: Log[]; + /**Array of send email actions if rule is triggered */ + send_email_actions: SendEmailSms[]; + /**Array of send sms actions if rule is triggered. Has same structure as send email */ + send_sms_actions: SendEmailSms[]; + /**Criteria for the `Emond` rule */ + criterion: Record[]; + /**Variables associated with the criterion */ + variables: Record[]; + /**If the emond client is enabled */ + emond_clients_enabled: boolean; + /**Emond plist created */ + plist_created: string; + /**Emond plist modified */ + plist_modifed: string; + /**Emond plist changed */ + plist_changed: string; + /**Emond plist accessed */ + plist_accessed: string; + /**Path to the Emonod plist file */ + evidence: string; +} + +/** + * Commands to execute if rule is triggered + */ +interface Command { + /**Command name */ + command: string; + /**User associated with command */ + user: string; + /**Group associated with command */ + group: string; + /**Arguments associated with command */ + arguments: string[]; +} + +/** + * Log settings if rule is triggered + */ +interface Log { + /**Log message content */ + message: string; + /**Facility associated with log action */ + facility: string; + /**Level of log */ + log_level: string; + /**Log type */ + log_type: string; + /**Parameters associated with log action */ + parameters: Record; +} + +/** + * Email or SMS to send if rule is triggered + */ +interface SendEmailSms { + /**Content of the email/sms */ + message: string; + /**Subject of the email/sms */ + subject: string; + /**Path to local binary */ + localization_bundle_path: string; + /**Remote URL to send the message */ + relay_host: string; + /**Email associated with email/sms action */ + admin_email: string; + /**Targerts to receive email/sms */ + recipient_addresses: string[]; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/execpolicy.md b/artemis-docs/docs/Artifacts/macOS Artifacts/execpolicy.md index 9ec6e882..7c091e23 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/execpolicy.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/execpolicy.md @@ -1,115 +1,117 @@ ---- -description: Application execution tracker -keywords: - - macOS - - sqlite ---- - -# ExecPolicy - -macOS Execution Policy (ExecPolicy) tracks application execution on a system. -It only tracks execution of applications that tracked by GateKeeper - -Other Parsers: - -- Any SQLITE viewer - -References: - -- [ExecPolicy Info](https://eclecticlight.co/2023/03/13/ventura-has-changed-app-quarantine-with-a-new-xattr/) -- [Policy Internals](https://knight.sc/reverse%20engineering/2019/02/20/syspolicyd-internals.html) - -## TOML Collection - -```toml -[output] -name = "execpolicy_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "execpolicy" -[artifacts.execpolicy] -# Optional -# alt_file = "" -``` - -## Collection Options - -- `alt_file` Use an alternative file to the ExecPolicy database. This - configuration is **optional**. By default artemis will read the default - ExecPolicy database at **/var/db/SystemPolicyConfiguration/ExecPolicy** - -## Output Structure - -An array of `ExecPolicy` entries - -```typescript -export interface ExecPolicy { - /**Is file signed */ - is_signed: number; - /**File ID name */ - file_identifier: string; - /**App bundle ID */ - bundle_identifier: string; - /**Bundle version */ - bundle_version: string; - /**Team ID */ - team_identifier: string; - /**Signing ID */ - signing_identifier: string; - /**Code Directory hash*/ - cdhash: string; - /**SHA256 hash of application */ - main_executable_hash: string; - /**Executable timestamp */ - executable_timestamp: string; - /**Size of file */ - file_size: number; - /**Is library */ - is_library: number; - /**Is file used */ - is_used: number; - /**File ID associated with entry */ - responsible_file_identifier: string; - /**Is valid entry */ - is_valid: number; - /**Is quarantined entry */ - is_quarantined: number; - /**Timestamp for executable measurements */ - executable_measurements_v2_timestamp: string; - /**Reported timestamp */ - reported_timstamp: string; - /**Primary key */ - pk: number; - /**Volume UUID for entry */ - volume_uuid: string; - /**Object ID for entry */ - object_id: number; - /**Filesystem type */ - fs_type_name: string; - /**App Bundle ID */ - bundle_id: string; - /**Policy match for entry */ - policy_match: number; - /**Malware result for entry */ - malware_result: number; - /**Flags for entry */ - flags: number; - /**Modified time */ - mod_time: string; - /**Policy scan cache timestamp */ - policy_scan_cache_timestamp: number; - /**Revocation check timestamp */ - revocation_check_time: string; - /**Scan version for entry */ - scan_version: number; - /**Top policy match for entry */ - top_policy_match: number; -} -``` +--- +description: Application execution tracker +keywords: + - macOS + - sqlite +--- + +# ExecPolicy + +macOS Execution Policy (ExecPolicy) tracks application execution on a system. +It only tracks execution of applications that tracked by GateKeeper + +Other Parsers: + +- Any SQLITE viewer + +References: + +- [ExecPolicy Info](https://eclecticlight.co/2023/03/13/ventura-has-changed-app-quarantine-with-a-new-xattr/) +- [Policy Internals](https://knight.sc/reverse%20engineering/2019/02/20/syspolicyd-internals.html) + +## TOML Collection + +```toml +[output] +name = "execpolicy_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "execpolicy" +[artifacts.execpolicy] +# Optional +# alt_file = "" +``` + +## Collection Options + +- `alt_file` Use an alternative file to the ExecPolicy database. This + configuration is **optional**. By default artemis will read the default + ExecPolicy database at **/var/db/SystemPolicyConfiguration/ExecPolicy** + +## Output Structure + +An array of `ExecPolicy` entries + +```typescript +export interface ExecPolicy { + /**Is file signed */ + is_signed: number; + /**File ID name */ + file_identifier: string; + /**App bundle ID */ + bundle_identifier: string; + /**Bundle version */ + bundle_version: string; + /**Team ID */ + team_identifier: string; + /**Signing ID */ + signing_identifier: string; + /**Code Directory hash*/ + cdhash: string; + /**SHA256 hash of application */ + main_executable_hash: string; + /**Executable timestamp */ + executable_timestamp: string; + /**Size of file */ + file_size: number; + /**Is library */ + is_library: number; + /**Is file used */ + is_used: number; + /**File ID associated with entry */ + responsible_file_identifier: string; + /**Is valid entry */ + is_valid: number; + /**Is quarantined entry */ + is_quarantined: number; + /**Timestamp for executable measurements */ + executable_measurements_v2_timestamp: string; + /**Reported timestamp */ + reported_timstamp: string; + /**Primary key */ + pk: number; + /**Volume UUID for entry */ + volume_uuid: string; + /**Object ID for entry */ + object_id: number; + /**Filesystem type */ + fs_type_name: string; + /**App Bundle ID */ + bundle_id: string; + /**Policy match for entry */ + policy_match: number; + /**Malware result for entry */ + malware_result: number; + /**Flags for entry */ + flags: number; + /**Modified time */ + mod_time: string; + /**Policy scan cache timestamp */ + policy_scan_cache_timestamp: number; + /**Revocation check timestamp */ + revocation_check_time: string; + /**Scan version for entry */ + scan_version: number; + /**Top policy match for entry */ + top_policy_match: number; + /**Path to the ExecPolicy database */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/files.md b/artemis-docs/docs/Artifacts/macOS Artifacts/files.md index f7b5ba05..8aca662c 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/files.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/files.md @@ -1,134 +1,138 @@ ---- -description: macOS file metadata -keywords: - - macos - - file meta ---- - -# Files - -A regular macOS filelisting. Artemis uses the -[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files -and directories on the system. This artifact will fail on any System Integrity -Protection (SIP) protected files. - -Since a filelisting can be extremely large, every 100k entries artemis will -output the data and then continue. - -Artemis will skip -[firmlink](http://www.swiftforensics.com/2019/10/macos-1015-volumes-firmlink-magic.html) -paths on the system by checking for registered firmlink paths at: - -- /usr/share/firmlinks - -Other Parsers: - -- Any tool that can recursively list files and directories - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "files_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "files" # Name of artifact -[artifacts.files] -start_path = "/usr/bin" # Start of file listing -# Optional -depth = 5 # How many sub directories to descend -# Optional -metadata = true # Get executable metadata -# Optional -md5 = true # MD5 all files -# Optional -sha1 = false # SHA1 all files -# Optional -sha256 = false # SHA256 all files -# Optional -path_regex = "" # Regex for paths -# Optional -file_regex = "" # Regex for files -``` - -## Collection Options - -- `start_path` Where to start the file listing. Must exist on the endpoint. To - start at root use `/`. This configuration is **required** -- `depth` Specify how many directories to descend from the `start_path`. Default - is one (1). Must be a postive number. Max value is 255. This configuration is - **optional** -- `metadata` Get [Macho](macho.md) data from `Macho` files. This configuration - is **optional**. Default is **false** -- `md5` Boolean value to enable MD5 hashing on all files. This configuration is - **optional**. Default is **false** -- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration - is **optional**. Default is **false** -- `sha256` Boolean value to enable SHA256 hashing on all files. This - configuration is **optional**. Default is **false** -- `path_regex` Only descend into paths (directories) that match the provided - regex. This configuration is **optional**. Default is no Regex -- `file_regex` Only return entres that match the provided regex. This - configuration is **optional**. Default is no Regex - -## Output Structure - -An array of `MacosFileInfo` entries - -```typescript -export interface MacosFileInfo { - /**Full path to file or directory */ - full_path: string; - /**Directory path */ - directory: string; - /**Filename */ - filename: string; - /**Extension of file if any */ - extension: string; - /**Created timestamp */ - created: string; - /**Modified timestamp */ - modified: string; - /**Changed timestamp */ - changed: string; - /**Accessed timestamp */ - accessed: string; - /**Size of file in bytes */ - size: number; - /**Inode associated with entry */ - inode: number; - /**Mode of file entry */ - mode: number; - /**User ID associated with file */ - uid: number; - /**Group ID associated with file */ - gid: number; - /**MD5 of file */ - md5: string; - /**SHA1 of file */ - sha1: string; - /**SHA256 of file */ - sha256: string; - /**Is the entry a file */ - is_file: boolean; - /**Is the entry a directory */ - is_directory: boolean; - /**Is the entry a symbolic links */ - is_symlink: boolean; - /**Depth the file from provided start point */ - depth: number; - /**Macho binary metadata */ - binary_info: MachoInfo[]; -} -``` +--- +description: macOS file metadata +keywords: + - macos + - file meta +--- + +# Files + +A regular macOS filelisting. Artemis uses the +[walkdir](https://crates.io/crates/walkdir) crate to recursively walk the files +and directories on the system. This artifact will fail on any System Integrity +Protection (SIP) protected files. + +Since a filelisting can be extremely large, every 10k entries artemis will +output the data and then continue. + +Artemis will skip +[firmlink](http://www.swiftforensics.com/2019/10/macos-1015-volumes-firmlink-magic.html) +paths on the system by checking for registered firmlink paths at: + +- /usr/share/firmlinks + +Other Parsers: + +- Any tool that can recursively list files and directories + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "files_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "files" # Name of artifact +[artifacts.files] +start_path = "/usr/bin" # Start of file listing +# Optional +depth = 5 # How many sub directories to descend +# Optional +metadata = true # Get executable metadata +# Optional +md5 = true # MD5 all files +# Optional +sha1 = false # SHA1 all files +# Optional +sha256 = false # SHA256 all files +# Optional +path_regex = "" # Regex for paths +# Optional +file_regex = "" # Regex for files +# Optional +yara = "" # Base64 encoded Yara rule or a remote Yara rule +``` + +## Collection Options + +- `start_path` Where to start the file listing. Must exist on the endpoint. To + start at root use `/`. This configuration is **required** +- `depth` Specify how many directories to descend from the `start_path`. Default + is one (1). Must be a postive number. Max value is 255. This configuration is + **optional** +- `metadata` Get [Macho](macho.md) data from `Macho` files. This configuration + is **optional**. Default is **false** +- `md5` Boolean value to enable MD5 hashing on all files. This configuration is + **optional**. Default is **false** +- `sha1` Boolean value to enable SHA1 hashing on all files. This configuration + is **optional**. Default is **false** +- `sha256` Boolean value to enable SHA256 hashing on all files. This + configuration is **optional**. Default is **false** +- `path_regex` Only descend into paths (directories) that match the provided + regex. This configuration is **optional**. Default is no Regex +- `file_regex` Only return entres that match the provided regex. This + configuration is **optional**. Default is no Regex +- `yara` Either a base64 encoded Yara rule or a Yara rule hosted on a remote server + + +## Output Structure + +An array of `MacosFileInfo` entries + +```typescript +export interface MacosFileInfo { + /**Full path to file or directory */ + full_path: string; + /**Directory path */ + directory: string; + /**Filename */ + filename: string; + /**Extension of file if any */ + extension: string; + /**Created timestamp */ + created: string; + /**Modified timestamp */ + modified: string; + /**Changed timestamp */ + changed: string; + /**Accessed timestamp */ + accessed: string; + /**Size of file in bytes */ + size: number; + /**Inode associated with entry */ + inode: number; + /**Mode of file entry */ + mode: number; + /**User ID associated with file */ + uid: number; + /**Group ID associated with file */ + gid: number; + /**MD5 of file */ + md5: string; + /**SHA1 of file */ + sha1: string; + /**SHA256 of file */ + sha256: string; + /**Is the entry a file */ + is_file: boolean; + /**Is the entry a directory */ + is_directory: boolean; + /**Is the entry a symbolic links */ + is_symlink: boolean; + /**Depth the file from provided start point */ + depth: number; + /**Macho binary metadata */ + binary_info: MachoInfo[]; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/fsevents.md b/artemis-docs/docs/Artifacts/macOS Artifacts/fsevents.md index 305f3e44..490aa2a1 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/fsevents.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/fsevents.md @@ -1,77 +1,77 @@ ---- -description: macOS file change tracker -keywords: - - macos - - file metadata - - binary ---- - -# Fsevents - -macOS Filesystem Events (FsEvents) track changes to files on a macOS system -(similar to UsnJrnl on Windows). Parsing this data can sometimes show files -that have been deleted. -Resides at /**System/Volumes/Data/.fseventsd/** or -**/.fseventsd** on older systems. Artemis will try to parse both locations by -default. - -Other Parsers: - -- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/macos.forensics.fsevents/) - -References: - -- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/MacOS%20File%20System%20Events%20Disk%20Log%20Stream%20format.asciidoc) - -## TOML Collection - -```toml -[output] -name = "fsevents_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "fseventsd" -[artifacts.fseventsd] -# Optional -# alt_file = "" -``` - -## Collection Options - -- `alt_file` Use an alternative FsEvent file. This configuration is - **optional**. By default artemis will read the default locations for FsEvent - files - -## Output Structure - -An array of `Fsevents` entries - -```typescript -export interface Fsevents { - /**Flags associated with FsEvent record */ - flags: string[]; - /**Full path to file associated with FsEvent record */ - path: string; - /**Node ID associated with FsEvent record */ - node: number; - /**Event ID associated with FsEvent record */ - event_id: number; - /**Path to the FsEvent file */ - source: string; - /**Created timestamp of the FsEvent source */ - source_created: string; - /**Modified timestamp of the FsEvent source */ - source_modified: string; - /**Changed timestamp of the FsEvent source */ - source_changed: string; - /**Accessed timestamp of the FsEvent source */ - source_accessed: string; -} -``` +--- +description: macOS file change tracker +keywords: + - macos + - file metadata + - binary +--- + +# Fsevents + +macOS Filesystem Events (FsEvents) track changes to files on a macOS system +(similar to UsnJrnl on Windows). Parsing this data can sometimes show files +that have been deleted. +Resides at /**System/Volumes/Data/.fseventsd/** or +**/.fseventsd** on older systems. Artemis will try to parse both locations by +default. + +Other Parsers: + +- [Velociraptor](https://docs.velociraptor.app/artifact_references/pages/macos.forensics.fsevents/) + +References: + +- [Libyal](https://github.com/libyal/dtformats/blob/main/documentation/MacOS%20File%20System%20Events%20Disk%20Log%20Stream%20format.asciidoc) + +## TOML Collection + +```toml +[output] +name = "fsevents_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "fseventsd" +[artifacts.fseventsd] +# Optional +# alt_file = "" +``` + +## Collection Options + +- `alt_file` Use an alternative FsEvent file. This configuration is + **optional**. By default artemis will read the default locations for FsEvent + files + +## Output Structure + +An array of `Fsevents` entries + +```typescript +export interface Fsevents { + /**Flags associated with FsEvent record */ + flags: string[]; + /**Full path to file associated with FsEvent record */ + path: string; + /**Node ID associated with FsEvent record */ + node: number; + /**Event ID associated with FsEvent record */ + event_id: number; + /**Path to the FsEvent file */ + evidence: string; + /**Created timestamp of the FsEvent source */ + source_created: string; + /**Modified timestamp of the FsEvent source */ + source_modified: string; + /**Changed timestamp of the FsEvent source */ + source_changed: string; + /**Accessed timestamp of the FsEvent source */ + source_accessed: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/groups.md b/artemis-docs/docs/Artifacts/macOS Artifacts/groups.md index d76ef617..eaf37c2d 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/groups.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/groups.md @@ -1,67 +1,69 @@ ---- -description: macOS groups -keywords: - - macos - - accounts - - plist ---- - -# Groups - -Gets group info parsing the plist files at -**/var/db/dslocal/nodes/Default/groups**. - -Other Parsers: - -- Any tool that can parse a plist file - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "groups_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "groups-macos" -[artifacts.groups_macos] -# Optional -# alt_path = "" -``` - -## Collection Options - -- `alt_path` Use an alternative Groups path. This configuration is **optional**. - By default artemis will read all Groups at - **/var/db/dslocal/nodes/Default/groups** - -## Output Structure - -An array of `Groups` entries - -```typescript -export interface Groups { - /**GID for the group */ - gid: string[]; - /**Name of the group */ - name: string[]; - /**Real name associated with the group */ - real_name: string[]; - /**Users associated with group */ - users: string[]; - /**Group members in the group */ - groupmembers: string[]; - /**UUID associated with the group */ - uuid: string[]; -} -``` +--- +description: macOS groups +keywords: + - macos + - accounts + - plist +--- + +# Groups + +Gets group info parsing the plist files at +**/var/db/dslocal/nodes/Default/groups**. + +Other Parsers: + +- Any tool that can parse a plist file + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "groups_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "groups-macos" +[artifacts.groups_macos] +# Optional +# alt_dir = "" +``` + +## Collection Options + +- `alt_dir` Use an alternative Groups path. This configuration is **optional**. + By default artemis will read all Groups at + **/var/db/dslocal/nodes/Default/groups** + +## Output Structure + +An array of `Groups` entries + +```typescript +export interface Groups { + /**GID for the group */ + gid: string[]; + /**Name of the group */ + name: string[]; + /**Real name associated with the group */ + real_name: string[]; + /**Users associated with group */ + users: string[]; + /**Group members in the group */ + groupmembers: string[]; + /**UUID associated with the group */ + uuid: string[]; + /**Path to the Groups plist file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/launchd.md b/artemis-docs/docs/Artifacts/macOS Artifacts/launchd.md index f21da777..d92057cb 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/launchd.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/launchd.md @@ -1,76 +1,76 @@ ---- -description: macOS launch daemons and agents -keywords: - - macOS - - persistence - - plist ---- - -# Launchd - -macOS launch daemons (launchd) are the most common way to register -applications for persistence on macOS. launchd can be registered for a single -user or system wide. artemis will try to parse all known launchd locations by -default. - -- **/Users/*/Library/LaunchDaemons/** -- **/Users/*/Library/LaunchAgents/** -- **/System/Library/LaunchDaemons/** -- **/Library/Apple/System/Library/LaunchDaemons/** -- **/System/Library/LaunchAgents/** -- **/Library/Apple/System/Library/LaunchAgents/** - -Other Parsers: - -- Any tool that can parse a plist file - -References: - -- [launchd](https://www.launchd.info/) -- `man launchd.plist` - -## TOML Collection - -```toml -[output] -name = "launchd_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "launchd" -[artifacts.launchd] -# Optional -# alt_file = "" -``` - -## Collection Options - -- `alt_file` Use an alternative Launchd file. This configuration is - **optional**. By default artemis will read all Launchd Daemons and Agents - -## Output Structure - -An array of `Launchd` entries - -```typescript -export interface Launchd { - /**JSON representation of launchd plist contents */ - launchd_data: Record; - /**Full path of the plist file */ - plist_path: string; - /**Created timestamp for plist file */ - created: string; - /**Modified timestamp for plist file */ - modified: string; - /**Accessed timestamp for plist file */ - accessed: string; - /**Changed timestamp for plist file */ - changed: string; -} -``` +--- +description: macOS launch daemons and agents +keywords: + - macOS + - persistence + - plist +--- + +# Launchd + +macOS launch daemons (launchd) are the most common way to register +applications for persistence on macOS. launchd can be registered for a single +user or system wide. artemis will try to parse all known launchd locations by +default. + +- **/Users/*/Library/LaunchDaemons/** +- **/Users/*/Library/LaunchAgents/** +- **/System/Library/LaunchDaemons/** +- **/Library/Apple/System/Library/LaunchDaemons/** +- **/System/Library/LaunchAgents/** +- **/Library/Apple/System/Library/LaunchAgents/** + +Other Parsers: + +- Any tool that can parse a plist file + +References: + +- [launchd](https://www.launchd.info/) +- `man launchd.plist` + +## TOML Collection + +```toml +[output] +name = "launchd_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "launchd" +[artifacts.launchd] +# Optional +# alt_file = "" +``` + +## Collection Options + +- `alt_file` Use an alternative Launchd file. This configuration is + **optional**. By default artemis will read all Launchd Daemons and Agents + +## Output Structure + +An array of `Launchd` entries + +```typescript +export interface Launchd { + /**JSON representation of launchd plist contents */ + launchd_data: Record; + /**Full path of the plist file */ + evidence: string; + /**Created timestamp for plist file */ + created: string; + /**Modified timestamp for plist file */ + modified: string; + /**Accessed timestamp for plist file */ + accessed: string; + /**Changed timestamp for plist file */ + changed: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/loginitems.md b/artemis-docs/docs/Artifacts/macOS Artifacts/loginitems.md index ac46f7a3..e8ecfbc0 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/loginitems.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/loginitems.md @@ -1,197 +1,197 @@ ---- -description: macOS LoginItems -keywords: - - macOS - - persistence - - plist - - binary ---- - -# Loginitems - -macOS LoginItems are a form of persistence on macOS systems. They are -triggered when a user logs on to the system. They are located at: - -- **/Users/%/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm** - (pre-Ventura) -- **/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v\*.btm** (Ventura+) - - -Other Parsers: - -- [Bookmarks](https://mac-alias.readthedocs.io/en/latest/index.html) - -References: - -- [macOS Persistence](https://www.sentinelone.com/blog/how-malware-persists-on-macos/) -- [Bookmarks](https://michaellynn.github.io/2015/10/24/apples-bookmarkdata-exposed/) - -## TOML Collection - -```toml -[output] -name = "loginitems_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "loginitems" -[artifacts.loginitems] -# Optional -# alt_file = "" -``` - -## Collection Options - -- `alt_file` Use an alternative LoginItem file. This configuration is - **optional**. By default artemis will read default locations for LoginItems - -## Output Structure - -An array of `LoginItem` entries - -```typescript -export interface LoginItems { - /**Path to file to run */ - path: string; - /**Path represented as Catalog Node ID */ - cnid_path: string; - /**Created timestamp of target file */ - created: string; - /**Path to the volume of target file */ - volume_path: string; - /**Target file URL type */ - volume_url: string; - /**Name of volume target file is on */ - volume_name: string; - /**Volume UUID */ - volume_uuid: string; - /**Size of target volume in bytes */ - volume_size: number; - /**Created timestamp of volume */ - volume_created: string; - /**Volume Property flags */ - volume_flags: VolumeFlags[]; - /**Flag if volume if the root filesystem */ - volume_root: boolean; - /**Localized name of target file */ - localized_name: string; - /**Read-Write security extension of target file */ - security_extension_rw: string; - /**Read-Only security extension of target file */ - security_extension_ro: string; - /**File property flags */ - target_flags: TargetFlags[]; - /**Username associated with `Bookmark` */ - username: string; - /**Folder index number associated with target file */ - folder_index: number; - /**UID associated with `LoginItem` */ - uid: number; - /**`LoginItem` creation flags */ - creation_options: CreationFlags[]; - /**Is `LoginItem` bundled in app */ - is_bundled: boolean; - /**App ID associated with `LoginItem` */ - app_id: string; - /**App binary name */ - app_binary: string; - /**Is target file executable */ - is_executable: boolean; - /**Does target file have file reference flag */ - file_ref_flag: boolean; - /**Path to `LoginItem` source */ - source_path: string; -} - -export enum TargetFlags { - RegularFile = "RegularFile", - Directory = "Directory", - SymbolicLink = "SymbolicLink", - Volume = "Volume", - Package = "Package", - SystemImmutable = "SystemImmutable", - UserImmutable = "UserImmutable", - Hidden = "Hidden", - HasHiddenExtension = "HasHiddenExtension", - Application = "Application", - Compressed = "Compressed", - CanSetHiddenExtension = "CanSetHiddenExtension", - Readable = "Readable", - Writable = "Writable", - Executable = "Executable", - AliasFile = "AliasFile", - MountTrigger = "MountTrigger", -} - -export enum CreationFlags { - MinimalBookmark = "MinimalBookmark", - SuitableBookmark = "SuitableBookmark", - SecurityScope = "SecurityScope", - SecurityScopeAllowOnlyReadAccess = "SecurityScopeAllowOnlyReadAccess", - WithoutImplicitSecurityScope = "WithoutImplicitSecurityScope", - PreferFileIDResolutionMask = "PreferFileIDResolutionMask", -} - -export enum VolumeFlags { - Local = "Local", - Automount = "Automount", - DontBrowse = "DontBrowse", - ReadOnly = "ReadOnly", - Quarantined = "Quarantined", - Ejectable = "Ejectable", - Removable = "Removable", - Internal = "Internal", - External = "External", - DiskImage = "DiskImage", - FileVault = "FileVault", - LocaliDiskMirror = "LocaliDiskMirror", - Ipod = "Ipod", - Idisk = "Idisk", - Cd = "Cd", - Dvd = "Dvd", - DeviceFileSystem = "DeviceFileSystem", - TimeMachine = "TimeMachine", - Airport = "Airport", - VideoDisk = "VideoDisk", - DvdVideo = "DvdVideo", - BdVideo = "BdVideo", - MobileTimeMachine = "MobileTimeMachine", - NetworkOptical = "NetworkOptical", - BeingRepaired = "BeingRepaired", - Unmounted = "Unmounted", - SupportsPersistentIds = "SupportsPersistentIds", - SupportsSearchFs = "SupportsSearchFs", - SupportsExchange = "SupportsExchange", - SupportsSymbolicLinks = "SupportsSymbolicLinks", - SupportsDenyModes = "SupportsDenyModes", - SupportsCopyFile = "SupportsCopyFile", - SupportsReadDirAttr = "SupportsReadDirAttr", - SupportsJournaling = "SupportsJournaling", - SupportsRename = "SupportsRename", - SupportsFastStatFs = "SupportsFastStatFs", - SupportsCaseSensitiveNames = "SupportsCaseSensitiveNames", - SupportsCasePreservedNames = "SupportsCasePreservedNames", - SupportsFlock = "SupportsFlock", - SupportsNoRootDirectoryTimes = "SupportsNoRootDirectoryTimes", - SupportsExtendedSecurity = "SupportsExtendedSecurity", - Supports2TbFileSize = "Supports2TbFileSize", - SupportsHardLinks = "SupportsHardLinks", - SupportsMandatoryByteRangeLocks = "SupportsMandatoryByteRangeLocks", - SupportsPathFromId = "SupportsPathFromId", - Journaling = "Journaling", - SupportsSparseFiles = "SupportsSparseFiles", - SupportsZeroRunes = "SupportsZeroRunes", - SupportsVolumeSizes = "SupportsVolumeSizes", - SupportsRemoteEvents = "SupportsRemoteEvents", - SupportsHiddenFiles = "SupportsHiddenFiles", - SupportsDecmpFsCompression = "SupportsDecmpFsCompression", - Has64BitObjectIds = "Has64BitObjectIds", - PropertyFlagsAll = "PropertyFlagsAll", -} -``` +--- +description: macOS LoginItems +keywords: + - macOS + - persistence + - plist + - binary +--- + +# Loginitems + +macOS LoginItems are a form of persistence on macOS systems. They are +triggered when a user logs on to the system. They are located at: + +- **/Users/%/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm** + (pre-Ventura) +- **/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v\*.btm** (Ventura+) + + +Other Parsers: + +- [Bookmarks](https://mac-alias.readthedocs.io/en/latest/index.html) + +References: + +- [macOS Persistence](https://www.sentinelone.com/blog/how-malware-persists-on-macos/) +- [Bookmarks](https://michaellynn.github.io/2015/10/24/apples-bookmarkdata-exposed/) + +## TOML Collection + +```toml +[output] +name = "loginitems_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "loginitems" +[artifacts.loginitems] +# Optional +# alt_file = "" +``` + +## Collection Options + +- `alt_file` Use an alternative LoginItem file. This configuration is + **optional**. By default artemis will read default locations for LoginItems + +## Output Structure + +An array of `LoginItem` entries + +```typescript +export interface LoginItems { + /**Path to file to run */ + path: string; + /**Path represented as Catalog Node ID */ + cnid_path: string; + /**Created timestamp of target file */ + created: string; + /**Path to the volume of target file */ + volume_path: string; + /**Target file URL type */ + volume_url: string; + /**Name of volume target file is on */ + volume_name: string; + /**Volume UUID */ + volume_uuid: string; + /**Size of target volume in bytes */ + volume_size: number; + /**Created timestamp of volume */ + volume_created: string; + /**Volume Property flags */ + volume_flags: VolumeFlags[]; + /**Flag if volume if the root filesystem */ + volume_root: boolean; + /**Localized name of target file */ + localized_name: string; + /**Read-Write security extension of target file */ + security_extension_rw: string; + /**Read-Only security extension of target file */ + security_extension_ro: string; + /**File property flags */ + target_flags: TargetFlags[]; + /**Username associated with `Bookmark` */ + username: string; + /**Folder index number associated with target file */ + folder_index: number; + /**UID associated with `LoginItem` */ + uid: number; + /**`LoginItem` creation flags */ + creation_options: CreationFlags[]; + /**Is `LoginItem` bundled in app */ + is_bundled: boolean; + /**App ID associated with `LoginItem` */ + app_id: string; + /**App binary name */ + app_binary: string; + /**Is target file executable */ + is_executable: boolean; + /**Does target file have file reference flag */ + file_ref_flag: boolean; + /**Path to `LoginItem` source */ + evidence: string; +} + +export enum TargetFlags { + RegularFile = "RegularFile", + Directory = "Directory", + SymbolicLink = "SymbolicLink", + Volume = "Volume", + Package = "Package", + SystemImmutable = "SystemImmutable", + UserImmutable = "UserImmutable", + Hidden = "Hidden", + HasHiddenExtension = "HasHiddenExtension", + Application = "Application", + Compressed = "Compressed", + CanSetHiddenExtension = "CanSetHiddenExtension", + Readable = "Readable", + Writable = "Writable", + Executable = "Executable", + AliasFile = "AliasFile", + MountTrigger = "MountTrigger", +} + +export enum CreationFlags { + MinimalBookmark = "MinimalBookmark", + SuitableBookmark = "SuitableBookmark", + SecurityScope = "SecurityScope", + SecurityScopeAllowOnlyReadAccess = "SecurityScopeAllowOnlyReadAccess", + WithoutImplicitSecurityScope = "WithoutImplicitSecurityScope", + PreferFileIDResolutionMask = "PreferFileIDResolutionMask", +} + +export enum VolumeFlags { + Local = "Local", + Automount = "Automount", + DontBrowse = "DontBrowse", + ReadOnly = "ReadOnly", + Quarantined = "Quarantined", + Ejectable = "Ejectable", + Removable = "Removable", + Internal = "Internal", + External = "External", + DiskImage = "DiskImage", + FileVault = "FileVault", + LocaliDiskMirror = "LocaliDiskMirror", + Ipod = "Ipod", + Idisk = "Idisk", + Cd = "Cd", + Dvd = "Dvd", + DeviceFileSystem = "DeviceFileSystem", + TimeMachine = "TimeMachine", + Airport = "Airport", + VideoDisk = "VideoDisk", + DvdVideo = "DvdVideo", + BdVideo = "BdVideo", + MobileTimeMachine = "MobileTimeMachine", + NetworkOptical = "NetworkOptical", + BeingRepaired = "BeingRepaired", + Unmounted = "Unmounted", + SupportsPersistentIds = "SupportsPersistentIds", + SupportsSearchFs = "SupportsSearchFs", + SupportsExchange = "SupportsExchange", + SupportsSymbolicLinks = "SupportsSymbolicLinks", + SupportsDenyModes = "SupportsDenyModes", + SupportsCopyFile = "SupportsCopyFile", + SupportsReadDirAttr = "SupportsReadDirAttr", + SupportsJournaling = "SupportsJournaling", + SupportsRename = "SupportsRename", + SupportsFastStatFs = "SupportsFastStatFs", + SupportsCaseSensitiveNames = "SupportsCaseSensitiveNames", + SupportsCasePreservedNames = "SupportsCasePreservedNames", + SupportsFlock = "SupportsFlock", + SupportsNoRootDirectoryTimes = "SupportsNoRootDirectoryTimes", + SupportsExtendedSecurity = "SupportsExtendedSecurity", + Supports2TbFileSize = "Supports2TbFileSize", + SupportsHardLinks = "SupportsHardLinks", + SupportsMandatoryByteRangeLocks = "SupportsMandatoryByteRangeLocks", + SupportsPathFromId = "SupportsPathFromId", + Journaling = "Journaling", + SupportsSparseFiles = "SupportsSparseFiles", + SupportsZeroRunes = "SupportsZeroRunes", + SupportsVolumeSizes = "SupportsVolumeSizes", + SupportsRemoteEvents = "SupportsRemoteEvents", + SupportsHiddenFiles = "SupportsHiddenFiles", + SupportsDecmpFsCompression = "SupportsDecmpFsCompression", + Has64BitObjectIds = "Has64BitObjectIds", + PropertyFlagsAll = "PropertyFlagsAll", +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/lulu.md b/artemis-docs/docs/Artifacts/macOS Artifacts/lulu.md index 281bb609..3aa4376a 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/lulu.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/lulu.md @@ -24,32 +24,43 @@ import { luluRules } from "./artemis-api/mod"; function main() { const results = luluRules(); - console.log(results); + console.log(JSON.stringify(results)); } + +main(); ``` ## Output Structure -A `LuluRules` object structure +An array of `Rule` objects ```typescript -export interface LuluRules { - path: string; - rules: Rule[]; -} - export interface Rule { + /**Path to the rules.plist file */ + evidence: string; + /**Binary file allowed to make connection */ file: string; + /**UUID associated with the rule */ uuid: string; + /**Address associated with the rule */ endpoint_addr: string; + /**Is regex enabled */ is_regex: boolean; + /**Scope associated with the rule */ scope: string; + /**Rule action */ type: string; + /**Key associated with the rule */ key: string; + /**LuLu Action performed */ action: LuluAction; + /**Host associated with the rule */ endpoint_host: string; + /**Code Signing info associated with the binary */ code_signing_info: Record; + /**Process ID */ pid: number; + /**Port associated with the rule */ endpoint_port: number; } diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/recentfiles.md b/artemis-docs/docs/Artifacts/macOS Artifacts/recentfiles.md new file mode 100644 index 00000000..93d2d2e7 --- /dev/null +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/recentfiles.md @@ -0,0 +1,61 @@ +--- +description: macOS Recent Files +keywords: + - macos + - plist +--- + +# Recent Files + +Artemis supports parsing macOS recently open files (sfl files). These plist files contain files and directories recently opened by macOS applications + +## Collection + +You have to use the artemis [api](../../API/overview.md) in order to parse +`Recent Files` data. + +## Sample API Script + +```typescript +import { recentFilesMacos } from "./artemis-api/mod"; + +function main() { + const results = recentFilesMacos(); + console.log(JSON.stringify(results)); +} + +main(); +``` + +## Output Structure + +An array of `RecentFiles` objects + +```typescript +export interface RecentFiles { + evidence: string; + shared_file_type: SharedFileType; + message: string; + datetime: string; + timestamp_desc: string; + artifact: "Recent Files"; + data_type: "macos:plist:recentfile:entry"; + plist_data_type: PlistDataType; + [ key: string ]: unknown; +} + +export enum SharedFileType { + FinderFavorite = "Finder Favorite", + VolumeFavorite = "Volume Favorite", + UnknownFavorite = "Unknown Favorite", + ApplicationRecentFiles = "Application Recent File", + ProjectFavorite = "Tag Favorite", + RecentApplication = "Recent Application", + RecentDocuments = "Recent Documents", +} + +export enum PlistDataType { + Bookmark = "Bookmark", + CodeSign = "Code Signing" +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/shellhistory.md b/artemis-docs/docs/Artifacts/macOS Artifacts/shellhistory.md index 25520b95..87fd011b 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/shellhistory.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/shellhistory.md @@ -22,7 +22,7 @@ Artemis supports parsing zsh and bash shell history. Other parsers: -- Any program that read a text file +- Any program that can read a text file References: diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/spotlight.md b/artemis-docs/docs/Artifacts/macOS Artifacts/spotlight.md index 325c6c99..2193fee8 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/spotlight.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/spotlight.md @@ -1,380 +1,380 @@ ---- -description: The macOS Spotlight database -keywords: - - macOS - - file meta - - binary ---- - -# Spotlight - -macOS Spotlight is an indexing service for tracking files and content. The -Spotlight database can contain a huge amount of metadata associated with the -indexed content such as: - -- Timestamps -- Partial file content -- File type and much more - -The primary database is typically located at the root of the macOS Data volume.\ -ex: /System/Volumes/Data/.Spotlight-V100/Store-V3/123-445566-778-12384/ - -By default artemis will only attempt to parse the Spotlight database located at -the Data volume - -However, additional databases can also be found on the macOS system. Known -additional locations are: - -- **/Users/\*/Library/Caches/com.apple.helpd/index.spotlightV\*** -- **/Users/\*/Library/Metadata/CoreSpotlight/index.spotlightV\*** -- **/Users/\*/Library/Developer/Xcode/DocumentationCache/\*/\*DeveloperDocumentation.index/** -- **/Users/\*/Library/Metadata/CoreSpotlight/\*/index.spotlightV\*** -- **/Users/\*/Library/Caches/com.apple.helpd/\*/index.spotlightV\*** - -Similar to the filelisting artifact, every 10k entries artemis will output the -data and continue. - -Other Parsers: - -- [spotlight_parser](https://github.com/ydkhatri/spotlight_parser) -- [mac_apt](https://github.com/ydkhatri/mac_apt) - -References: - -- [Forensic and Security](https://forensicsandsecurity.com/papers/SpotlightMacForensicsSlides.pdf) -- [Spotlight](https://en.wikipedia.org/wiki/Spotlight_(Apple)) -- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/Apple%20Spotlight%20store%20database%20file%20format.asciidoc) - -## TOML Collection - -```toml -[output] -name = "spotlight_collection" -directory = "./tmp" -format = "jsonl" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "spotlight" -[artifacts.spotlight] -# Optional -# alt_path = "" - -# Include additional known Spotlight paths such as -# /Users/*/Library/Caches/com.apple.helpd/index.spotlightV*/* -# /Users/*/Library/Metadata/CoreSpotlight/index.spotlightV*/* -# /Users/*/Library/Developer/Xcode/DocumentationCache/*/*/DeveloperDocumentation.index/* -# /Users/*/Library/Metadata/CoreSpotlight/*/index.spotlightV*/* -# /Users/*/Library/Caches/com.apple.helpd/*/index.spotlightV*/* -# This is optional. Default is false -include_additional = true -``` - -## Collection Options - -- `alt_path` Alternative path to a Spotlight database. This configuration is - **optional** -- `include_additional` Artemis will parse additional known Spotlight database - locations. This configuration is **optional** - -## Output Structure - -An array of `Spotlight` entries - -````typescript -export interface Spotlight { - /**Inode number associated with indexed file */ - inode: bigint; - /**Parent inode associated with indexed file */ - parent_inode: bigint; - /**Flags associated with indexed entry */ - flags: number; - /**Store ID associated with indexed entry */ - store_id: number; - /**Last time Spotlight entry was updated */ - last_updated: string; - /** - * Properties associated with the entry - * - * * Example: - * ``` - * "values": { - "kMDItemContentTypeTree": { - "attribute": "AttrList", - "value": [ - "public.item", - "dyn.ah62d4rv4ge81g75mq34gk55d", - "public.data" - ] - }, - "_kMDItemFileName": { - "attribute": "AttrString", - "value": "arm64e-apple-ios-macabi.swiftdoc" - }, - "kMDItemContentCreationDate_Ranking": { - "attribute": "AttrDate", - "value": [ - 1619308800 - ] - }, - "kMDItemPhysicalSize": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 77824 - ] - }, - "_kMDItemDisplayNameWithExtensions": { - "attribute": "AttrString", - "value": "arm64e-apple-ios-macabi.swiftdoc\u0016\u0002" - }, - "_kMDItemTypeCode": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "_kMDItemCreationDate": { - "attribute": "AttrDate", - "value": [ - 1619320292 - ] - }, - "kMDItemSupportFileType": { - "attribute": "AttrList", - "value": [ - "MDSystemFile" - ] - }, - "_kMDItemFinderLabel": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "kMDItemInterestingDate_Ranking": { - "attribute": "AttrDate", - "value": [ - 1619308800 - ] - }, - "kMDItemKind": { - "attribute": "AttrList", - "value": [ - "Document\u0016\u0002", - "المستند\u0016\u0002ar", - "Document\u0016\u0002ca", - "Dokument\u0016\u0002cs", - "Dokument\u0016\u0002da", - "Dokument\u0016\u0002de", - "Έγγραφο\u0016\u0002el", - "Document\u0016\u0002en", - "Document\u0016\u0002en_AU", - "Document\u0016\u0002en_GB", - "Documento\u0016\u0002es", - "Documento\u0016\u0002es_419", - "Dokumentti\u0016\u0002fi", - "Document\u0016\u0002fr", - "Document\u0016\u0002fr_CA", - "מסמך\u0016\u0002he", - "दस्तावेज़\u0016\u0002hi", - "Dokument\u0016\u0002hr", - "Dokumentum\u0016\u0002hu", - "Dokumen\u0016\u0002id", - "Documento\u0016\u0002it", - "書類\u0016\u0002ja", - "문서\u0016\u0002ko", - "Dokumen\u0016\u0002ms", - "Document\u0016\u0002nl", - "Dokument\u0016\u0002no", - "dokument\u0016\u0002pl", - "Documento\u0016\u0002pt", - "Documento\u0016\u0002pt_PT", - "Document\u0016\u0002ro", - "Документ\u0016\u0002ru", - "Dokument\u0016\u0002sk", - "Dokument\u0016\u0002sv", - "เอกสาร\u0016\u0002th", - "Belge\u0016\u0002tr", - "Документ\u0016\u0002uk", - "Tài liệu\u0016\u0002vi", - "文稿\u0016\u0002zh_CN", - "文件\u0016\u0002zh_HK", - "文件\u0016\u0002zh_TW" - ] - }, - "kMDItemContentCreationDate": { - "attribute": "AttrDate", - "value": [ - 1619320292 - ] - }, - "_kMDItemInterestingDate": { - "attribute": "AttrDate", - "value": [ - 1619320292 - ] - }, - "kMDItemDateAdded": { - "attribute": "AttrDate", - "value": [ - 1642305054 - ] - }, - "_kMDItemContentChangeDate": { - "attribute": "AttrDate", - "value": [ - 1619320292 - ] - }, - "kMDItemDisplayName": { - "attribute": "AttrString", - "value": "arm64e-apple-ios-macabi.swiftdoc\u0016\u0002" - }, - "kMDItemContentModificationDate": { - "attribute": "AttrDate", - "value": [ - 1619320292 - ] - }, - "_kMDItemGroupId": { - "attribute": "AttrVariableSizeInt", - "value": 18 - }, - "kMDItemContentModificationDate_Ranking": { - "attribute": "AttrDate", - "value": [ - 1619308800 - ] - }, - "_kMDItemFinderFlags": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "kMDItemDateAdded_Ranking": { - "attribute": "AttrDate", - "value": [ - 1642291200 - ] - }, - "_kMDItemOwnerGroupID": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "kMDItemContentType": { - "attribute": "AttrList", - "value": "dyn.ah62d4rv4ge81g75mq34gk55d" - }, - "kMDItemDocumentIdentifier": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "kMDItemLogicalSize": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 218460 - ] - }, - "_kMDItemOwnerUserID": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "_kMDItemCreatorCode": { - "attribute": "AttrVariableSizeIntMultiValue", - "value": [ - 0 - ] - }, - "_kMDItemIsExtensionHidden": { - "attribute": "AttrBool", - "value": false - } - } - * ``` - */ - values: Record; - /**Location of the Spotlight database that was parsed */ - directory: string; -} - -/** - * Properties associated with the indexed entry - */ -interface SpotlightProperties { - /** - * Attribute type associated with the entry. - * Possible options are: - * - AttrBool - * - AttrUnknown - * - AttrVariableSizeInt - * - AttrUnknown2 - * - AttrUnknown3 - * - AttrUnknown4 - * - AttrVariableSizeInt2 - * - AttrVariableSizeIntMultiValue - * - AttrByte - * - AttrFloat32 - * - AttrFloat64 - * - AttrString - * - AttrDate (ISO RFC 3339 date) - * - AttrBinary - * - AttrList - * - Unknown - */ - attribute: DataAttribute; - /** - * Data associated with the property. Type can be determined based on the attribute. - * **Important** `value` may also be an array containing data associated with the `attribute` - */ - value: unknown; -} - -/** - * All possible Attribute types - */ -enum DataAttribute { - /**Boolean value */ - AttrBool = "AttrBool", - /**This attribute is undocumented */ - AttrUnknown = "AttrUnknown", - /**A value of variable size */ - AttrVariableSizeInt = "AttrVariableSizeInt", - /**This attribute is undocumented */ - AttrUnknown2 = "AttrUnknown2", - /**This attribute is undocumented */ - AttrUnknown3 = "AttrUnknown3", - /**This attribute is undocumented */ - AttrUnknown4 = "AttrUnknown4", - /**A value of variable size */ - AttrVariableSizeInt2 = "AttrVariableSizeInt2", - /**A value of variable size (it may be in an array) */ - AttrVariableSizeIntMultiValue = "AttrVariableSizeIntMultiValue", - /**A value with a size of one byte */ - AttrByte = "AttrByte", - /**A 32-bit float value */ - AttrFloat32 = "AttrFloat32", - /**A 64-bit float value */ - AttrFloat64 = "AttrFloat64", - /**A string value */ - AttrString = "AttrString", - /**A date value in ISO RFC 3339 format */ - AttrDate = "AttrDate", - /**Base64 encoded binary value. (Depending on the property this may actually be a normal string value (similar to `AttrString`) */ - AttrBinary = "AttrBinary", - /**An array of values */ - AttrList = "AttrList", - /**Artemis failed to determine the attribute */ - Unknown = "Unknown", -} -```` +--- +description: The macOS Spotlight database +keywords: + - macOS + - file meta + - binary +--- + +# Spotlight + +macOS Spotlight is an indexing service for tracking files and content. The +Spotlight database can contain a huge amount of metadata associated with the +indexed content such as: + +- Timestamps +- Partial file content +- File type and much more + +The primary database is typically located at the root of the macOS Data volume.\ +ex: /System/Volumes/Data/.Spotlight-V100/Store-V3/123-445566-778-12384/ + +By default artemis will only attempt to parse the Spotlight database located at +the Data volume + +However, additional databases can also be found on the macOS system. Known +additional locations are: + +- **/Users/\*/Library/Caches/com.apple.helpd/index.spotlightV\*** +- **/Users/\*/Library/Metadata/CoreSpotlight/index.spotlightV\*** +- **/Users/\*/Library/Developer/Xcode/DocumentationCache/\*/\*DeveloperDocumentation.index/** +- **/Users/\*/Library/Metadata/CoreSpotlight/\*/index.spotlightV\*** +- **/Users/\*/Library/Caches/com.apple.helpd/\*/index.spotlightV\*** + +Similar to the filelisting artifact, every 10k entries artemis will output the +data and continue. + +Other Parsers: + +- [spotlight_parser](https://github.com/ydkhatri/spotlight_parser) +- [mac_apt](https://github.com/ydkhatri/mac_apt) + +References: + +- [Forensic and Security](https://forensicsandsecurity.com/papers/SpotlightMacForensicsSlides.pdf) +- [Spotlight](https://en.wikipedia.org/wiki/Spotlight_(Apple)) +- [libyal](https://github.com/libyal/dtformats/blob/main/documentation/Apple%20Spotlight%20store%20database%20file%20format.asciidoc) + +## TOML Collection + +```toml +[output] +name = "spotlight_collection" +directory = "./tmp" +format = "jsonl" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "spotlight" +[artifacts.spotlight] +# Optional +# alt_dir = "" + +# Include additional known Spotlight paths such as +# /Users/*/Library/Caches/com.apple.helpd/index.spotlightV*/* +# /Users/*/Library/Metadata/CoreSpotlight/index.spotlightV*/* +# /Users/*/Library/Developer/Xcode/DocumentationCache/*/*/DeveloperDocumentation.index/* +# /Users/*/Library/Metadata/CoreSpotlight/*/index.spotlightV*/* +# /Users/*/Library/Caches/com.apple.helpd/*/index.spotlightV*/* +# This is optional. Default is false +include_additional = true +``` + +## Collection Options + +- `alt_dir` Alternative path to a Spotlight database. This configuration is + **optional** +- `include_additional` Artemis will parse additional known Spotlight database + locations. This configuration is **optional** + +## Output Structure + +An array of `Spotlight` entries + +````typescript +export interface Spotlight { + /**Inode number associated with indexed file */ + inode: bigint; + /**Parent inode associated with indexed file */ + parent_inode: bigint; + /**Flags associated with indexed entry */ + flags: number; + /**Store ID associated with indexed entry */ + store_id: number; + /**Last time Spotlight entry was updated */ + last_updated: string; + /** + * Properties associated with the entry + * + * * Example: + * ``` + * "values": { + "kMDItemContentTypeTree": { + "attribute": "AttrList", + "value": [ + "public.item", + "dyn.ah62d4rv4ge81g75mq34gk55d", + "public.data" + ] + }, + "_kMDItemFileName": { + "attribute": "AttrString", + "value": "arm64e-apple-ios-macabi.swiftdoc" + }, + "kMDItemContentCreationDate_Ranking": { + "attribute": "AttrDate", + "value": [ + 1619308800 + ] + }, + "kMDItemPhysicalSize": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 77824 + ] + }, + "_kMDItemDisplayNameWithExtensions": { + "attribute": "AttrString", + "value": "arm64e-apple-ios-macabi.swiftdoc\u0016\u0002" + }, + "_kMDItemTypeCode": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "_kMDItemCreationDate": { + "attribute": "AttrDate", + "value": [ + 1619320292 + ] + }, + "kMDItemSupportFileType": { + "attribute": "AttrList", + "value": [ + "MDSystemFile" + ] + }, + "_kMDItemFinderLabel": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "kMDItemInterestingDate_Ranking": { + "attribute": "AttrDate", + "value": [ + 1619308800 + ] + }, + "kMDItemKind": { + "attribute": "AttrList", + "value": [ + "Document\u0016\u0002", + "المستند\u0016\u0002ar", + "Document\u0016\u0002ca", + "Dokument\u0016\u0002cs", + "Dokument\u0016\u0002da", + "Dokument\u0016\u0002de", + "Έγγραφο\u0016\u0002el", + "Document\u0016\u0002en", + "Document\u0016\u0002en_AU", + "Document\u0016\u0002en_GB", + "Documento\u0016\u0002es", + "Documento\u0016\u0002es_419", + "Dokumentti\u0016\u0002fi", + "Document\u0016\u0002fr", + "Document\u0016\u0002fr_CA", + "מסמך\u0016\u0002he", + "दस्तावेज़\u0016\u0002hi", + "Dokument\u0016\u0002hr", + "Dokumentum\u0016\u0002hu", + "Dokumen\u0016\u0002id", + "Documento\u0016\u0002it", + "書類\u0016\u0002ja", + "문서\u0016\u0002ko", + "Dokumen\u0016\u0002ms", + "Document\u0016\u0002nl", + "Dokument\u0016\u0002no", + "dokument\u0016\u0002pl", + "Documento\u0016\u0002pt", + "Documento\u0016\u0002pt_PT", + "Document\u0016\u0002ro", + "Документ\u0016\u0002ru", + "Dokument\u0016\u0002sk", + "Dokument\u0016\u0002sv", + "เอกสาร\u0016\u0002th", + "Belge\u0016\u0002tr", + "Документ\u0016\u0002uk", + "Tài liệu\u0016\u0002vi", + "文稿\u0016\u0002zh_CN", + "文件\u0016\u0002zh_HK", + "文件\u0016\u0002zh_TW" + ] + }, + "kMDItemContentCreationDate": { + "attribute": "AttrDate", + "value": [ + 1619320292 + ] + }, + "_kMDItemInterestingDate": { + "attribute": "AttrDate", + "value": [ + 1619320292 + ] + }, + "kMDItemDateAdded": { + "attribute": "AttrDate", + "value": [ + 1642305054 + ] + }, + "_kMDItemContentChangeDate": { + "attribute": "AttrDate", + "value": [ + 1619320292 + ] + }, + "kMDItemDisplayName": { + "attribute": "AttrString", + "value": "arm64e-apple-ios-macabi.swiftdoc\u0016\u0002" + }, + "kMDItemContentModificationDate": { + "attribute": "AttrDate", + "value": [ + 1619320292 + ] + }, + "_kMDItemGroupId": { + "attribute": "AttrVariableSizeInt", + "value": 18 + }, + "kMDItemContentModificationDate_Ranking": { + "attribute": "AttrDate", + "value": [ + 1619308800 + ] + }, + "_kMDItemFinderFlags": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "kMDItemDateAdded_Ranking": { + "attribute": "AttrDate", + "value": [ + 1642291200 + ] + }, + "_kMDItemOwnerGroupID": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "kMDItemContentType": { + "attribute": "AttrList", + "value": "dyn.ah62d4rv4ge81g75mq34gk55d" + }, + "kMDItemDocumentIdentifier": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "kMDItemLogicalSize": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 218460 + ] + }, + "_kMDItemOwnerUserID": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "_kMDItemCreatorCode": { + "attribute": "AttrVariableSizeIntMultiValue", + "value": [ + 0 + ] + }, + "_kMDItemIsExtensionHidden": { + "attribute": "AttrBool", + "value": false + } + } + * ``` + */ + values: Record; + /**Location of the Spotlight database that was parsed */ + evidence: string; +} + +/** + * Properties associated with the indexed entry + */ +interface SpotlightProperties { + /** + * Attribute type associated with the entry. + * Possible options are: + * - AttrBool + * - AttrUnknown + * - AttrVariableSizeInt + * - AttrUnknown2 + * - AttrUnknown3 + * - AttrUnknown4 + * - AttrVariableSizeInt2 + * - AttrVariableSizeIntMultiValue + * - AttrByte + * - AttrFloat32 + * - AttrFloat64 + * - AttrString + * - AttrDate (ISO RFC 3339 date) + * - AttrBinary + * - AttrList + * - Unknown + */ + attribute: DataAttribute; + /** + * Data associated with the property. Type can be determined based on the attribute. + * **Important** `value` may also be an array containing data associated with the `attribute` + */ + value: unknown; +} + +/** + * All possible Attribute types + */ +enum DataAttribute { + /**Boolean value */ + AttrBool = "AttrBool", + /**This attribute is undocumented */ + AttrUnknown = "AttrUnknown", + /**A value of variable size */ + AttrVariableSizeInt = "AttrVariableSizeInt", + /**This attribute is undocumented */ + AttrUnknown2 = "AttrUnknown2", + /**This attribute is undocumented */ + AttrUnknown3 = "AttrUnknown3", + /**This attribute is undocumented */ + AttrUnknown4 = "AttrUnknown4", + /**A value of variable size */ + AttrVariableSizeInt2 = "AttrVariableSizeInt2", + /**A value of variable size (it may be in an array) */ + AttrVariableSizeIntMultiValue = "AttrVariableSizeIntMultiValue", + /**A value with a size of one byte */ + AttrByte = "AttrByte", + /**A 32-bit float value */ + AttrFloat32 = "AttrFloat32", + /**A 64-bit float value */ + AttrFloat64 = "AttrFloat64", + /**A string value */ + AttrString = "AttrString", + /**A date value in ISO RFC 3339 format */ + AttrDate = "AttrDate", + /**Base64 encoded binary value. (Depending on the property this may actually be a normal string value (similar to `AttrString`) */ + AttrBinary = "AttrBinary", + /**An array of values */ + AttrList = "AttrList", + /**Artemis failed to determine the attribute */ + Unknown = "Unknown", +} +```` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/unifiedlogs.md b/artemis-docs/docs/Artifacts/macOS Artifacts/unifiedlogs.md index 4da941fe..88d3c9b7 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/unifiedlogs.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/unifiedlogs.md @@ -1,109 +1,111 @@ ---- -description: Primary source of logs on macOS -keywords: - - macOS - - logs - - binary ---- - -# UnifiedLogs - -macOS Unified Logs are the primary files associated with logging system -activity. They are stored in a binary format at **/var/db/diagnostics/**. - -Other Parsers: - -- [UnifiedLogReader](https://github.com/ydkhatri/UnifiedLogReader) (Only partial - support) - -References: - -- [Unified Logging](https://eclecticlight.co/2018/03/19/macos-unified-log-1-why-what-and-how/) -- [Reviewing logs](https://www.mandiant.com/resources/blog/reviewing-macos-unified-logs) -- [Reviewing more logs](https://www.crowdstrike.com/blog/how-to-leverage-apple-unified-log-for-incident-response/) - -## TOML Collection - -```toml -[output] -name = "unifiedlogs_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "unifiedlogs" -[artifacts.unifiedlogs] -sources = ["Special"] -# Optional -# logarchive_path = "" -``` - -## Collection Options - -- `sources` List of directories that should be included when parsing the - `unifiedlogs` These directories are found at **/var/db/diagnostics/**. Only the - following directories contain logs: - - Persist - - Special - - Signpost - - HighVolume -- `logarchive_path` Alternative path to a logarchive formatted directory. This - configuration is **optional** - -To parse all logs you would use -`sources = ["Special", "Persist", "Signpost", "HighVolume"]` - -## Output Structure - -An array of `UnifiedLog` entries - -```typescript -export interface UnifiedLog { - /**Subsystem used by the log entry */ - subsystem: string; - /**Library associated with the log entry */ - library: string; - /**Log entry category */ - category: string; - /**Process ID associated with log entry */ - pid: number; - /**Effective user ID associated with log entry */ - euid: number; - /**Thread ID associated with log entry */ - thread_id: number; - /**Activity ID associated with log entry */ - activity_id: number; - /**UUID of library associated with the log entry */ - library_uuid: string; - /**UNIXEPOCH timestamp of log entry in nanoseconds */ - time: number; - /**ISO RFC 3339 timestamp with nanosecond precision */ - timestamp: string; - /**Log entry event type */ - event_type: string; - /**Log entry log type */ - log_type: string; - /**Process associated with log entry */ - process: string; - /**UUID of process associated with log entry */ - process_uuid: string; - /**Raw string message associated with log entry*/ - raw_message: string; - /**Boot UUID associated with log entry */ - boot_uuid: string; - /**Timezone associated with log entry */ - timezone_name: string; - /**Strings associated with the log entry */ - mesage_entries: Record; - /** - * Resolved message entry associated log entry. - * Merge of `raw_message` and `message_entries` - */ - message: string; -} -``` +--- +description: Primary source of logs on macOS +keywords: + - macOS + - logs + - binary +--- + +# UnifiedLogs + +macOS Unified Logs are the primary files associated with logging system +activity. They are stored in a binary format at **/var/db/diagnostics/**. + +Other Parsers: + +- [UnifiedLogReader](https://github.com/ydkhatri/UnifiedLogReader) (Only partial + support) + +References: + +- [Unified Logging](https://eclecticlight.co/2018/03/19/macos-unified-log-1-why-what-and-how/) +- [Reviewing logs](https://www.mandiant.com/resources/blog/reviewing-macos-unified-logs) +- [Reviewing more logs](https://www.crowdstrike.com/blog/how-to-leverage-apple-unified-log-for-incident-response/) + +## TOML Collection + +```toml +[output] +name = "unifiedlogs_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "unifiedlogs" +[artifacts.unifiedlogs] +sources = ["Special"] +# Optional +# logarchive_path = "" +``` + +## Collection Options + +- `sources` List of directories that should be included when parsing the + `unifiedlogs` These directories are found at **/var/db/diagnostics/**. Only the + following directories contain logs: + - Persist + - Special + - Signpost + - HighVolume +- `logarchive_path` Alternative path to a logarchive formatted directory. This + configuration is **optional** + +To parse all logs you would use +`sources = ["Special", "Persist", "Signpost", "HighVolume"]` + +## Output Structure + +An array of `UnifiedLog` entries + +```typescript +export interface UnifiedLog { + /**Subsystem used by the log entry */ + subsystem: string; + /**Library associated with the log entry */ + library: string; + /**Log entry category */ + category: string; + /**Process ID associated with log entry */ + pid: number; + /**Effective user ID associated with log entry */ + euid: number; + /**Thread ID associated with log entry */ + thread_id: number; + /**Activity ID associated with log entry */ + activity_id: number; + /**UUID of library associated with the log entry */ + library_uuid: string; + /**UNIXEPOCH timestamp of log entry in nanoseconds */ + time: number; + /**ISO RFC 3339 timestamp with nanosecond precision */ + timestamp: string; + /**Log entry event type */ + event_type: string; + /**Log entry log type */ + log_type: string; + /**Process associated with log entry */ + process: string; + /**UUID of process associated with log entry */ + process_uuid: string; + /**Raw string message associated with log entry*/ + raw_message: string; + /**Boot UUID associated with log entry */ + boot_uuid: string; + /**Timezone associated with log entry */ + timezone_name: string; + /**Strings associated with the log entry */ + mesage_entries: Record; + /** + * Resolved message entry associated log entry. + * Merge of `raw_message` and `message_entries` + */ + message: string; + /**Path to the UnifiedLog file */ + evidence: string; +} +``` diff --git a/artemis-docs/docs/Artifacts/macOS Artifacts/users.md b/artemis-docs/docs/Artifacts/macOS Artifacts/users.md index 46354e8a..e6223fea 100644 --- a/artemis-docs/docs/Artifacts/macOS Artifacts/users.md +++ b/artemis-docs/docs/Artifacts/macOS Artifacts/users.md @@ -1,76 +1,78 @@ ---- -description: macOS users -keywords: - - macos - - accounts - - plist ---- - -# Users - -Gets user info parsing the plist files at -**/var/db/dslocal/nodes/Default/users**. - -Other Parsers: - -- Any tool that can parse a plist file - -References: - -- N/A - -## TOML Collection - -```toml -[output] -name = "users_collection" -directory = "./tmp" -format = "json" -compress = false -endpoint_id = "abdc" -collection_id = 1 -output = "local" -timeline = false - -[[artifacts]] -artifact_name = "user-macos" -[artifacts.users] -# Optional -# alt_path = "" -``` - -## Collection Options - -- `alt_path` Use an alternative Users path. This configuration is **optional**. - By default artemis will read all Users at - `/var/db/dslocal/nodes/Default/users` - -## Output Structure - -An array of `Users` entries - -```typescript -export interface Users { - /**UID for the user */ - uid: string[]; - /**GID associated with the user */ - gid: string[]; - /**User name */ - name: string[]; - /**Real name associated with user */ - real_name: string[]; - /**Base64 encoded photo associated with user */ - account_photo: string[]; - /**Timestamp the user was created */ - account_created: string; - /**Password last changed for the user */ - password_last_set: string; - /**Shell associated with the user */ - shell: string[]; - /**Unlock associated with the user */ - unlock_options: string[]; - /**Home path associated with user */ - home_path: string[]; - /**UUID associated with user */ - uuid: string[]; -``` +--- +description: macOS users +keywords: + - macos + - accounts + - plist +--- + +# Users + +Gets user info parsing the plist files at +**/var/db/dslocal/nodes/Default/users**. + +Other Parsers: + +- Any tool that can parse a plist file + +References: + +- N/A + +## TOML Collection + +```toml +[output] +name = "users_collection" +directory = "./tmp" +format = "json" +compress = false +endpoint_id = "abdc" +collection_id = 1 +output = "local" +timeline = false + +[[artifacts]] +artifact_name = "user-macos" +[artifacts.users] +# Optional +# alt_dir = "" +``` + +## Collection Options + +- `alt_dir` Use an alternative Users path. This configuration is **optional**. + By default artemis will read all Users at + `/var/db/dslocal/nodes/Default/users` + +## Output Structure + +An array of `Users` entries + +```typescript +export interface Users { + /**UID for the user */ + uid: string[]; + /**GID associated with the user */ + gid: string[]; + /**User name */ + name: string[]; + /**Real name associated with user */ + real_name: string[]; + /**Base64 encoded photo associated with user */ + account_photo: string[]; + /**Timestamp the user was created */ + account_created: string; + /**Password last changed for the user */ + password_last_set: string; + /**Shell associated with the user */ + shell: string[]; + /**Unlock associated with the user */ + unlock_options: string[]; + /**Home path associated with user */ + home_path: string[]; + /**UUID associated with user */ + uuid: string[]; + /**Path to the Users plist file */ + evidence: string; +``` diff --git a/artemis-docs/docs/Artifacts/overview.md b/artemis-docs/docs/Artifacts/overview.md index 01627a3e..94418952 100644 --- a/artemis-docs/docs/Artifacts/overview.md +++ b/artemis-docs/docs/Artifacts/overview.md @@ -15,11 +15,12 @@ A breakdown of artifacts by OS is below. | OS | Number of Artifacts | | --------------------------------- | ------------------- | -| [Windows](./windows.md) | 49 | -| [macOS](./macos.md) | 44 | +| [Windows](./windows.md) | 54 | +| [macOS](./macos.md) | 45 | | [Linux](./linux.md) | 23 | | [FreeBSD](./freebsd.md) | 8 | -| [Applications](./applications.md) | 15 | +| [ESXi](./esxi.md) | 7 | +| [Applications](./applications.md) | 16 | Artemis also supports parsing apps and artifacts from unencrypted iTunes backups diff --git a/artemis-docs/docs/Contributing/adding.md b/artemis-docs/docs/Contributing/adding.md index 18608f82..96f3f348 100644 --- a/artemis-docs/docs/Contributing/adding.md +++ b/artemis-docs/docs/Contributing/adding.md @@ -1,30 +1,30 @@ ---- -sidebar_position: 3 ---- - -# Adding a Feature - -Before working on a new feature for artemis please make sure you have read the -[Contributing](https://github.com/puffycid/artemis/blob/main/CONTRIBUTING.md) -document. Most important thing is to first create an issue! Basic overview of -adding a new feature: - -1. Create an issue. If you want to work on it, make sure to explicitly - volunteer! -2. Create a branch on your clone artemis repo -3. Work on feature -4. Ensure tests are made for all functions -5. If you are adding a new artifact, add an integration test -6. Run `cargo clippy`. -7. Run `cargo fmt` -8. Open a pull request! - -## Other Useful Development Tools - -List of useful tools that could aid in development. - -- [Audit](https://github.com/RustSec/rustsec/tree/main/cargo-audit) -- [scc](https://github.com/boyter/scc) -- [clippy](https://github.com/rust-lang/rust-clippy) -- [nextest](https://nexte.st/) -- [just](https://github.com/casey/just) +--- +sidebar_position: 3 +--- + +# New Features + +Before working on a new feature for artemis please make sure you have read the +[Contributing](https://github.com/puffycid/artemis/blob/main/CONTRIBUTING.md) +document. Most important thing is to first create an issue! Basic overview of +adding a new feature: + +1. Create an issue. If you want to work on it, make sure to explicitly + volunteer! +2. Create a branch on your clone artemis repo +3. Work on feature +4. Ensure tests are made for all functions +5. If you are adding a new artifact, add an integration test +6. Run `cargo clippy`. +7. Run `cargo fmt` +8. Open a pull request! + +## Other Useful Development Tools + +List of useful tools that could aid in development. + +- [Audit](https://github.com/RustSec/rustsec/tree/main/cargo-audit) +- [scc](https://github.com/boyter/scc) +- [clippy](https://github.com/rust-lang/rust-clippy) +- [nextest](https://nexte.st/) +- [just](https://github.com/casey/just) diff --git a/artemis-docs/docs/Contributing/api.md b/artemis-docs/docs/Contributing/api.md index 9de8c537..190f72ae 100644 --- a/artemis-docs/docs/Contributing/api.md +++ b/artemis-docs/docs/Contributing/api.md @@ -1,78 +1,82 @@ ---- -sidebar_position: 6 ---- - -# API Contributions - -Contributing to the artemis-api is slightly different than contributing directly -to artemis. The biggest difference is the API is coded in TypeScript instead of -Rust. - -This can make contributions significantly easier if interested in contributing -to artemis. - -The Prerequisites for adding API features are the same as creating artemis -scripts as mentioned in [scripting](../Intro/Scripting/boa.md). You will need: - -- A text editor that supports TypeScript - -## Adding a Feature - -Please try to create an issue before working on a feature. Basic overview of -adding a new feature: - -1. Create an issue. If you want to work on it, make sure to explicitly - volunteer! -2. Create a branch on your clone artemis repo -3. Work on feature -4. If you are adding a new artifact make sure you have updated the artemis docs -5. Open a pull request! - -Please checkout available [API](../API/overview.md) functions that can be used -to make scripting easier. - -## Artifact Scope - -Unlike artemis, the API does not have strict limits on what can be included. You -may include non-forensic related artifacts or features such as: - -- WiFi information -- Installed applications -- Generic system information -- Shelling out to other tools or applications (ex: You may execute PowerShell - commands from the API if you want to) -- Submit data to network services (ex: Submit hashes to VirusTotal API) - -## Testing the API - -Writing tests for the TypeScript API is a bit more involved than writing tests for the Rust codebase. Creating tests for the API is a 5 step process: - -0. Compile the test runner binary `script_runner` via `cargo build --release --examples` and place under the corresponding test folder -1. Export a test function -2. Register the test function in test.ts under tests/test.ts -3. Write your test and place it under the test/ folder -4. Run tests with compile_tests.ps1 or compile_tests.py - -You may place test data under test/test_data. - -### Example API Test - -The [rpm](../Artifacts/Linux%20Artifacts/rpm.md) API exposes the test function `testRpmInfo` which calls all of the RPM parsing functions and validates they return correct data. - -This test function is then registered in test/test.ts and used for running RPM tests from tests/linux/rpm/main.ts. - -```typescript -import { testRpmInfo } from "../../test"; - -function main() { - console.log('Running RPM tests....'); - - console.log(' Starting RPM info test....'); - testRpmInfo(); - - console.log(' RPM info test passed! 🥳'); - console.log('All RPM tests passed! 🥳💃🕺'); -} - -main(); -``` +--- +sidebar_position: 6 +--- + +# API Contributions + +Contributing to the artemis-api is slightly different than contributing directly +to artemis. The biggest difference is the API is coded in TypeScript instead of +Rust. + +This can make contributions significantly easier if interested in contributing +to artemis. + +The Prerequisites for adding API features are the same as creating artemis +scripts as mentioned in [scripting](../Intro/Scripting/boa.md). You will need: + +- A text editor that supports TypeScript + +## Adding a Feature + +Please try to create an issue before working on a feature. Basic overview of +adding a new feature: + +1. Create an issue. If you want to work on it, make sure to explicitly + volunteer! +2. Create a branch on your clone artemis repo +3. Work on feature +4. If you are adding a new artifact make sure you have updated the artemis docs +5. Open a pull request! + +Please checkout available [API](../API/overview.md) functions that can be used +to make scripting easier. + +## Artifact Scope + +Unlike artemis, the API does not have strict limits on what can be included. You +may include non-forensic related artifacts or features such as: + +- WiFi information +- Installed applications +- Generic system information +- Shelling out to other tools or applications (ex: You may execute PowerShell + commands from the API if you want to) +- Submit data to network services (ex: Submit hashes to VirusTotal API) + +## Testing the API + +Writing tests for the TypeScript API is a bit more involved than writing tests for the Rust codebase. You will also need the [artemis](https://github.com/puffyCid/artemis) source code. + +1. Clone the artemis repo +2. Compile the API test runner binary `script_runner` via `cargo build --release --examples` and place `script_runner` under the corresponding API test folder +3. Export a test function +4. Register the test function in test.ts under tests/test.ts +5. Write your test and place it under the test/ folder +6. Run tests with compile_tests.ps1 or compile_tests.py + +You may place test data under test/test_data. + +### Example API Test + +The [rpm](../Artifacts/Linux%20Artifacts/rpm.md) API exposes the test function `testRpmInfo` which calls all of the RPM parsing functions and validates they return correct data. + +This test function is then registered in test/test.ts and used for running RPM tests from tests/linux/rpm/main.ts. + +```typescript +import { testRpmInfo } from "../../test"; + +function main() { + console.log('Running RPM tests....'); + + console.log(' Starting RPM info test....'); + testRpmInfo(); + + console.log(' RPM info test passed! 🥳'); + console.log('All RPM tests passed! 🥳💃🕺'); +} + +main(); +``` + + +All PR's against the [artemis-api](https://github.com/puffyCid/artemis-api) repo run tests. \ No newline at end of file diff --git a/artemis-docs/docs/Contributing/building.md b/artemis-docs/docs/Contributing/building.md index c2128a5d..887cd91e 100644 --- a/artemis-docs/docs/Contributing/building.md +++ b/artemis-docs/docs/Contributing/building.md @@ -86,6 +86,7 @@ Available recipes: build # Build the entire artemis project. complex # Review complexity with scc default # Run cargo clippy on artemis project + end2end # Run End to End tests filesystem # Test only the FileSystem functions nextest # Test the entire artemis project using nextest runtime # Test only the JavaScript runtime @@ -118,6 +119,7 @@ Available recipes: msi # Package Artemis into Windows MSI installer file pkg team_id version profile # Package Artemis into macOS PKG installer file rpm # Package Artemis into RPM file + vib # Package Artemis into ESXi VIB file [setup] setup-fedora # Setup Artemis development environment for Fedora @@ -128,8 +130,8 @@ Available recipes: [workspace] cli # Just build the artemis binary forensics # Just build the forensics library + profile # Just build the artemis binary and enable profiling slim # Just build the artemis binary. But do not enable Yara-X - ``` ### Building for esoteric platforms @@ -144,4 +146,19 @@ Artemis can be compiled for a variety of platforms using [cross](https://github. If you want to build for Android or NetBSD you have to disable the yara-x dependency: - cross build --release --no-default-features -If you want to build for Windows ARM. You will need a Windows ARM VM or device and will need to install [LLVM](https://learn.arm.com/install-guides/llvm-woa/) and cmake. \ No newline at end of file +If you want to build for Windows ARM. You will need a Windows ARM VM or device and will need to install [LLVM](https://learn.arm.com/install-guides/llvm-woa/) and cmake. + + +### Building for ESXi + +If you want to compile artemis for ESXi you will need [cross](https://github.com/cross-rs/cross) and [just](https://github.com/casey/just). + +1. Run `just vib` and you will get a vSphere Installation Bundles (VIB) package + +:::warning + +Unsigned 3rd party binaries are discouraged on ESXi appliances. + +[UAC](https://github.com/tclahr/uac) is recommended if you want to collect data without using a 3rd party binary. + +::: \ No newline at end of file diff --git a/artemis-docs/docs/Contributing/overview.md b/artemis-docs/docs/Contributing/overview.md index 03cb133a..f6325809 100644 --- a/artemis-docs/docs/Contributing/overview.md +++ b/artemis-docs/docs/Contributing/overview.md @@ -1,148 +1,154 @@ ---- -sidebar_position: 2 ---- - -# Getting Started - -The artemis source code is about ~88k lines of Rust code across ~690 files as of -December 2025 (this includes tests). However its organized in a pretty simple -structure. - -:::tip - -Use the just command `just complex` to measure lines of Rust and complexity!\ -(requires [scc](https://github.com/boyter/scc)) - -::: - -From the root of the artemis repo: - -- `forensics/` workspace contains the library component of artemis. The bulk of the - code is located here -- `cli/` workspace contains the executable component artemis. -- `timeline/` workspace contains functions to timeline artifacts - -From the `forensics/src/` directory - -| Directory | Description | -| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| artifacts | Contains the code related to parsing forensic artifacts.
It is broken down by OS and application artifacts | -| filesystem | Contains code to help interact with the filesystem. It contains helper functions that can be used when adding new artifacts/features.
Ex: reading/hashing files, getting file timestamps, listing files, etc | -| output | Contains code related to outputting parsed data | -| runtime | Contains code related to the embedded JS runtime | -| structs | Contains code related to how TOML collection files are parsed. It tells artemis how to interpret TOML collections. | -| utils | Contains code related to help parse artifacts and provide other features to artemis.
Ex: Decompress/compress data, get environment variables,create a Regex expression, extract strings, convert timestamps, etc | -| core.rs | Contains the entry point to the **forensis** workspace. | - - -## Adding New Artifacts - -To keep the codebase organized the follow should be followed when adding a new -artifact. - -- Artifacts have their own subfolder. Ex: `src/artifacts/os/windows/prefetch` -- The subfolder will probably have the following files at minimum: - - parser.rs - Contains `pub(crate)` accessible functions for the artifact - - error.rs - Artifact specific errors - -## Timestamps - -All timestamps artemis outputs are in ISO RFC 3339 format -(YYYY-MM-DDTHH:mm:ss.SSSZ). The timestamp from should be from UNIXEPOCH time. - -If your new artifact has a timestamp, you will need to make sure the timestamp -is in YYYY-MM-DDTHH:mm:ss.SSSZ format. Though exceptions may be allowed if -needed, these exceptions will only be for the duration (ex: seconds vs -nanoseconds). - -No other time formats such as Windows FILETIME, FATTIME, Chromium time, etc are -allowed. - -:::tip - -Use the time functions under **utils** to help with timestamp conversions! - -::: - -## Artifact Scope - -Currently all artifacts that artemis parses are statically coded in the binary -(they are written in Rust). While this ok, it prevents us from dynamically -updating the parser if the artifact format changes (ex: new Windows release). - -Currently the [JS runtime](../Intro/Scripting/boa.md) has minimal support for -creating parsers. If you are interested in adding a small parser to artemis, it -could be worth first trying to code it using the JS runtime. - -An simple JS parser can be found in the -[artemis API](https://github.com/puffyCid/artemis-api/blob/main/src/images/icns.ts) -repo. - -However, if you want to implement a new parser for parsing common Windows -artifacts such as *Jumplists* then that is definitely something that could be -worth including as a Rust coded parser. - -When in doubt or unsure open an issue! - -## Suggestions - -If you want add a new artifact but want to see how other artifacts are -implemented, some suggested ones to review are: - -- `UserAssist`: If you want to add a new Registry artifact. The UserAssist - artifact is less than 300 lines (not counting tests). And includes: - - Parsing binary data - - Converting timestamps - - Collecting user Registry data -- `FsEvents`: If you want to to parse a binary file. The FsEvents is less than - 300 lines (not counting tests). And includes: - - - Parsing binary data - - Decompressing data - - Getting data flags - -:::info - -FsEvents is the first artifact created for artemis. Its the oldest code in the project! - -::: - -## Useful Helper Functions - -The artemis codebase contains a handful of artifacts (ex: `Registry`) that -expose helper functions that allow other artifacts to reuse parts of that -artifact to help get artifact specific data. - -For example the Windows Registry artifact exposes a helper function that other -Registry based artifacts can leverage to help parse the Registry: - -- `pub(crate) fn get_registry_keys(start_path: &str, regex: &Regex, file_path: &str)` - will read a Registry file at provided file_path and filter to based on - start_path and regex. If start_path and regex are empty a full Registry - listing is returned. All Regex comparisons are done in lowercase. - -Some other examples listed below: - -- `/filesystem` contains code to help interact with the filesystem. - - - `pub(crate) fn list_files(path: &str)` returns list of files - - `pub(crate) fn read_file(path: &str)` reads a file - - `pub(crate) fn hash_file(hashes: &Hashes, path: &str)` hashes a file based - on selected hashes (MD5, SHA1, SHA256) - -- `/filesystem/ntfs` contains code to help interact with the raw NTFS - filesystem. It lets us bypass locked files. This is only available on Windows - - - `pub(crate) fn raw_read_file(path: &str)` reads a file. Will bypass file - locks - - `pub(crate) fn read_attribute(path: &str, attribute: &str)` can read an - Alternative Data Stream (ADS) - - `pub(crate) fn get_user_registry_files(drive: &char)` returns a Vector that - contains references to all user Registry files (NTUSER.DAT and - UsrClass.dat). It does **not** read the files, it just provides all the data - needed to start reading them. - -- `/src/artifacts/os/macos/plist/property_list.rs` contains code help parse - plist files. - - `pub(crate) fn parse_plist_file(path: &str)` will parse a plist file and - return it as a serde Value +--- +sidebar_position: 2 +--- + +# Getting Started + +The artemis source code is about ~88k lines of Rust code across ~690 files as of +December 2025 (this includes tests). However its organized in a pretty simple +structure. + +:::tip + +Use the just command `just complex` to measure lines of Rust and complexity!\ +(requires [scc](https://github.com/boyter/scc)) + +::: + +From the root of the artemis repo: + +- `forensics/` workspace contains the library component of artemis. The bulk of the + code is located here +- `cli/` workspace contains the executable component artemis. +- `timeline/` workspace contains functions to timeline artifacts + +From the `forensics/src/` directory + +| Directory | Description | +| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| artifacts | Contains the code related to parsing forensic artifacts.
It is broken down by OS and application artifacts | +| filesystem | Contains code to help interact with the filesystem. It contains helper functions that can be used when adding new artifacts/features.
Ex: reading/hashing files, getting file timestamps, listing files, etc | +| output | Contains code related to outputting parsed data | +| runtime | Contains code related to the embedded JS runtime | +| structs | Contains code related to how TOML collection files are parsed. It tells artemis how to interpret TOML collections. | +| utils | Contains code related to help parse artifacts and provide other features to artemis.
Ex: Decompress/compress data, get environment variables,create a Regex expression, extract strings, convert timestamps, etc | +| core.rs | Contains the entry point to the **forensics** workspace. | + + +## Adding New Artifacts + +To keep the codebase organized the follow should be followed when adding a new +artifact. + +- Artifacts have their own subfolder. Ex: `src/artifacts/os/windows/prefetch` +- The subfolder will probably have the following files at minimum: + - parser.rs - Contains `pub(crate)` accessible functions for the artifact + - error.rs - Artifact specific errors + +## Timestamps + +All timestamps artemis outputs are in ISO RFC 3339 format +(YYYY-MM-DDTHH:mm:ss.SSSZ). The timestamp from should be from UNIXEPOCH time. + +If your new artifact has a timestamp, you will need to make sure the timestamp +is in YYYY-MM-DDTHH:mm:ss.SSSZ format. Though exceptions may be allowed if +needed, these exceptions will only be for the duration (ex: seconds vs +nanoseconds). + +No other time formats such as Windows FILETIME, FATTIME, Chromium time, etc are +allowed. + +:::tip + +Use the time functions under **utils** to help with timestamp conversions! + +::: + +## Evidence Field + +All artifacts should include an evidence field that points to the full path of file or folder that is the source of the artifact. + +For example, the evidence field for the Amcache artifact would be the full path of the Amcache.hve file. + +## Artifact Scope + +Currently all artifacts that artemis parses are statically coded in the binary +(they are written in Rust). While this ok, it prevents us from dynamically +updating the parser if the artifact format changes (ex: new Windows release). + +Currently the [JS runtime](../Intro/Scripting/boa.md) has minimal support for +creating parsers. If you are interested in adding a small parser to artemis, it +could be worth first trying to code it using the JS runtime. + +An simple JS parser can be found in the +[artemis API](https://github.com/puffyCid/artemis-api/blob/main/src/images/icns.ts) +repo. + +However, if you want to implement a new parser for parsing common Windows +artifacts such as *Jumplists* then that is definitely something that could be +worth including as a Rust coded parser. + +When in doubt or unsure open an issue! + +## Suggestions + +If you want add a new artifact but want to see how other artifacts are +implemented, some suggested ones to review are: + +- `UserAssist`: If you want to add a new Registry artifact. The UserAssist + artifact is less than 300 lines (not counting tests). And includes: + - Parsing binary data + - Converting timestamps + - Collecting user Registry data +- `FsEvents`: If you want to to parse a binary file. The FsEvents is less than + 300 lines (not counting tests). And includes: + + - Parsing binary data + - Decompressing data + - Getting data flags + +:::info + +FsEvents is the first artifact created for artemis. Its the oldest code in the project! + +::: + +## Useful Helper Functions + +The artemis codebase contains a handful of artifacts (ex: `Registry`) that +expose helper functions that allow other artifacts to reuse parts of that +artifact to help get artifact specific data. + +For example the Windows Registry artifact exposes a helper function that other +Registry based artifacts can leverage to help parse the Registry: + +- `pub(crate) fn get_registry_keys(start_path: &str, regex: &Regex, file_path: &str)` + will read a Registry file at provided file_path and filter to based on + start_path and regex. If start_path and regex are empty a full Registry + listing is returned. All Regex comparisons are done in lowercase. + +Some other examples listed below: + +- `/filesystem` contains code to help interact with the filesystem. + + - `pub(crate) fn list_files(path: &str)` returns list of files + - `pub(crate) fn read_file(path: &str)` reads a file + - `pub(crate) fn hash_file(hashes: &Hashes, path: &str)` hashes a file based + on selected hashes (MD5, SHA1, SHA256) + +- `/filesystem/ntfs` contains code to help interact with the raw NTFS + filesystem. It lets us bypass locked files. This is only available on Windows + + - `pub(crate) fn raw_read_file(path: &str)` reads a file. Will bypass file + locks + - `pub(crate) fn read_attribute(path: &str, attribute: &str)` can read an + Alternative Data Stream (ADS) + - `pub(crate) fn get_user_registry_files(drive: &char)` returns a Vector that + contains references to all user Registry files (NTUSER.DAT and + UsrClass.dat). It does **not** read the files, it just provides all the data + needed to start reading them. + +- `/src/artifacts/os/macos/plist/property_list.rs` contains code help parse + plist files. + - `pub(crate) fn parse_plist_file(path: &str)` will parse a plist file and + return it as a serde Value diff --git a/artemis-docs/docs/Intro/Collections/Examples/_category_.json b/artemis-docs/docs/Intro/Collections/Examples/_category_.json index 7b441e90..f6e0cbf6 100644 --- a/artemis-docs/docs/Intro/Collections/Examples/_category_.json +++ b/artemis-docs/docs/Intro/Collections/Examples/_category_.json @@ -1,8 +1,8 @@ { "label": "Examples", - "position": 5, + "position": 7, "link": { "type": "generated-index", "description": "Exmaple TOML Collections" } -} +} \ No newline at end of file diff --git a/artemis-docs/docs/Intro/Collections/acquisitions.md b/artemis-docs/docs/Intro/Collections/acquisitions.md new file mode 100644 index 00000000..fb7ed178 --- /dev/null +++ b/artemis-docs/docs/Intro/Collections/acquisitions.md @@ -0,0 +1,138 @@ +--- +sidebar_position: 5 +description: Acquiring Files +--- + + +# File Acquisitions + +Artemis supports acquiring files from a system for later processing. Acquiring a file is just like collecting forensic artifacts. + + +## Format +An example file acquisition collection is below: + +```toml +[output] +name = "triage_collection" +directory = "./tmp" +format = "json" +compress = false +timeline = false +endpoint_id = "6c51b123-1522-4572-9f2a-0bd5abd81b82" +collection_id = 1 +output = "local" + +[[artifacts]] +artifact_name = "triage" +[[artifacts.triage]] +name = "Default journal location" +path = "/var/log/journal/" +file_mask = "*.journal" +recursive = true +recreate_directories = true +``` + +- `recursive` Should artemis recursively walk the path provided +- `file_mask` A glob or regex of files artemis should acquire +- `recreate_directories` Should artemis recreate the directory structure when acquiring the file + + +:::info + +The file acquisition structure was inspired by [KapeFiles](https://github.com/EricZimmerman/KapeFiles). +You can convert KAPE collections to the artemis format by [downloading](https://github.com/puffyCid/artemis/tree/main/tools/Kape) a Python script to run the conversion + +::: + +## Output + +If you run the collection above on Linux system you will get same types of files you get when collecting forensic artifacts. + +``` +b8141817-69b5-43cf-acec-cc9c66621a23.log files.zip report_88da2beb-a9f7-4179-a0bb-3a6512e14a7b.json status_fedora.log +``` + +File acquisitions are compressed into the zip file **files.zip**. Since we enabled `recreate_directories` the directory structure should be retained + +``` +Archive: files.zip + Length Date Time Name +--------- ---------- ----- ---- + 16777216 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000002f76272-0006269487fbba0a.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000002f77162-0006272393872423.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000002f77d90-0006279b777261b2.journal + 272 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000302a710-0006279d1dd74605.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003057d90-00062946b7392ff3.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000305b0eb-00062bb1ef80d881.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000305bbb5-00062bb1f7633c49.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000305f8d3-00062ec17f3f0b08.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003062e53-000630f66b5e7793.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003065683-0006316df454faff.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003065f6e-0006316df8e74793.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030671b2-00063492218b398e.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000306755f-000634921fae12ac.journal + 33554432 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003068d02-000635c2c3cd8f29.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000307d265-0006394b11e2bed3.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000308258d-00063aa2708d9087.journal + 16777216 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003085216-00063bf9c9bbffcd.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003089682-00063e290c3eec77.journal + 16777216 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003091180-00064086d36f2126.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030983d5-000641efe20d6355.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003099cc6-0006430f35ceeaf9.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000309d947-00064422958a9fab.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000309e43d-000644229e43ebed.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000309fbf3-0006464738cbb0e3.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030a0de2-000646f3e240bfbc.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030ac2a1-000648b049ef907e.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030af2e9-00064bc0a0d1bb52.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/system@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030b0194-00064bfdf9bf9bec.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000002f75e8a-000625f96c6adac9.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000002f77a71-00062723a05d1cd3.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000305867b-00062946ba9e7850.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000305b9c5-00062bb1f26f86a6.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000305bbbc-00062bb1f7714572.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003065f96-0006316dfa2b9e70.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030675a7-00063492215b7ad2.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003068d05-000635c2c3d051ed.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003082f1d-00063aa27542d7a9.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003087d94-00063d10471eb475.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003091b37-00064086e0df69d1.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030942da-0006414ec5b1464c.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-0000000003098dc6-000641f00b3f017f.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000309e2ff-000644229ec21cfa.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-000000000309e442-000644229e476b69.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030a05a7-000646473ec38072.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030a0774-0006464745d0343d.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030acc48-000648b04e25d187.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030afc2a-00064bc0a8212038.journal + 8388608 01-01-1980 00:00 var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030b0b2b-00064bfe02811908.journal + 22309 01-01-1980 00:00 acquisition_report.json +``` + +The **files.zip** also contains a JSON acquisition report which contains some metadata about the collected files: + +```json + { + "created": "2026-02-26T21:23:25.000Z", + "modified": "2026-02-26T21:23:25.000Z", + "accessed": "2026-03-13T22:49:57.000Z", + "changed": "2026-02-26T21:23:25.000Z", + "full_path": "/var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030afc2a-00064bc0a8212038.journal", + "filename": "user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030afc2a-00064bc0a8212038.journal", + "md5": "03f3f3bfa91df6306d05ad2a2f313280", + "size": 8388608 + }, + { + "created": "2026-03-01T22:31:22.000Z", + "modified": "2026-03-01T22:31:22.000Z", + "accessed": "2026-03-13T22:49:57.000Z", + "changed": "2026-03-01T22:31:22.000Z", + "full_path": "/var/log/journal/19b88c32e63d43d583efce254b5f2b0d/user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030b0b2b-00064bfe02811908.journal", + "filename": "user-1000@c547b1d5e6e54ca3abfc70fda4dd54c7-00000000030b0b2b-00064bfe02811908.journal", + "md5": "fea921a7c022c226ba6554bbd3987b09", + "size": 8388608 + } +``` \ No newline at end of file diff --git a/artemis-docs/docs/Intro/Collections/esoteric.md b/artemis-docs/docs/Intro/Collections/esoteric.md new file mode 100644 index 00000000..e77182d6 --- /dev/null +++ b/artemis-docs/docs/Intro/Collections/esoteric.md @@ -0,0 +1,133 @@ +--- +sidebar_position: 6 +description: Additional Platforms +--- + +# Musl Support + +Artemis has support for compiling with the musl C standard library. This is useful if you want to run artemis on very niche Linux systems or systems that support ELF binaries. This allows us to avoid the system C runtime and statically link msul instead. + +If you want to compile artemis with the musl runtime you will need [cross](https://github.com/cross-rs/cross). Once cross is installed just run: + +- cross build --release --bin artemis --target x86_64-unknown-linux-musl + +You should then get an artemis Linux binary at: + +- target/x86_64-unknown-linux-musl/release/artemis + + +## ESXi + +A possible use case for musl binaries is running artemis on ESXi systems. You can use artemis to parse logs and data for forensic analysis. + +:::info + +Artemis has only been tested on ESXi version 8.0.3. But it *should* run on older and newer versions of ESXi. + +Feel free to open issue if errors are encountered. + +::: + +:::warning + +Unsigned 3rd party binaries are discouraged on ESXi appliances. + +[UAC](https://github.com/tclahr/uac) is recommended if you want to collect data without using a 3rd party binary. + +If you have never collected data from an ESXi system before. You should try [UAC](https://github.com/tclahr/uac). + +::: + +The easiest method to run artemis on ESXi is: + +1. Download the latest stable [musl linux release](https://github.com/puffyCid/artemis/releases) +2. Package artemis into a vSphere Installation Bundles (VIB) +3. SSH into the ESXi appliance and install the VIB package + +Since the VIB package is not signed, you will need root permissions in order to install the VIB package: + +- `esxcli software vib install -f -v file:///vmfs/volumes//artemis.vib` + +Once artemis is installed you can start collecting supported [artifacts](../../Artifacts/esxi.md). + +``` +esxcli software vib install -f -v file:///vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/artemis.vib +Installation Result + Message: Operation finished successfully. + VIBs Installed: puffycid_bootbank_artemis_0.19.0 + VIBs Removed: + VIBs Skipped: + Reboot Required: false + DPU Results: + +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] artemis -h +A cross platform forensic parser + +Usage: artemis [OPTIONS] [COMMAND] + +Commands: + acquire Acquire forensic artifacts + help Print this message or the help of the given subcommand(s) + +Options: + -t, --toml Full path to TOML collector + -d, --decode Base64 encoded TOML file + -j, --javascript Full path to JavaScript file + -h, --help Print help + -V, --version Print version + +[root@localhost:/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0] artemis -j main.js +[artemis] Starting artemis collection! +2026-04-03T23:07:31Z In(182) vmkernel: VMB: 65: Reserved 4 MPNs starting @ 0x4c4 +2026-04-03T23:07:31Z In(182) vmkernel: VMB_ACPI: 793: No SPCR table found. +2026-04-03T23:07:31Z In(182) vmkernel: VMB_SERIAL: 332: Serial port set to default configuration. +2026-04-03T23:07:31Z In(182) vmkernel: VMB: 79: TDX: Unsupported on CPU (MSR_MTRRCAP = 0x508) +2026-04-03T23:07:31Z In(182) vmkernel: VMB_MEMMAP: 2744: memmap[0]: addr 0, len 9fc00, type 1 +``` + +Remove artemis once you are done: + +- `esxcli software vib remove -n artemis` + +:::danger + +Artemis has only been tested on development/test instances of ESXi devices. +Unsigned 3rd party binaries are discouraged on ESXi appliances. + +Currently [UAC](https://github.com/tclahr/uac) is suggested if you want to collect data + +You should only consider using artemis if you want todo the following: + +- Run yara rules against ESXi appliance +- Generate a filelisting timeline +- Develop additional artifact parsers +- Run it on a test ESXi instance + +::: + + +:::danger + +You may also run artemis without packaging it in a VIB. +However, runtime ESXi security protections associated with `/User/ExecInstalledOnly -i 0` will need to be disabled. + +::: + +### ESXi Limitations + +Artemis currently does not support the following artifacts on ESXi systems: + +- Process listing +- Network connections +- System info + + +In addition, cloud uploads and remote TOML collections are currently not supported. + + +:::info + +Is ESXi Linux based? +[No (supposedly)](https://www.v-front.de/2013/08/a-myth-busted-and-faq-esxi-is-not-based.html) + +::: \ No newline at end of file diff --git a/artemis-docs/docs/Intro/Collections/output.md b/artemis-docs/docs/Intro/Collections/output.md index ea5afcf6..4b965e1a 100644 --- a/artemis-docs/docs/Intro/Collections/output.md +++ b/artemis-docs/docs/Intro/Collections/output.md @@ -437,7 +437,7 @@ All artifacts parsed by artemis will be formatted similar to the output above. - `complete_time` The time artemis completed parsing the data - `start_time` The time artemis started parsing the data - `hostname` The hostname of the endpoint - - `os_version` Thes OS version of the endpoint + - `os_version` The OS version of the endpoint - `platform` The platform of the endpoint. Ex: Windows or macOS - `kernel_version` The kernel version of the endpoint - `load_performance` The endpoint performance for one, five, and fifteen @@ -500,15 +500,15 @@ The summary report is always outputted in JSON An example is below: ```json { - "boot_time": "2026-01-14T11:15:58.000Z", + "boot_time": "2026-04-17T11:14:41.000Z", "hostname": "win", "os_version": "11 (26200)", - "uptime": 1496419, + "uptime": 923558, "kernel_version": "26200", "platform": "Windows", "cpu": [ { - "frequency": 998, + "frequency": 806, "cpu_usage": 100.0, "name": "CPU 1", "vendor_id": "ARM x64", @@ -522,24 +522,24 @@ An example is below: "file_system": "NTFS", "mount_point": "C:\\", "total_space": 1021771247616, - "available_space": 863308914688, + "available_space": 839567216640, "removable": false, "name": "Local Disk" } ], "memory": { - "available_memory": 5520465920, - "free_memory": 5520465920, - "free_swap": 12348030976, + "available_memory": 6326059008, + "free_memory": 6326059008, + "free_swap": 12884901888, "total_memory": 16761454592, - "total_swap": 12348030976, - "used_memory": 11240988672, + "total_swap": 12884901888, + "used_memory": 10435395584, "used_swap": 0 }, "interfaces": [ { - "ip": "192.168.1.132", - "mac": "00:00:00:00", + "ip": "2600:4040:475f:db00:4de5:23d7:ec57:cbd6", + "mac": "11:a7:2f:22:ss:44", "name": "Wi-Fi" } ], @@ -548,31 +548,41 @@ An example is below: "avg_five_min": 0.0, "avg_fifteen_min": 0.0 }, - "version": "0.18.0", - "rust_version": "1.91.1", - "build_date": "2026-01-26", + "artemis_version": "0.19.0", + "artemis_commit": "af6df149736e8e9430276a211192fca51ebb4b93", + "artemis_args": "C:\\Users\\dev\\Projects\\artemis\\target\\release\\artemis.exe acquire --timeline amcache", + "artemis_features": "yarax", + "artemis_target": "aarch64-pc-windows-msvc", + "artemis_profile": "release", + "rust_version": "1.95.0", + "build_date": "2026-04-27", "product_name": "Microsoft Surface Laptop, 7th Edition", "product_family": "Surface", - "product_serial": "ssssss", - "product_uuid": "aaaaaa-e32e-dedc-2061-aaaaa", - "product_version": "aaaaaaaa", + "product_serial": "1234567", + "product_uuid": "dddddddd-cccc-aaaa-1111-1111111111", + "product_version": "124I:00108T:000M:0000000F:0B:11C:12M:02D:14U:02T:2R:21S:1A:0", "vendor": "Microsoft Corporation", - "collection_id": 1, - "endpoint_id": "6c51b123-1522-4572-9f2a-0bd5abd81b82", - "start_time": "2026-01-31T18:56:15.000Z", - "end_time": "2026-01-31T18:56:18.000Z", + "collection_id": 0, + "endpoint_id": "local", + "start_time": "2026-04-28T03:47:17.000Z", + "end_time": "2026-04-28T03:47:19.000Z", "total_output_files": 1, "artifacts": [ "amcache" ], + "log_file": "./tmp/local_collector/c1e179e9-f757-4c2d-bd9d-00138ff68e37.log", + "output_format": "jsonl", + "output": "local", "artifact_runs": [ { "name": "amcache", - "hash": "0f6f77ca2ccdcf1643074d8c3b0e1f41", - "last_run": "2026-01-31T18:56:18.000Z", - "unixepoch": 1769885778, + "artifact_options_hash": "2f622787d4c93f377a9ada8ad3945471", + "last_run": "2026-04-28T03:47:19.000Z", + "unixepoch": 1777348039, "output_count": 1, - "log_file": "./tmp/amcache_collection/c8c8a9b6-8a1a-4fdf-bd64-ee8bafbec84a.log", + "output_files": [ + "./tmp/local_collector/amcache_4b095246-ad69-48f8-a64e-93555dfb08de.jsonl" + ], "status": "completed" } ] diff --git a/artemis-docs/docs/Intro/Collections/uploads.md b/artemis-docs/docs/Intro/Collections/uploads.md index 52e264aa..5604cac1 100644 --- a/artemis-docs/docs/Intro/Collections/uploads.md +++ b/artemis-docs/docs/Intro/Collections/uploads.md @@ -12,7 +12,7 @@ services: 2. Microsoft Azure 3. Amazon Web Services (AWS) -Uploading collections to a remote serivce requires three (3) steps: +Uploading collections to a remote service requires three (3) steps: 1. Name of remote service. Valid options are: `"gcp", "azure", "aws"` 2. URL to the remote service diff --git a/artemis-docs/docs/Intro/Scripting/boa.md b/artemis-docs/docs/Intro/Scripting/boa.md index b1c2a848..f90c213f 100644 --- a/artemis-docs/docs/Intro/Scripting/boa.md +++ b/artemis-docs/docs/Intro/Scripting/boa.md @@ -1,41 +1,54 @@ ---- -sidebar_position: 1 -description: How to script with artemis ---- - -# Overview - -A really cool capability of artemis is it contains an embedded JavaScript -runtime thats designed specifically for DFIR! Artemis uses -[Boa](https://boajs.dev/) a JS engine written in Rust. - -Using an embedded JS engine allows us to call Rust functions from JavaScript!\ -For example, the artemis function `get_registry()` can be used to parse a -provided Registry file on disk. By registering this function with Boa we can -call this function directly from JavaScript! In addition to JavaScript, we have -[TypeScript](https://www.typescriptlang.org/) bindings that we can leverage that makes scripting even easier! - -To summarize: - -1. We can create a script using TypeScript and call artemis Rust functions directly -2. Compile TypeScript to JavaScript -3. Execute JavaScript using artemis - -:::info - -The JS runtime in artemis is kind of like the VQL language for -[Velociraptor](https://docs.velociraptor.app/docs/vql/) or the -[Dissect forensic framework](https://github.com/fox-it/dissect) - -All three let you script forensic collections and parsing - -::: - -## Prerequisites for Scripting. - -1. A text-editor or IDE that supports TypeScript. - [VSCodium](https://vscodium.com/) and - [VSCode](https://code.visualstudio.com/) have been tested -2. A TypeScript to JavaScript bundler - - [esbuild](https://esbuild.github.io/). Is a a popular one and is extremely - fast +--- +sidebar_position: 1 +description: How to script with artemis +--- + +# Overview + +A really cool capability of artemis is it contains an embedded JavaScript +runtime thats designed specifically for DFIR! Artemis uses +[Boa](https://boajs.dev/) a JS engine written in Rust. + +Using an embedded JS engine allows us to call Rust functions from JavaScript!\ +For example, the artemis function `get_registry()` can be used to parse a +provided Registry file on disk. By registering this function with Boa we can +call this function directly from JavaScript! In addition to JavaScript, we have +[TypeScript](https://www.typescriptlang.org/) bindings that we can leverage that makes scripting even easier! + +To summarize: + +1. We can create a script using TypeScript and call artemis Rust functions directly +2. Compile TypeScript to JavaScript +3. Execute JavaScript using artemis + +:::info + +The JS runtime in artemis is kind of like the VQL language for +[Velociraptor](https://docs.velociraptor.app/docs/vql/) or the +[Dissect forensic framework](https://github.com/fox-it/dissect) + +All three let you script forensic collections and parsing + +::: + +## Prerequisites for Scripting. + +1. A text-editor or IDE that supports TypeScript. + [VSCodium](https://vscodium.com/) and + [VSCode](https://code.visualstudio.com/) have been tested +2. A TypeScript to JavaScript bundler + - [esbuild](https://esbuild.github.io/). Is a a popular one and is extremely + fast + + +### Is any Sandboxing done for BoaJS? + +No. + +Currently there is no sandbox for the JavaScript runtime. Similar to the VQL language for Velociraptor. The JavaScript runtime allows you to: + +- Read Registry files +- Read files on disk +- Execute remote commands +- Parse forensic artifacts +- Make network requests \ No newline at end of file diff --git a/artemis-docs/docs/Intro/Scripting/bundling.md b/artemis-docs/docs/Intro/Scripting/bundling.md index 73cc4015..b14ec158 100644 --- a/artemis-docs/docs/Intro/Scripting/bundling.md +++ b/artemis-docs/docs/Intro/Scripting/bundling.md @@ -1,166 +1,182 @@ ---- -sidebar_position: 3 -description: What is bundling ---- - -# Bundling - -## What is a bundler - -Before we can start creating more powerful scripts, it is important to reviewing bundling. - -Artemis only supports executing a single script file. This means our entire script must be contained in a single file. - -So if we want to import the artemis API we need to learn how to use a bundler to help compile our script into a single file. - -There are multiple types of bundler applications that can help us with this -task. One of the most popular ones is [esbuild](https://esbuild.github.io/) - -## Esbuild - -[esbuild](https://esbuild.github.io/) is a popular Bundler for JavaScript. It can be run as a standalone binary on your local development laptop. -Once you have esbuild installed you can compile and bundle your TypeScript with the following command: - -``` -esbuild --bundle --outfile=out.js main.ts -``` - -The above commands will use the main.ts file and bundle all of its imports into one .js file using esbuild. - -:::info - -esbuild can also minify JavaScript code! This can dramatically reduce the size of your script - -``` -esbuild --bundle --minify --outfile=out.js main.ts -``` - -::: - -## Examples - -### Intermediate - -Lets start using the API! - -1. Clone the artemis API repo: https://github.com/puffyCid/artemis-api -2. Create the file main.ts and add the following - - -```typescript -// Update this path to point to your cloned artemis-api repo -import { timeNow } from "./artemis-api/src/time/conversion"; - -function main() { - const time_in_seconds = timeNow(); - - console.log(`Time in unix epoch seconds: ${time_in_seconds}`); -} - -main(); -``` - -If you hover over the `timeNow()` function your text editor should provide function documentation. - -If you try to run the above above code with artemis you will get a similar error as below: - -``` -> artemis -j main.ts -[artemis] Starting artemis collection! -01:29:25 [ERROR] [runtime] Could not execute script: JsError { inner: Native(JsNativeError { kind: Syntax, message: "expected token '.', got '{' in import.meta at line 1, col 8", cause: None, .. }) } -[artemis] Finished artemis collection! -``` - -Artemis cannot resolve imports! We need to bundle this script and compile to JavaScript. - -Using esbuild try the following commands: `esbuild --bundle --outfile=out.js main.ts` - -You should now have a main.js file in your directory. If you open it in a text editor you should see something similar to below: - -```javascript -(() => { - // ./artemis-api/src/time/conversion.ts - function timeNow() { - // This is a Rust function! - const data = js_time_now(); - return Number(data); - } - - // main.ts - function main() { - const time_in_seconds = timeNow(); - console.log(`Time in unix epoch seconds: ${time_in_seconds}`); - } - main(); -})(); -``` - -The bundler took our tiny ~10 line TypeScript file and compiled it to JavaScript and bundled our import! - -Now run the main.js file with artemis: -``` -> artemis -j main.js -[artemis] Starting artemis collection! -Time in unix epoch seconds: 1767058428 -[artemis] Finished artemis collection! -``` - -### Advanced - -Lets try something a bit more challenging! Lets get a process listing! - -1. Clone the artemis API repo: https://github.com/puffyCid/artemis-api -2. Create the file main.ts and add the following - -```typescript -import { processListing } from "./artemis-api/mod"; - -function main() { - // Get a list of running processes - const proc_list = processListing(); - console.log(`Got ${proc_list.length} processes`); - - console.log(`Looping through processes...`); - - // Loop through each entry - for (const entry of proc_list) { - console.log(`Process ID ${entry.pid}. Name is ${entry.name}`); - } -} - -main(); -``` - -If you hover over the `processListing()` function your text editor should provide function documentation. As you type your text editor should provide property hints and auto complete suggestions. - -Using esbuild try the following commands: `esbuild --bundle --minify --outfile=out.js main.ts` - -Now run the main.js file with artemis: -``` -> artemis -j main.js -[artemis] Starting artemis collection! -Got 2669 processes -Looping through processes... -... -Process ID 151. Name is kdevtmpfs -Process ID 6323. Name is dbus-broker -Process ID 733184. Name is gdbus -Process ID 6890. Name is gnome-keyring-d -Process ID 3344. Name is chronyd -Process ID 8032. Name is mullvad-gui -Process ID 372481. Name is syncthing -Process ID 691791. Name is node -... -[artemis] Finished artemis collection! -``` - - -## API Documentation - -The most useful API artifacts and functions are exposed in the file [artemis-api/mod.ts](https://github.com/puffyCid/artemis-api/blob/main/mod.ts). If you import (or view the mod.ts) you have access to nearly all of the API artifacts and parsers. - -The API documentation can also be viewed at: -- [API Docs](../../API/overview.md) - -All artifact properties can be viewed at: -- [Artifacts](../../Artifacts/overview.md) \ No newline at end of file +--- +sidebar_position: 3 +description: What is bundling +--- + +# Bundling + +## What is a bundler + +Before we can start creating more powerful scripts, it is important to reviewing bundling. + +Artemis only supports executing a single script file. This means our entire script must be contained in a single file. + +So if we want to import the artemis API we need to learn how to use a bundler to help compile our script into a single file. + +There are multiple types of bundler applications that can help us with this +task. One of the most popular ones is [esbuild](https://esbuild.github.io/) + +## Esbuild + +[esbuild](https://esbuild.github.io/) is a popular Bundler for JavaScript. It can be run as a standalone binary on your local development laptop. +Once you have esbuild installed you can compile and bundle your TypeScript with the following command: + +``` +esbuild --bundle --outfile=out.js main.ts +``` + +The above commands will use the main.ts file and bundle all of its imports into one .js file using esbuild. + +:::info + +esbuild can also minify JavaScript code! This can dramatically reduce the size of your script + +``` +esbuild --bundle --minify --outfile=out.js main.ts +``` + +::: + +## Examples + +### Intermediate + +Lets start using the API! + +1. Clone the artemis API repo: https://github.com/puffyCid/artemis-api +2. Create the file main.ts and add the following + + +```typescript +// Update this path to point to your cloned artemis-api repo +import { timeNow } from "./artemis-api/src/time/conversion"; + +function main() { + const time_in_seconds = timeNow(); + + console.log(`Time in unix epoch seconds: ${time_in_seconds}`); +} + +main(); +``` + +If you hover over the `timeNow()` function your text editor should provide function documentation. + +If you try to run the above above code with artemis you will get a similar error as below: + +``` +> artemis -j main.ts +[artemis] Starting artemis collection! +01:29:25 [ERROR] [runtime] Could not execute script: JsError { inner: Native(JsNativeError { kind: Syntax, message: "expected token '.', got '{' in import.meta at line 1, col 8", cause: None, .. }) } +[artemis] Finished artemis collection! +``` + +Artemis cannot resolve imports! We need to bundle this script and compile to JavaScript. + +Using esbuild try the following commands: `esbuild --bundle --outfile=out.js main.ts` + +You should now have a main.js file in your directory. If you open it in a text editor you should see something similar to below: + +```javascript +(() => { + // ./artemis-api/src/time/conversion.ts + function timeNow() { + // This is a Rust function! + const data = js_time_now(); + return Number(data); + } + + // main.ts + function main() { + const time_in_seconds = timeNow(); + console.log(`Time in unix epoch seconds: ${time_in_seconds}`); + } + main(); +})(); +``` + +The bundler took our tiny ~10 line TypeScript file and compiled it to JavaScript and bundled our import! + +Now run the main.js file with artemis: +``` +> artemis -j main.js +[artemis] Starting artemis collection! +Time in unix epoch seconds: 1767058428 +[artemis] Finished artemis collection! +``` + +### Advanced + +Lets try something a bit more challenging! Lets get a process listing! + +1. Clone the artemis API repo: https://github.com/puffyCid/artemis-api +2. Create the file main.ts and add the following + +```typescript +import { processListing } from "./artemis-api/mod"; + +function main() { + // Get a list of running processes + const proc_list = processListing(); + console.log(`Got ${proc_list.length} processes`); + + console.log(`Looping through processes...`); + + // Loop through each entry + for (const entry of proc_list) { + console.log(`Process ID ${entry.pid}. Name is ${entry.name}`); + } +} + +main(); +``` + +If you hover over the `processListing()` function your text editor should provide function documentation. As you type your text editor should provide property hints and auto complete suggestions. + +Using esbuild try the following commands: `esbuild --bundle --minify --outfile=out.js main.ts` + +Now run the main.js file with artemis: +``` +> artemis -j main.js +[artemis] Starting artemis collection! +Got 2669 processes +Looping through processes... +... +Process ID 151. Name is kdevtmpfs +Process ID 6323. Name is dbus-broker +Process ID 733184. Name is gdbus +Process ID 6890. Name is gnome-keyring-d +Process ID 3344. Name is chronyd +Process ID 8032. Name is mullvad-gui +Process ID 372481. Name is syncthing +Process ID 691791. Name is node +... +[artemis] Finished artemis collection! +``` + + +## API Documentation + +The most useful API artifacts and functions are exposed in the file [artemis-api/mod.ts](https://github.com/puffyCid/artemis-api/blob/main/mod.ts). If you import (or view the mod.ts) you have access to nearly all of the API artifacts and parsers. + +The API documentation can also be viewed at: +- [API Docs](../../API/overview.md) + +All artifact properties can be viewed at: +- [Artifacts](../../Artifacts/overview.md) + + +### API Releases and Nightly Branch + +There are currently two versions of the artemis API: + +- Main branch +- Nightly branch + +The main branch of the API will always support the latest artemis [version](https://github.com/puffyCid/artemis/releases). +The nightly branch of the API will always support the [nightly version](https://github.com/puffyCid/artemis/releases/tag/nightly) of artemis. + + +*Most* of public artemis API is stable and will not heavily change between releases. Typically you can use latest released version of artemis against the nightly API branch. + +When a new version of artemis is released the nightly API branch gets merged into the main API branch and a new nightly API branch is created. \ No newline at end of file diff --git a/artemis-docs/docs/Intro/cli.md b/artemis-docs/docs/Intro/cli.md index 12b2c050..69993d6b 100644 --- a/artemis-docs/docs/Intro/cli.md +++ b/artemis-docs/docs/Intro/cli.md @@ -1,207 +1,215 @@ ---- -sidebar_position: 3 ---- - -# CLI Options - -Artemis is designed to have a very simple CLI menu. The CLI allows you to quickly get started with parsing forensic data. - -## Running Artemis - -Once you have [installed](./installation.md) artemis you can access its help -menu with the command below: - -``` -artemis -h -A cross platform forensic parser - -Usage: artemis [OPTIONS] [COMMAND] - -Commands: - acquire Acquire forensic artifacts - help Print this message or the help of the given subcommand(s) - -Options: - -t, --toml Full path to TOML collector - -d, --decode Base64 encoded TOML file - -j, --javascript Full path to JavaScript file - -h, --help Print help - -V, --version Print version -``` - -## Collecting Artifacts - -The easiest way to start collecting forensic artifacts is to use the `acquire` -command. This will allow you to select specific artifacts. - -For example for macOS a user can acquire any of the artifacts below: - -``` -artemis acquire -h -Acquire forensic artifacts - -Usage: artemis acquire [OPTIONS] [COMMAND] - -Commands: - processes Collect processes - connections Collect network connections - filelisting Pull filelisting - systeminfo Get systeminfo - prefetch windows: Parse Prefetch - eventlogs windows: Parse EventLogs - rawfilelisting windows: Parse NTFS to get filelisting - shimdb windows: Parse ShimDatabase - registry windows: Parse Registry - userassist windows: Parse Userassist - shimcache windows: Parse Shimcache - shellbags windows: Parse Shellbags - amcache windows: Parse Amcache - shortcuts windows: Parse Shortcuts - usnjrnl windows: Parse UsnJrnl - bits windows: Parse BITS - srum windows: Parse SRUM - users-windows windows: Parse Users - search windows: Parse Windows Search - tasks windows: Parse Windows Tasks - services windows: Parse Windows Services - jumplists windows: Parse Jumplists - recyclebin windows: Parse RecycleBin - wmipersist windows: Parse WMI Repository - outlook windows: Parse Outlook messages - mft windows: Parse MFT file - execpolicy macos: Parse ExecPolicy - users-macos macos: Collect local users - fsevents macos: Parse FsEvents entries - emond macos: Parse Emond persistence. Removed in Ventura - loginitems macos: Parse LoginItems - launchd macos: Parse Launch Daemons and Agents - groups-macos macos: Collect local groups - unifiedlogs macos: Parse the Unified Logs - sudologs-macos macos: Parse Sudo log entries from Unified Logs - spotlight macos: Parse the Spotlight database - sudologs-linux linux: Grab Sudo logs - journals linux: Parse systemd Journal files - logons linux: Parse Logon files - rawfilelisting-ext4 linux: Parse the raw ext4 filesystem - help Print this message or the help of the given subcommand(s) - -Options: - --format Output format. JSON or JSONL or CSV [default: JSON] - --output-dir Optional output directory for storing results [default: ./tmp] - --compress GZIP Compress results - --timeline Timeline parsed data. Output is always JSONL - -h, --help Print help - -``` - -To collect a process listing you would type: - -``` -artemis acquire processes -``` - -## TOML Collections - -If you want to collect multiple artifacts the easiest way to do that is to -create a TOML file and provide the TOML file to artemis. There are two (2) ways -to provide TOML collections: - -- Provide the full path the TOML file on disk -- base64 encode a TOML file and provide that as an argument - -The artemis source code provides several pre-made TOML collection files that can -used as examples. - -For example on **macOS** we downloaded the -[processes.toml](https://github.com/puffycid/artemis/blob/main/forensics/tests/test_data/macos/processes.toml) -file from the artemis repo to the same directory as the **macOS** artemis binary -and ran using **sudo** - -``` -sudo ./artemis -t processes.toml -[artemis] Starting artemis collection! -[artemis] Finished artemis collection! -``` - -On **Windows** we downloaded the -[processes.toml](https://github.com/puffycid/artemis/blob/main/forensics/tests/test_data/windows/processes.toml) -file from the artemis repo to the same directory as the **Windows** artemis -binary and ran using **Administrator** privileges - -``` -artemis.exe -t processes.toml -[artemis] Starting artemis collection! -[artemis] Finished artemis collection! -``` - -Both `processes.toml` files tell artemis to output the results to a directory -called `tmp/process_collection` in the current directory and output using jsonl -format - -``` -./tmp -└── process_collection - └── d7f89e7b-fcd8-42e8-8769-6fe7eaf58bee.jsonl -``` - -To run the same collection except as a base64 encoded string on **macOS** we can -do the following: - -``` -sudo ./artemis -d c3lzdGVtID0gIm1hY29zIgoKW291dHB1dF0KbmFtZSA9ICJwcm9jZXNzX2NvbGxlY3Rpb24iCmRpcmVjdG9yeSA9ICIuL3RtcCIKZm9ybWF0ID0gImpzb25sIgpjb21wcmVzcyA9IGZhbHNlCmVuZHBvaW50X2lkID0gImFiZGMiCmNvbGxlY3Rpb25faWQgPSAxCm91dHB1dCA9ICJsb2NhbCIKCltbYXJ0aWZhY3RzXV0KYXJ0aWZhY3RfbmFtZSA9ICJwcm9jZXNzZXMiICMgTmFtZSBvZiBhcnRpZmFjdApbYXJ0aWZhY3RzLnByb2Nlc3Nlc10KbWV0YWRhdGEgPSB0cnVlICMgR2V0IGV4ZWN1dGFibGUgbWV0YWRhdGEKbWQ1ID0gdHJ1ZSAjIE1ENSBhbGwgZmlsZXMKc2hhMSA9IGZhbHNlICMgU0hBMSBhbGwgZmlsZXMKc2hhMjU2ID0gZmFsc2UgIyBTSEEyNTYgYWxsIGZpbGVz -[artemis] Starting artemis collection! -[artemis] Finished artemis collection! -``` - -On **Windows** it would be (using **Administrator** privileges again): - -``` -artemis.exe -d c3lzdGVtID0gIndpbmRvd3MiCgpbb3V0cHV0XQpuYW1lID0gInByb2Nlc3Nlc19jb2xsZWN0aW9uIgpkaXJlY3RvcnkgPSAiLi90bXAiCmZvcm1hdCA9ICJqc29uIgpjb21wcmVzcyA9IGZhbHNlCmVuZHBvaW50X2lkID0gImFiZGMiCmNvbGxlY3Rpb25faWQgPSAxCm91dHB1dCA9ICJsb2NhbCIKCltbYXJ0aWZhY3RzXV0KYXJ0aWZhY3RfbmFtZSA9ICJwcm9jZXNzZXMiICMgTmFtZSBvZiBhcnRpZmFjdApbYXJ0aWZhY3RzLnByb2Nlc3Nlc10KbWV0YWRhdGEgPSB0cnVlICMgR2V0IGV4ZWN1dGFibGUgbWV0YWRhdGEKbWQ1ID0gdHJ1ZSAjIE1ENSBhbGwgZmlsZXMKc2hhMSA9IGZhbHNlICMgU0hBMSBhbGwgZmlsZXMKc2hhMjU2ID0gZmFsc2UgIyBTSEEyNTYgYWxsIGZpbGVz -[artemis] Starting artemis collection! -[artemis] Finished artemis collection! -``` - -## JavaScript Collections - -You can also execute JavaScript code using artemis. - -```javascript -function getProcesses(md5, sha1, sha256, binary_info) { - const hashes = { - md5, - sha1, - sha256, - }; - const data = js_get_processes( - JSON.stringify(hashes), - binary_info, - ); - const results = JSON.parse(data); - return results; -} - -function main() { - const md5 = false; - const sha1 = false; - const sha256 = false; - const binary_info = false; - const proc_list = getProcesses(md5, sha1, sha256, binary_info); - console.log(proc_list[0].full_path); - return proc_list; -} -main(); -``` - -To execute the above code - -``` -artemis -j vanilla.js -[artemis] Starting artemis collection! -[runtime]: "/usr/libexec/nesessionmanager" -[artemis] Finished artemis collection! -``` - -Collecting data via JavaScript is a bit more complex than other methods. But it -provides a lot more flexibility on what you can do with the data. - -See the section on [Scripting](../Intro/Scripting/boa.md) to learn more! +--- +sidebar_position: 3 +--- + +# CLI Options + +Artemis is designed to have a very simple CLI menu. The CLI allows you to quickly get started with parsing forensic data. + +## Running Artemis + +Once you have [installed](./installation.md) artemis you can access its help +menu with the command below: + +``` +artemis -h +A cross platform forensic parser + +Usage: artemis [OPTIONS] [COMMAND] + +Commands: + acquire Acquire forensic artifacts + help Print this message or the help of the given subcommand(s) + +Options: + -t, --toml Full path to TOML collector + -d, --decode Base64 encoded TOML file + -j, --javascript Full path to JavaScript file + -h, --help Print help + -V, --version Print version +``` + +## Collecting Artifacts + +The easiest way to start collecting forensic artifacts is to use the `acquire` +command. This will allow you to select specific artifacts. + +For example for macOS a user can acquire any of the artifacts below: + +``` +artemis acquire -h +Acquire forensic artifacts + +Usage: artemis acquire [OPTIONS] [COMMAND] + +Commands: + processes Collect processes + connections Collect network connections + filelisting Pull filelisting + systeminfo Get systeminfo + prefetch windows: Parse Prefetch + eventlogs windows: Parse EventLogs + rawfilelisting windows: Parse NTFS to get filelisting + shimdb windows: Parse ShimDatabase + registry windows: Parse Registry + userassist windows: Parse Userassist + shimcache windows: Parse Shimcache + shellbags windows: Parse Shellbags + amcache windows: Parse Amcache + shortcuts windows: Parse Shortcuts + usnjrnl windows: Parse UsnJrnl + bits windows: Parse BITS + srum windows: Parse SRUM + users-windows windows: Parse Users + search windows: Parse Windows Search + tasks windows: Parse Windows Tasks + services windows: Parse Windows Services + jumplists windows: Parse Jumplists + recyclebin windows: Parse RecycleBin + wmipersist windows: Parse WMI Repository + outlook windows: Parse Outlook messages + mft windows: Parse MFT file + execpolicy macos: Parse ExecPolicy + users-macos macos: Collect local users + fsevents macos: Parse FsEvents entries + emond macos: Parse Emond persistence. Removed in Ventura + loginitems macos: Parse LoginItems + launchd macos: Parse Launch Daemons and Agents + groups-macos macos: Collect local groups + unifiedlogs macos: Parse the Unified Logs + sudologs-macos macos: Parse Sudo log entries from Unified Logs + spotlight macos: Parse the Spotlight database + sudologs-linux linux: Grab Sudo logs + journals linux: Parse systemd Journal files + logons linux: Parse Logon files + rawfilelisting-ext4 linux: Parse the raw ext4 filesystem + help Print this message or the help of the given subcommand(s) + +Options: + --format Output format. JSON or JSONL or CSV [default: JSON] + --output-dir Optional output directory for storing results [default: ./tmp] + --compress GZIP Compress results + --timeline Timeline parsed data. Output is always JSONL + -h, --help Print help + +``` + +To collect a process listing you would type: + +``` +artemis acquire processes +``` + +## TOML Collections + +If you want to collect multiple artifacts the easiest way to do that is to +create a TOML file and provide the TOML file to artemis. There are two (2) ways +to provide TOML collections: + +- Provide the full path the TOML file on disk +- base64 encode a TOML file and provide that as an argument + +The artemis source code provides several pre-made TOML collection files that can +used as examples. + +For example on **macOS** we downloaded the +[processes.toml](https://github.com/puffycid/artemis/blob/main/forensics/tests/test_data/macos/processes.toml) +file from the artemis repo to the same directory as the **macOS** artemis binary +and ran using **sudo** + +``` +sudo ./artemis -t processes.toml +[artemis] Starting artemis collection! +[artemis] Finished artemis collection! +``` + +On **Windows** we downloaded the +[processes.toml](https://github.com/puffycid/artemis/blob/main/forensics/tests/test_data/windows/processes.toml) +file from the artemis repo to the same directory as the **Windows** artemis +binary and ran using **Administrator** privileges + +``` +artemis.exe -t processes.toml +[artemis] Starting artemis collection! +[artemis] Finished artemis collection! +``` + +Both `processes.toml` files tell artemis to output the results to a directory +called `tmp/process_collection` in the current directory and output using jsonl +format + +``` +./tmp +└── process_collection + └── d7f89e7b-fcd8-42e8-8769-6fe7eaf58bee.jsonl +``` + +To run the same collection except as a base64 encoded string on **macOS** we can +do the following: + +``` +sudo ./artemis -d c3lzdGVtID0gIm1hY29zIgoKW291dHB1dF0KbmFtZSA9ICJwcm9jZXNzX2NvbGxlY3Rpb24iCmRpcmVjdG9yeSA9ICIuL3RtcCIKZm9ybWF0ID0gImpzb25sIgpjb21wcmVzcyA9IGZhbHNlCmVuZHBvaW50X2lkID0gImFiZGMiCmNvbGxlY3Rpb25faWQgPSAxCm91dHB1dCA9ICJsb2NhbCIKCltbYXJ0aWZhY3RzXV0KYXJ0aWZhY3RfbmFtZSA9ICJwcm9jZXNzZXMiICMgTmFtZSBvZiBhcnRpZmFjdApbYXJ0aWZhY3RzLnByb2Nlc3Nlc10KbWV0YWRhdGEgPSB0cnVlICMgR2V0IGV4ZWN1dGFibGUgbWV0YWRhdGEKbWQ1ID0gdHJ1ZSAjIE1ENSBhbGwgZmlsZXMKc2hhMSA9IGZhbHNlICMgU0hBMSBhbGwgZmlsZXMKc2hhMjU2ID0gZmFsc2UgIyBTSEEyNTYgYWxsIGZpbGVz +[artemis] Starting artemis collection! +[artemis] Finished artemis collection! +``` + +On **Windows** it would be (using **Administrator** privileges again): + +``` +artemis.exe -d c3lzdGVtID0gIndpbmRvd3MiCgpbb3V0cHV0XQpuYW1lID0gInByb2Nlc3Nlc19jb2xsZWN0aW9uIgpkaXJlY3RvcnkgPSAiLi90bXAiCmZvcm1hdCA9ICJqc29uIgpjb21wcmVzcyA9IGZhbHNlCmVuZHBvaW50X2lkID0gImFiZGMiCmNvbGxlY3Rpb25faWQgPSAxCm91dHB1dCA9ICJsb2NhbCIKCltbYXJ0aWZhY3RzXV0KYXJ0aWZhY3RfbmFtZSA9ICJwcm9jZXNzZXMiICMgTmFtZSBvZiBhcnRpZmFjdApbYXJ0aWZhY3RzLnByb2Nlc3Nlc10KbWV0YWRhdGEgPSB0cnVlICMgR2V0IGV4ZWN1dGFibGUgbWV0YWRhdGEKbWQ1ID0gdHJ1ZSAjIE1ENSBhbGwgZmlsZXMKc2hhMSA9IGZhbHNlICMgU0hBMSBhbGwgZmlsZXMKc2hhMjU2ID0gZmFsc2UgIyBTSEEyNTYgYWxsIGZpbGVz +[artemis] Starting artemis collection! +[artemis] Finished artemis collection! +``` + +### Remote TOML Collections + +You can also point artemis to a remote TOML file hosted on an external server. For example if you point artemis to the [URL](https://raw.githubusercontent.com/puffycid/artemis/refs/heads/main/forensics/tests/test_data/windows/processes.toml) below it will collect a process listing. + +``` +artemis -t https://raw.githubusercontent.com/puffycid/artemis/refs/heads/main/forensics/tests/test_data/windows/processes.toml +``` + +## JavaScript Collections + +You can also execute JavaScript code using artemis. + +```javascript +function getProcesses(md5, sha1, sha256, binary_info) { + const hashes = { + md5, + sha1, + sha256, + }; + const data = js_get_processes( + JSON.stringify(hashes), + binary_info, + ); + const results = JSON.parse(data); + return results; +} + +function main() { + const md5 = false; + const sha1 = false; + const sha256 = false; + const binary_info = false; + const proc_list = getProcesses(md5, sha1, sha256, binary_info); + console.log(proc_list[0].full_path); + return proc_list; +} +main(); +``` + +To execute the above code + +``` +artemis -j vanilla.js +[artemis] Starting artemis collection! +[runtime]: "/usr/libexec/nesessionmanager" +[artemis] Finished artemis collection! +``` + +Collecting data via JavaScript is a bit more complex than other methods. But it +provides a lot of flexibility on what you can do with the data. + +See the section on [Scripting](../Intro/Scripting/boa.md) to learn more! diff --git a/artemis-docs/docs/Intro/installation.md b/artemis-docs/docs/Intro/installation.md index 4cb52f7f..03f5d6a2 100644 --- a/artemis-docs/docs/Intro/installation.md +++ b/artemis-docs/docs/Intro/installation.md @@ -54,6 +54,7 @@ Artemis also supports the following platforms: - Android - Linux (RISC-V, musl, PowerPC, SPARC) - [illumos](https://en.wikipedia.org/wiki/Illumos) +- ESXi ## GitHub Releases diff --git a/artemis-docs/package-lock.json b/artemis-docs/package-lock.json index d27001e3..fe914d2d 100644 --- a/artemis-docs/package-lock.json +++ b/artemis-docs/package-lock.json @@ -8,9 +8,9 @@ "name": "artemis-docs", "version": "0.0.0", "dependencies": { - "@docusaurus/core": "^3.9.2", - "@docusaurus/preset-classic": "^3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", + "@docusaurus/core": "^3.10.0", + "@docusaurus/preset-classic": "^3.10.0", + "@docusaurus/theme-mermaid": "^3.10.0", "@mdx-js/react": "^3.1.1", "clsx": "^2.1.1", "prism-react-renderer": "^2.4.1", @@ -18,7 +18,7 @@ "react-dom": "^19.2.0" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.9.2", + "@docusaurus/module-type-aliases": "^3.10.0", "@tsconfig/docusaurus": "^2.0.7", "typescript": "~5.9.3" }, @@ -27,46 +27,46 @@ } }, "node_modules/@algolia/abtesting": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.14.0.tgz", - "integrity": "sha512-cZfj+1Z1dgrk3YPtNQNt0H9Rr67P8b4M79JjUKGS0d7/EbFbGxGgSu6zby5f22KXo3LT0LZa4O2c6VVbupJuDg==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.16.1.tgz", + "integrity": "sha512-Xxk4l00pYI+jE0PNw8y0MvsQWh5278WRtZQav8/BMMi3HKi2xmeuqe11WJ3y8/6nuBHdv39w76OpJb09TMfAVQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/autocomplete-core": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", - "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.8.tgz", + "integrity": "sha512-3YEorYg44niXcm7gkft3nXYItHd44e8tmh4D33CTszPgP0QWkaLEaFywiNyJBo7UL/mqObA/G9RYuU7R8tN1IA==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", - "@algolia/autocomplete-shared": "1.19.2" + "@algolia/autocomplete-plugin-algolia-insights": "1.19.8", + "@algolia/autocomplete-shared": "1.19.8" } }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", - "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.8.tgz", + "integrity": "sha512-ZvJWO8ZZJDpc1LNM2TTBdmQsZBLMR4rU5iNR2OYvEeFBiaf/0ESnRSSLQbryarJY4SVxtoz6A2ZtDMNM+iQEAA==", "license": "MIT", "dependencies": { - "@algolia/autocomplete-shared": "1.19.2" + "@algolia/autocomplete-shared": "1.19.8" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "node_modules/@algolia/autocomplete-shared": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", - "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "version": "1.19.8", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.8.tgz", + "integrity": "sha512-h5hf2t8ejF6vlOgvLaZzQbWs5SyH2z4PAWygNAvvD/2RI29hdQ54ldUGwqVuj9Srs+n8XUKTPUqb7fvhBhQrnQ==", "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -74,99 +74,99 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.48.0.tgz", - "integrity": "sha512-n17WSJ7vazmM6yDkWBAjY12J8ERkW9toOqNgQ1GEZu/Kc4dJDJod1iy+QP5T/UlR3WICgZDi/7a/VX5TY5LAPQ==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.50.1.tgz", + "integrity": "sha512-4peZlPXMwTOey9q1rQKMdCnwZb/E95/1e+7KujXpLLSh0FawJzg//U2NM+r4AiJy4+naT2MTBhj0K30yshnVTA==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.48.0.tgz", - "integrity": "sha512-v5bMZMEqW9U2l40/tTAaRyn4AKrYLio7KcRuHmLaJtxuJAhvZiE7Y62XIsF070juz4MN3eyvfQmI+y5+OVbZuA==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.50.1.tgz", + "integrity": "sha512-i+aWHHG8NZvGFHtPeMZkxL2Loc6Fm7iaRo15lYSMx8gFL+at9vgdWxhka7mD1fqxkrxXsQstUBCIsSY8FvkEOw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.48.0.tgz", - "integrity": "sha512-7H3DgRyi7UByScc0wz7EMrhgNl7fKPDjKX9OcWixLwCj7yrRXDSIzwunykuYUUO7V7HD4s319e15FlJ9CQIIFQ==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.50.1.tgz", + "integrity": "sha512-Hw52Fwapyk/7hMSV/fI4+s3H9MGZEUcRh4VphyXLAk2oLYdndVUkc6KBi0zwHSzwPAr+ZBwFPe2x6naUt9mZGw==", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.48.0.tgz", - "integrity": "sha512-tXmkB6qrIGAXrtRYHQNpfW0ekru/qymV02bjT0w5QGaGw0W91yT+53WB6dTtRRsIrgS30Al6efBvyaEosjZ5uw==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.50.1.tgz", + "integrity": "sha512-Bn/wtwhJ7p1OD/6pY+Zzn+zlu2N/SJnH46md/PAbvqIzmjVuwjNwD4y0vV5Ov8naeukXdd7UU9v550+v8+mtlg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.48.0.tgz", - "integrity": "sha512-4tXEsrdtcBZbDF73u14Kb3otN+xUdTVGop1tBjict+Rc/FhsJQVIwJIcTrOJqmvhtBfc56Bu65FiVOnpAZCxcw==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.50.1.tgz", + "integrity": "sha512-0V4Tu0RWR8YxkgI9EPVOZHGE4K5pEIhkLNN0CTkP/rnPsqaaSQpNMYW3/mGWdiKOWbX0iVmwLB9QESk3H0jS5g==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.48.0.tgz", - "integrity": "sha512-unzSUwWFpsDrO8935RhMAlyK0Ttua/5XveVIwzfjs5w+GVBsHgIkbOe8VbBJccMU/z1LCwvu1AY3kffuSLAR5Q==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.1.tgz", + "integrity": "sha512-jofcWNYMXJDDr87Z2eivlWY6o71Zn7F7aOvQCXSDAo9QTlyf7BhXEsZymLUvF0O1yU9Q9wvrjAWn8uVHYnAvgw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.48.0.tgz", - "integrity": "sha512-RB9bKgYTVUiOcEb5bOcZ169jiiVW811dCsJoLT19DcbbFmU4QaK0ghSTssij35QBQ3SCOitXOUrHcGgNVwS7sQ==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.50.1.tgz", + "integrity": "sha512-OteRb8WubcmEvU0YlMJwCXs3Q6xrdkb0v50/qZBJP1TF0CvujFZQM++9BjEkTER/Jr9wbPHvjSFKnbMta0b4dQ==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" @@ -179,81 +179,81 @@ "license": "MIT" }, "node_modules/@algolia/ingestion": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.48.0.tgz", - "integrity": "sha512-rhoSoPu+TDzDpvpk3cY/pYgbeWXr23DxnAIH/AkN0dUC+GCnVIeNSQkLaJ+CL4NZ51cjLIjksrzb4KC5Xu+ktw==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.50.1.tgz", + "integrity": "sha512-0GmfSgDQK6oiIVXnJvGxtNFOfosBspRTR7csCOYCTL1P8QtxX2vDCIKwTM7xdSAEbJaZ43QlWg25q0Qdsndz8Q==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.48.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.48.0.tgz", - "integrity": "sha512-aSe6jKvWt+8VdjOaq2ERtsXp9+qMXNJ3mTyTc1VMhNfgPl7ArOhRMRSQ8QBnY8ZL4yV5Xpezb7lAg8pdGrrulg==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.50.1.tgz", + "integrity": "sha512-ySuigKEe4YjYV3si8NVk9BHQpFj/1B+ON7DhhvTvbrZJseHQQloxzq0yHwKmznSdlO6C956fx4pcfOKkZClsyg==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.48.0.tgz", - "integrity": "sha512-p9tfI1bimAaZrdiVExL/dDyGUZ8gyiSHsktP1ZWGzt5hXpM3nhv4tSjyHtXjEKtA0UvsaHKwSfFE8aAAm1eIQA==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.50.1.tgz", + "integrity": "sha512-Cp8T/B0gVmjFlzzp6eP47hwKh5FGyeqQp1N48/ANDdvdiQkPqLyFHQVDwLBH0LddfIPQE+yqmZIgmKc82haF4A==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "@algolia/client-common": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.48.0.tgz", - "integrity": "sha512-XshyfpsQB7BLnHseMinp3fVHOGlTv6uEHOzNK/3XrEF9mjxoZAcdVfY1OCXObfwRWX5qXZOq8FnrndFd44iVsQ==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.1.tgz", + "integrity": "sha512-XKdGGLikfrlK66ZSXh/vWcXZZ8Vg3byDFbJD8pwEvN1FoBRGxhxya476IY2ohoTymLa4qB5LBRlIa+2TLHx3Uw==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0" + "@algolia/client-common": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.48.0.tgz", - "integrity": "sha512-Q4XNSVQU89bKNAPuvzSYqTH9AcbOOiIo6AeYMQTxgSJ2+uvT78CLPMG89RIIloYuAtSfE07s40OLV50++l1Bbw==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.50.1.tgz", + "integrity": "sha512-mBAU6WyVsDwhHyGM+nodt1/oebHxgvuLlOAoMGbj/1i6LygDHZWDgL1t5JEs37x9Aywv7ZGhqbM1GsfZ54sU6g==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0" + "@algolia/client-common": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.48.0.tgz", - "integrity": "sha512-ZgxV2+5qt3NLeUYBTsi6PLyHcENQWC0iFppFZekHSEDA2wcLdTUjnaJzimTEULHIvJuLRCkUs4JABdhuJktEag==", + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.50.1.tgz", + "integrity": "sha512-qmo1LXrNKLHvJE6mdQbLnsZAoZvj7VyF2ft4xmbSGWI2WWm87fx/CjUX4kEExt4y0a6T6nEts6ofpUfH5TEE1A==", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.48.0" + "@algolia/client-common": "5.50.1" }, "engines": { "node": ">= 14.0.0" @@ -444,9 +444,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz", - "integrity": "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", @@ -621,22 +621,22 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -1761,9 +1761,9 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", - "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.2.tgz", + "integrity": "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.0", @@ -1845,12 +1845,12 @@ } }, "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", - "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "core-js-compat": "^3.48.0" }, "peerDependencies": { @@ -1920,22 +1920,10 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.0.tgz", - "integrity": "sha512-TgUkdp71C9pIbBcHudc+gXZnihEDOjUAmXO1VO4HHGES7QLZcShR0stfKIxLSNIYx2fqhmJChOjm/wkF8wv4gA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "dependencies": { - "core-js-pure": "^3.48.0" - }, "engines": { "node": ">=6.9.0" } @@ -1992,54 +1980,40 @@ "license": "MIT" }, "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-12.0.0.tgz", + "integrity": "sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/gast": "12.0.0", + "@chevrotain/types": "12.0.0" } }, - "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-12.0.0.tgz", + "integrity": "sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/types": "12.0.0" } }, - "node_modules/@chevrotain/gast/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-12.0.0.tgz", + "integrity": "sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==", "license": "Apache-2.0" }, "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-12.0.0.tgz", + "integrity": "sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==", "license": "Apache-2.0" }, "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-12.0.0.tgz", + "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", "license": "Apache-2.0" }, "node_modules/@colors/colors": { @@ -3378,9 +3352,9 @@ } }, "node_modules/@docsearch/core": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.5.4.tgz", - "integrity": "sha512-DbkfZbJyYAPFJtF71eAFOTQSy5z5c/hdSN0UrErORKDwXKLTJBR0c+5WxE5l+IKZx4xIaEa8RkrL7T28DTCOYw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/core/-/core-4.6.2.tgz", + "integrity": "sha512-/S0e6Dj7Zcm8m9Rru49YEX49dhU11be68c+S/BCyN8zQsTTgkKzXlhRbVL5mV6lOLC2+ZRRryaTdcm070Ug2oA==", "license": "MIT", "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -3400,20 +3374,20 @@ } }, "node_modules/@docsearch/css": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.5.4.tgz", - "integrity": "sha512-gzO4DJwyM9c4YEPHwaLV1nUCDC2N6yoh0QJj44dce2rcfN71mB+jpu3+F+Y/KMDF1EKV0C3m54leSWsraE94xg==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-4.6.2.tgz", + "integrity": "sha512-fH/cn8BjEEdM2nJdjNMHIvOVYupG6AIDtFVDgIZrNzdCSj4KXr9kd+hsehqsNGYjpUjObeKYKvgy/IwCb1jZYQ==", "license": "MIT" }, "node_modules/@docsearch/react": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.5.4.tgz", - "integrity": "sha512-iBNFfvWoUFRUJmGQ/r+0AEp2OJgJMoYIKRiRcTDON0hObBRSLlrv2ktb7w3nc1MeNm1JIpbPA99i59TiIR49fA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-4.6.2.tgz", + "integrity": "sha512-/BbtGFtqVOGwZx0dw/UfhN/0/DmMQYnulY4iv0tPRhC2JCXv0ka/+izwt3Jzo1ZxXS/2eMvv9zHsBJOK1I9f/w==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.19.2", - "@docsearch/core": "4.5.4", - "@docsearch/css": "4.5.4" + "@docsearch/core": "4.6.2", + "@docsearch/css": "4.6.2" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 20.0.0", @@ -3436,10 +3410,42 @@ } } }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-core": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.19.2.tgz", + "integrity": "sha512-mKv7RyuAzXvwmq+0XRK8HqZXt9iZ5Kkm2huLjgn5JoCPtDy+oh9yxUMfDDaVCw0oyzZ1isdJBc7l9nuCyyR7Nw==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.19.2", + "@algolia/autocomplete-shared": "1.19.2" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.19.2.tgz", + "integrity": "sha512-TjxbcC/r4vwmnZaPwrHtkXNeqvlpdyR+oR9Wi2XyfORkiGkLTVhX2j+O9SaCCINbKoDfc+c2PB8NjfOnz7+oKg==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.19.2" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@docsearch/react/node_modules/@algolia/autocomplete-shared": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.19.2.tgz", + "integrity": "sha512-jEazxZTVD2nLrC+wYlVHQgpBoBB5KPStrJxLzsIFl6Kqd1AlG9sIAGl39V5tECLpIQzB3Qa2T6ZPJ1ChkwMK/w==", + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, "node_modules/@docusaurus/babel": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.2.tgz", - "integrity": "sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.10.0.tgz", + "integrity": "sha512-mqCJhCZNZUDg0zgDEaPTM4DnRsisa24HdqTy/qn/MQlbwhTb4WVaZg6ZyX6yIVKqTz8fS1hBMgM+98z+BeJJDg==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", @@ -3450,10 +3456,9 @@ "@babel/preset-react": "^7.25.9", "@babel/preset-typescript": "^7.25.9", "@babel/runtime": "^7.25.9", - "@babel/runtime-corejs3": "^7.25.9", "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", + "@docusaurus/logger": "3.10.0", + "@docusaurus/utils": "3.10.0", "babel-plugin-dynamic-import-node": "^2.3.3", "fs-extra": "^11.1.1", "tslib": "^2.6.0" @@ -3463,17 +3468,17 @@ } }, "node_modules/@docusaurus/bundler": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.2.tgz", - "integrity": "sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.10.0.tgz", + "integrity": "sha512-iONUGZGgp+lAkw/cJZH6irONcF4p8+278IsdRlq8lYhxGjkoNUs0w7F4gVXBYSNChq5KG5/JleTSsdJySShxow==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.9.2", - "@docusaurus/cssnano-preset": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", + "@docusaurus/babel": "3.10.0", + "@docusaurus/cssnano-preset": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", "babel-loader": "^9.2.1", "clean-css": "^5.3.3", "copy-webpack-plugin": "^11.0.0", @@ -3506,18 +3511,18 @@ } }, "node_modules/@docusaurus/core": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", - "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", - "license": "MIT", - "dependencies": { - "@docusaurus/babel": "3.9.2", - "@docusaurus/bundler": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.10.0.tgz", + "integrity": "sha512-mgLdQsO8xppnQZc3LPi+Mf+PkPeyxJeIx11AXAq/14fsaMefInQiMEZUUmrc7J+956G/f7MwE7tn8KZgi3iRcA==", + "license": "MIT", + "dependencies": { + "@docusaurus/babel": "3.10.0", + "@docusaurus/bundler": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "boxen": "^6.2.1", "chalk": "^4.1.2", "chokidar": "^3.5.3", @@ -3529,7 +3534,7 @@ "escape-html": "^1.0.3", "eta": "^2.2.0", "eval": "^0.1.8", - "execa": "5.1.1", + "execa": "^5.1.1", "fs-extra": "^11.1.1", "html-tags": "^3.3.1", "html-webpack-plugin": "^5.6.0", @@ -3540,12 +3545,12 @@ "prompts": "^2.4.2", "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", + "react-loadable-ssr-addon-v5-slorber": "^1.0.3", "react-router": "^5.3.4", "react-router-config": "^5.1.1", "react-router-dom": "^5.3.4", "semver": "^7.5.4", - "serve-handler": "^6.1.6", + "serve-handler": "^6.1.7", "tinypool": "^1.0.2", "tslib": "^2.6.0", "update-notifier": "^6.0.2", @@ -3561,15 +3566,21 @@ "node": ">=20.0" }, "peerDependencies": { + "@docusaurus/faster": "*", "@mdx-js/react": "^3.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@docusaurus/faster": { + "optional": true + } } }, "node_modules/@docusaurus/cssnano-preset": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz", - "integrity": "sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.10.0.tgz", + "integrity": "sha512-qzSshTO1DB3TYW+dPUal5KHM7XPc5YQfzF3Kdb2NDACJUyGbNcFtw3tGkCJlYwhNCRKbZcmwraKUS1i5dcHdGg==", "license": "MIT", "dependencies": { "cssnano-preset-advanced": "^6.1.2", @@ -3582,9 +3593,9 @@ } }, "node_modules/@docusaurus/logger": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.2.tgz", - "integrity": "sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.10.0.tgz", + "integrity": "sha512-9jrZzFuBH1LDRlZ7cznAhCLmAZ3HSDqgwdrSSZdGHq9SPUOQgXXu8mnxe2ZRB9NS1PCpMTIOVUqDtZPIhMafZg==", "license": "MIT", "dependencies": { "chalk": "^4.1.2", @@ -3595,14 +3606,14 @@ } }, "node_modules/@docusaurus/mdx-loader": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz", - "integrity": "sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.10.0.tgz", + "integrity": "sha512-mQQV97080AH4PYNs087l202NMDqRopZA4mg5W76ZZyTFrmWhJ3mHg+8A+drJVENxw5/Q+wHMHLgsx+9z1nEs0A==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/logger": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "@mdx-js/mdx": "^3.0.0", "@slorber/remark-comment": "^1.0.0", "escape-html": "^1.0.3", @@ -3634,12 +3645,12 @@ } }, "node_modules/@docusaurus/module-type-aliases": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", - "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.10.0.tgz", + "integrity": "sha512-/1O0Zg8w3DFrYX/I6Fbss7OJrtZw1QoyjDhegiFNHVi9A9Y0gQ3jUAytVxF6ywpAWpLyLxch8nN8H/V3XfzdJQ==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.9.2", + "@docusaurus/types": "3.10.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -3653,20 +3664,21 @@ } }, "node_modules/@docusaurus/plugin-content-blog": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.9.2.tgz", - "integrity": "sha512-3I2HXy3L1QcjLJLGAoTvoBnpOwa6DPUa3Q0dMK19UTY9mhPkKQg/DYhAGTiBUKcTR0f08iw7kLPqOhIgdV3eVQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.10.0.tgz", + "integrity": "sha512-RuTz68DhB7CL96QO5UsFbciD7GPYq6QV+YMfF9V0+N4ZgLhJIBgpVAr8GobrKF6NRe5cyWWETU5z5T834piG9g==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "cheerio": "1.0.0-rc.12", + "combine-promises": "^1.1.0", "feed": "^4.2.2", "fs-extra": "^11.1.1", "lodash": "^4.17.21", @@ -3687,20 +3699,20 @@ } }, "node_modules/@docusaurus/plugin-content-docs": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.9.2.tgz", - "integrity": "sha512-C5wZsGuKTY8jEYsqdxhhFOe1ZDjH0uIYJ9T/jebHwkyxqnr4wW0jTkB72OMqNjsoQRcb0JN3PcSeTwFlVgzCZg==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.10.0.tgz", + "integrity": "sha512-9BjHhf15ct8Z7TThTC0xRndKDVvMKmVsAGAN7W9FpNRzfMdScOGcXtLmcCWtJGvAezjOJIm6CxOYCy3Io5+RnQ==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "@types/react-router-config": "^5.0.7", "combine-promises": "^1.1.0", "fs-extra": "^11.1.1", @@ -3720,16 +3732,16 @@ } }, "node_modules/@docusaurus/plugin-content-pages": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.9.2.tgz", - "integrity": "sha512-s4849w/p4noXUrGpPUF0BPqIAfdAe76BLaRGAGKZ1gTDNiGxGcpsLcwJ9OTi1/V8A+AzvsmI9pkjie2zjIQZKA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.10.0.tgz", + "integrity": "sha512-5amX8kEJI+nIGtuLVjYk59Y5utEJ3CHETFOPEE4cooIRLA4xM4iBsA6zFgu4ljcopeYwvBzFEWf5g2I6Yb9SkA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "fs-extra": "^11.1.1", "tslib": "^2.6.0", "webpack": "^5.88.1" @@ -3743,15 +3755,15 @@ } }, "node_modules/@docusaurus/plugin-css-cascade-layers": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.9.2.tgz", - "integrity": "sha512-w1s3+Ss+eOQbscGM4cfIFBlVg/QKxyYgj26k5AnakuHkKxH6004ZtuLe5awMBotIYF2bbGDoDhpgQ4r/kcj4rQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-css-cascade-layers/-/plugin-css-cascade-layers-3.10.0.tgz", + "integrity": "sha512-6q1vtt5FJcg5osgkHeM1euErECNqEZ5Z1j69yiNx2luEBIso+nxCkS9nqj8w+MK5X7rvKEToGhFfOFWncs51pQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "tslib": "^2.6.0" }, "engines": { @@ -3759,14 +3771,14 @@ } }, "node_modules/@docusaurus/plugin-debug": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.9.2.tgz", - "integrity": "sha512-j7a5hWuAFxyQAkilZwhsQ/b3T7FfHZ+0dub6j/GxKNFJp2h9qk/P1Bp7vrGASnvA9KNQBBL1ZXTe7jlh4VdPdA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.10.0.tgz", + "integrity": "sha512-XcljKN+G+nmmK69uQA1d9BlYU3ZftG3T3zpK8/7Hf/wrOlV7TA4Ampdrdwkg0jElKdKAoSnPhCO0/U3bQGsVQQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", "fs-extra": "^11.1.1", "react-json-view-lite": "^2.3.0", "tslib": "^2.6.0" @@ -3780,14 +3792,14 @@ } }, "node_modules/@docusaurus/plugin-google-analytics": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.9.2.tgz", - "integrity": "sha512-mAwwQJ1Us9jL/lVjXtErXto4p4/iaLlweC54yDUK1a97WfkC6Z2k5/769JsFgwOwOP+n5mUQGACXOEQ0XDuVUw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.10.0.tgz", + "integrity": "sha512-hTEoodatpBZnUat5nFExbuTGA1lhWGy7vZGuTew5Q3QDtGKFpSJLYmZJhdTjvCFwv1+qQ67hgAVlKdJOB8TXow==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "tslib": "^2.6.0" }, "engines": { @@ -3799,15 +3811,15 @@ } }, "node_modules/@docusaurus/plugin-google-gtag": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.9.2.tgz", - "integrity": "sha512-YJ4lDCphabBtw19ooSlc1MnxtYGpjFV9rEdzjLsUnBCeis2djUyCozZaFhCg6NGEwOn7HDDyMh0yzcdRpnuIvA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.10.0.tgz", + "integrity": "sha512-iB/Zzjv/eelJRbdULZqzWCbgMgJ7ht4ONVjXtN3+BI/muil6S87gQ1OJyPwlXD+ELdKkitC7bWv5eJdYOZLhrQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@types/gtag.js": "^0.0.12", + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", + "@types/gtag.js": "^0.0.20", "tslib": "^2.6.0" }, "engines": { @@ -3819,14 +3831,14 @@ } }, "node_modules/@docusaurus/plugin-google-tag-manager": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.9.2.tgz", - "integrity": "sha512-LJtIrkZN/tuHD8NqDAW1Tnw0ekOwRTfobWPsdO15YxcicBo2ykKF0/D6n0vVBfd3srwr9Z6rzrIWYrMzBGrvNw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.10.0.tgz", + "integrity": "sha512-FEjZxqKgLHa+Wez/EgKxRwvArNCWIScfyEQD95rot7jkxp6nonjI5XIbGfO/iYhM5Qinwe8aIEQHP2KZtpqVuA==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "tslib": "^2.6.0" }, "engines": { @@ -3838,17 +3850,17 @@ } }, "node_modules/@docusaurus/plugin-sitemap": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.9.2.tgz", - "integrity": "sha512-WLh7ymgDXjG8oPoM/T4/zUP7KcSuFYRZAUTl8vR6VzYkfc18GBM4xLhcT+AKOwun6kBivYKUJf+vlqYJkm+RHw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.10.0.tgz", + "integrity": "sha512-DVTSLjB97hIjmayGnGcBfognCeI7ZuUKgEnU7Oz81JYqXtVg94mVTthDjq3QHTylYNeCUbkaW8VF0FDLcc8pPw==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "fs-extra": "^11.1.1", "sitemap": "^7.1.1", "tslib": "^2.6.0" @@ -3862,15 +3874,15 @@ } }, "node_modules/@docusaurus/plugin-svgr": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.9.2.tgz", - "integrity": "sha512-n+1DE+5b3Lnf27TgVU5jM1d4x5tUh2oW5LTsBxJX4PsAPV0JGcmI6p3yLYtEY0LRVEIJh+8RsdQmRE66wSV8mw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-svgr/-/plugin-svgr-3.10.0.tgz", + "integrity": "sha512-lNljBESaETZqVBMPqkrGchr+UPT1eZzEPLmJhz8I76BxbjqgsUnRvrq6lQJ9sYjgmgX52KB7kkgczqd2yzoswQ==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "@svgr/core": "8.1.0", "@svgr/webpack": "^8.1.0", "tslib": "^2.6.0", @@ -3885,26 +3897,26 @@ } }, "node_modules/@docusaurus/preset-classic": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.9.2.tgz", - "integrity": "sha512-IgyYO2Gvaigi21LuDIe+nvmN/dfGXAiMcV/murFqcpjnZc7jxFAxW+9LEjdPt61uZLxG4ByW/oUmX/DDK9t/8w==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/plugin-content-blog": "3.9.2", - "@docusaurus/plugin-content-docs": "3.9.2", - "@docusaurus/plugin-content-pages": "3.9.2", - "@docusaurus/plugin-css-cascade-layers": "3.9.2", - "@docusaurus/plugin-debug": "3.9.2", - "@docusaurus/plugin-google-analytics": "3.9.2", - "@docusaurus/plugin-google-gtag": "3.9.2", - "@docusaurus/plugin-google-tag-manager": "3.9.2", - "@docusaurus/plugin-sitemap": "3.9.2", - "@docusaurus/plugin-svgr": "3.9.2", - "@docusaurus/theme-classic": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/theme-search-algolia": "3.9.2", - "@docusaurus/types": "3.9.2" + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.10.0.tgz", + "integrity": "sha512-kw/Ye02Hc6xP1OdTswy8yxQEHg0fdPpyWAQRxr5b2x3h7LlG2Zgbb5BDFROnXDDMpUxB7YejlocJIE5HIEfpNA==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/plugin-content-blog": "3.10.0", + "@docusaurus/plugin-content-docs": "3.10.0", + "@docusaurus/plugin-content-pages": "3.10.0", + "@docusaurus/plugin-css-cascade-layers": "3.10.0", + "@docusaurus/plugin-debug": "3.10.0", + "@docusaurus/plugin-google-analytics": "3.10.0", + "@docusaurus/plugin-google-gtag": "3.10.0", + "@docusaurus/plugin-google-tag-manager": "3.10.0", + "@docusaurus/plugin-sitemap": "3.10.0", + "@docusaurus/plugin-svgr": "3.10.0", + "@docusaurus/theme-classic": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/theme-search-algolia": "3.10.0", + "@docusaurus/types": "3.10.0" }, "engines": { "node": ">=20.0" @@ -3915,26 +3927,27 @@ } }, "node_modules/@docusaurus/theme-classic": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.9.2.tgz", - "integrity": "sha512-IGUsArG5hhekXd7RDb11v94ycpJpFdJPkLnt10fFQWOVxAtq5/D7hT6lzc2fhyQKaaCE62qVajOMKL7OiAFAIA==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/plugin-content-blog": "3.9.2", - "@docusaurus/plugin-content-docs": "3.9.2", - "@docusaurus/plugin-content-pages": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/theme-translations": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.10.0.tgz", + "integrity": "sha512-9msCAsRdN+UG+RwPwCFb0uKy4tGoPh5YfBozXeGUtIeAgsMdn6f3G/oY861luZ3t8S2ET8S9Y/1GnpJAGWytww==", + "license": "MIT", + "dependencies": { + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/plugin-content-blog": "3.10.0", + "@docusaurus/plugin-content-docs": "3.10.0", + "@docusaurus/plugin-content-pages": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/theme-translations": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", + "copy-text-to-clipboard": "^3.2.0", "infima": "0.2.0-alpha.45", "lodash": "^4.17.21", "nprogress": "^0.2.0", @@ -3955,15 +3968,15 @@ } }, "node_modules/@docusaurus/theme-common": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", - "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.10.0.tgz", + "integrity": "sha512-Dkp1YXKn16ByCJAdIjbDIOpVb4Z66MsVD694/ilX1vAAHaVEMrVsf/NPd9VgreyFx08rJ9GqV1MtzsbTcU73Kg==", "license": "MIT", "dependencies": { - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", + "@docusaurus/mdx-loader": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", "@types/history": "^4.7.11", "@types/react": "*", "@types/react-router-config": "*", @@ -3983,16 +3996,16 @@ } }, "node_modules/@docusaurus/theme-mermaid": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz", - "integrity": "sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.10.0.tgz", + "integrity": "sha512-Y2xrlwhIJ80oOZIO3PXL6A7J869splfcMI87E3NKpYsy3zJxOyV+BP1QMtGi59ajKgU868HPuyyn6J+6BZGOBg==", "license": "MIT", "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/module-type-aliases": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "mermaid": ">=11.6.0", "tslib": "^2.6.0" }, @@ -4011,19 +4024,20 @@ } }, "node_modules/@docusaurus/theme-search-algolia": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.9.2.tgz", - "integrity": "sha512-GBDSFNwjnh5/LdkxCKQHkgO2pIMX1447BxYUBG2wBiajS21uj64a+gH/qlbQjDLxmGrbrllBrtJkUHxIsiwRnw==", - "license": "MIT", - "dependencies": { - "@docsearch/react": "^3.9.0 || ^4.1.0", - "@docusaurus/core": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/plugin-content-docs": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/theme-translations": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.10.0.tgz", + "integrity": "sha512-f5FPKI08e3JRG63vR/o4qeuUVHUHzFzM0nnF+AkB67soAZgNsKJRf2qmUZvlQkGwlV+QFkKe4D0ANMh1jToU3g==", + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "^1.19.2", + "@docsearch/react": "^3.9.0 || ^4.3.2", + "@docusaurus/core": "3.10.0", + "@docusaurus/logger": "3.10.0", + "@docusaurus/plugin-content-docs": "3.10.0", + "@docusaurus/theme-common": "3.10.0", + "@docusaurus/theme-translations": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-validation": "3.10.0", "algoliasearch": "^5.37.0", "algoliasearch-helper": "^3.26.0", "clsx": "^2.0.0", @@ -4042,9 +4056,9 @@ } }, "node_modules/@docusaurus/theme-translations": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.9.2.tgz", - "integrity": "sha512-vIryvpP18ON9T9rjgMRFLr2xJVDpw1rtagEGf8Ccce4CkTrvM/fRB8N2nyWYOW5u3DdjkwKw5fBa+3tbn9P4PA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.10.0.tgz", + "integrity": "sha512-L9IbFLwTc5+XdgH45iQYufLn0SVZd6BUNelDbKIFlH+E4hhjuj/XHWAFMX/w2K59rfy8wak9McOaei7BSUfRPA==", "license": "MIT", "dependencies": { "fs-extra": "^11.1.1", @@ -4055,9 +4069,9 @@ } }, "node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.10.0.tgz", + "integrity": "sha512-F0dOt3FOoO20rRaFK7whGFQZ3ggyrWEdQc/c8/UiRuzhtg4y1w9FspXH5zpCT07uMnJKBPGh+qNazbNlCQqvSw==", "license": "MIT", "dependencies": { "@mdx-js/mdx": "^3.0.0", @@ -4091,16 +4105,16 @@ } }, "node_modules/@docusaurus/utils": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz", - "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.10.0.tgz", + "integrity": "sha512-T3B0WTigsIthe0D4LQa2k+7bJY+c3WS+Wq2JhcznOSpn1lSN64yNtHQXboCj3QnUs1EuAZszQG1SHKu5w5ZrlA==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-common": "3.9.2", + "@docusaurus/logger": "3.10.0", + "@docusaurus/types": "3.10.0", + "@docusaurus/utils-common": "3.10.0", "escape-string-regexp": "^4.0.0", - "execa": "5.1.1", + "execa": "^5.1.1", "file-loader": "^6.2.0", "fs-extra": "^11.1.1", "github-slugger": "^1.5.0", @@ -4123,12 +4137,12 @@ } }, "node_modules/@docusaurus/utils-common": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.2.tgz", - "integrity": "sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.10.0.tgz", + "integrity": "sha512-JyL7sb9QVDgYvudIS81Dv0lsWm7le0vGZSDwsztxWam1SPBqrnkvBy9UYL/amh6pbybkyYTd3CMTkO24oMlCSw==", "license": "MIT", "dependencies": { - "@docusaurus/types": "3.9.2", + "@docusaurus/types": "3.10.0", "tslib": "^2.6.0" }, "engines": { @@ -4136,14 +4150,14 @@ } }, "node_modules/@docusaurus/utils-validation": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz", - "integrity": "sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.10.0.tgz", + "integrity": "sha512-c+6n2+ZPOJtWWc8Bb/EYdpSDfjYEScdCu9fB/SNjOmSCf1IdVnGf2T53o0tsz0gDRtCL90tifTL0JE/oMuP1Mw==", "license": "MIT", "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", + "@docusaurus/logger": "3.10.0", + "@docusaurus/utils": "3.10.0", + "@docusaurus/utils-common": "3.10.0", "fs-extra": "^11.2.0", "joi": "^17.9.2", "js-yaml": "^4.1.0", @@ -4319,13 +4333,13 @@ } }, "node_modules/@jsonjoy.com/fs-core": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", - "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.57.1.tgz", + "integrity": "sha512-YrEi/ZPmgc+GfdO0esBF04qv8boK9Dg9WpRQw/+vM8Qt3nnVIJWIa8HwZ/LXVZ0DB11XUROM8El/7yYTJX+WtA==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", "thingies": "^2.5.0" }, "engines": { @@ -4340,14 +4354,14 @@ } }, "node_modules/@jsonjoy.com/fs-fsa": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", - "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.57.1.tgz", + "integrity": "sha512-ooEPvSW/HQDivPDPZMibHGKZf/QS4WRir1czGZmXmp3MsQqLECZEpN0JobrD8iV9BzsuwdIv+PxtWX9WpPLsIA==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", "thingies": "^2.5.0" }, "engines": { @@ -4362,16 +4376,16 @@ } }, "node_modules/@jsonjoy.com/fs-node": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", - "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.57.1.tgz", + "integrity": "sha512-3YaKhP8gXEKN+2O49GLNfNb5l2gbnCFHyAaybbA2JkkbQP3dpdef7WcUaHAulg/c5Dg4VncHsA3NWAUSZMR5KQ==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", - "@jsonjoy.com/fs-print": "4.56.10", - "@jsonjoy.com/fs-snapshot": "4.56.10", + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/fs-print": "4.57.1", + "@jsonjoy.com/fs-snapshot": "4.57.1", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, @@ -4387,9 +4401,9 @@ } }, "node_modules/@jsonjoy.com/fs-node-builtins": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", - "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.57.1.tgz", + "integrity": "sha512-XHkFKQ5GSH3uxm8c3ZYXVrexGdscpWKIcMWKFQpMpMJc8gA3AwOMBJXJlgpdJqmrhPyQXxaY9nbkNeYpacC0Og==", "license": "Apache-2.0", "engines": { "node": ">=10.0" @@ -4403,14 +4417,14 @@ } }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", - "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.57.1.tgz", + "integrity": "sha512-pqGHyWWzNck4jRfaGV39hkqpY5QjRUQ/nRbNT7FYbBa0xf4bDG+TE1Gt2KWZrSkrkZZDE3qZUjYMbjwSliX6pg==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-fsa": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10" + "@jsonjoy.com/fs-fsa": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1" }, "engines": { "node": ">=10.0" @@ -4424,12 +4438,12 @@ } }, "node_modules/@jsonjoy.com/fs-node-utils": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", - "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.57.1.tgz", + "integrity": "sha512-vp+7ZzIB8v43G+GLXTS4oDUSQmhAsRz532QmmWBbdYA20s465JvwhkSFvX9cVTqRRAQg+vZ7zWDaIEh0lFe2gw==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-builtins": "4.56.10" + "@jsonjoy.com/fs-node-builtins": "4.57.1" }, "engines": { "node": ">=10.0" @@ -4443,12 +4457,12 @@ } }, "node_modules/@jsonjoy.com/fs-print": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", - "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.57.1.tgz", + "integrity": "sha512-Ynct7ZJmfk6qoXDOKfpovNA36ITUx8rChLmRQtW08J73VOiuNsU8PB6d/Xs7fxJC2ohWR3a5AqyjmLojfrw5yw==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.57.1", "tree-dump": "^1.1.0" }, "engines": { @@ -4463,13 +4477,13 @@ } }, "node_modules/@jsonjoy.com/fs-snapshot": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", - "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.57.1.tgz", + "integrity": "sha512-/oG8xBNFMbDXTq9J7vepSA1kerS5vpgd3p5QZSPd+nX59uwodGJftI51gDYyHRpP57P3WCQf7LHtBYPqwUg2Bg==", "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", - "@jsonjoy.com/fs-node-utils": "4.56.10", + "@jsonjoy.com/fs-node-utils": "4.57.1", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, @@ -4740,12 +4754,12 @@ } }, "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.0.tgz", + "integrity": "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==", "license": "MIT", "dependencies": { - "langium": "3.3.1" + "langium": "^4.0.0" } }, "node_modules/@noble/hashes": { @@ -4796,92 +4810,92 @@ } }, "node_modules/@peculiar/asn1-cms": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", - "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", + "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "@peculiar/asn1-x509-attr": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-csr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", - "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", + "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-ecc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", - "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", + "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pfx": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", - "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", + "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-pkcs8": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", + "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", - "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", + "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", - "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", + "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", "license": "MIT", "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-pfx": "^2.6.0", - "@peculiar/asn1-pkcs8": "^2.6.0", + "@peculiar/asn1-cms": "^2.6.1", + "@peculiar/asn1-pfx": "^2.6.1", + "@peculiar/asn1-pkcs8": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "@peculiar/asn1-x509-attr": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", + "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-rsa": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", - "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", + "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } @@ -4898,9 +4912,9 @@ } }, "node_modules/@peculiar/asn1-x509": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", - "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", + "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", @@ -4910,13 +4924,13 @@ } }, "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", - "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", + "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", + "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } @@ -5309,19 +5323,10 @@ "node": ">=14.16" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@tsconfig/docusaurus": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-2.0.8.tgz", - "integrity": "sha512-25cxrJH44gGHdjljVHNMPWu3Baf96sPtMjkUgoon+mXViaSuCeoYki+XUTFIitsnoPmpev/vMGjk06Kqan3a7Q==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/docusaurus/-/docusaurus-2.0.9.tgz", + "integrity": "sha512-0jVxZCgy2v7TnJrW9kVNikNwJHeiWb68Zhiiw2vHw4tzBFSP5vS2zn3O5EY2oPEVz5dXZRkK7MnWG3Ay5q0mIg==", "dev": true, "license": "MIT" }, @@ -5617,9 +5622,9 @@ } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { "@types/ms": "*" @@ -5691,9 +5696,9 @@ "license": "MIT" }, "node_modules/@types/gtag.js": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.12.tgz", - "integrity": "sha512-YQV9bUsemkzG81Ea295/nF/5GijnD2Af7QhEofh7xu+kvCN6RdodgNwwGWXB5GMI3NoyvQo0odNctoH/qLMIpg==", + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/gtag.js/-/gtag.js-0.0.20.tgz", + "integrity": "sha512-wwAbk3SA2QeU67unN7zPxjEHmPmlXwZXZvQEpbEUQuMCRGgKyE1m6XDuTUA9b6pCGb/GqJmdfMOY5LuDjJSbbg==", "license": "MIT" }, "node_modules/@types/hast": { @@ -5796,24 +5801,24 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", - "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/prismjs": { - "version": "1.26.5", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", - "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", "license": "MIT" }, "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "license": "MIT" }, "node_modules/@types/range-parser": { @@ -5823,9 +5828,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", - "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -5969,6 +5974,16 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -6171,9 +6186,9 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -6204,9 +6219,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -6238,9 +6253,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -6283,34 +6298,34 @@ } }, "node_modules/algoliasearch": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.48.0.tgz", - "integrity": "sha512-aD8EQC6KEman6/S79FtPdQmB7D4af/etcRL/KwiKFKgAE62iU8c5PeEQvpvIcBPurC3O/4Lj78nOl7ZcoazqSw==", - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.14.0", - "@algolia/client-abtesting": "5.48.0", - "@algolia/client-analytics": "5.48.0", - "@algolia/client-common": "5.48.0", - "@algolia/client-insights": "5.48.0", - "@algolia/client-personalization": "5.48.0", - "@algolia/client-query-suggestions": "5.48.0", - "@algolia/client-search": "5.48.0", - "@algolia/ingestion": "1.48.0", - "@algolia/monitoring": "1.48.0", - "@algolia/recommend": "5.48.0", - "@algolia/requester-browser-xhr": "5.48.0", - "@algolia/requester-fetch": "5.48.0", - "@algolia/requester-node-http": "5.48.0" + "version": "5.50.1", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.50.1.tgz", + "integrity": "sha512-/bwdue1/8LWELn/DBalGRfuLsXBLXULJo/yOeavJtDu8rBwxIzC6/Rz9Jg19S21VkJvRuZO1k8CZXBMS73mYbA==", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.16.1", + "@algolia/client-abtesting": "5.50.1", + "@algolia/client-analytics": "5.50.1", + "@algolia/client-common": "5.50.1", + "@algolia/client-insights": "5.50.1", + "@algolia/client-personalization": "5.50.1", + "@algolia/client-query-suggestions": "5.50.1", + "@algolia/client-search": "5.50.1", + "@algolia/ingestion": "1.50.1", + "@algolia/monitoring": "1.50.1", + "@algolia/recommend": "5.50.1", + "@algolia/requester-browser-xhr": "5.50.1", + "@algolia/requester-fetch": "5.50.1", + "@algolia/requester-node-http": "5.50.1" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/algoliasearch-helper": { - "version": "3.27.0", - "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.27.0.tgz", - "integrity": "sha512-eNYchRerbsvk2doHOMfdS1/B6Tm70oGtu8mzQlrNzbCeQ8p1MjCW8t/BL6iZ5PD+cL5NNMgTMyMnmiXZ1sgmNw==", + "version": "3.28.1", + "resolved": "https://registry.npmjs.org/algoliasearch-helper/-/algoliasearch-helper-3.28.1.tgz", + "integrity": "sha512-6iXpbkkrAI5HFpCWXlNmIDSBuoN/U1XnEvb2yJAoWfqrZ+DrybI7MQ5P5mthFaprmocq+zbi6HxnR28xnZAYBw==", "license": "MIT", "dependencies": { "@algolia/events": "^4.0.1" @@ -6348,33 +6363,6 @@ "node": ">=8" } }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", @@ -6411,6 +6399,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", + "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -6475,9 +6472,9 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.24", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", - "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", "funding": [ { "type": "opencollective", @@ -6495,7 +6492,7 @@ "license": "MIT", "dependencies": { "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001766", + "caniuse-lite": "^1.0.30001774", "fraction.js": "^5.3.4", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" @@ -6537,13 +6534,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz", - "integrity": "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "license": "MIT", "dependencies": { "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -6573,12 +6570,12 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz", - "integrity": "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.6" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -6601,12 +6598,15 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "version": "2.10.18", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", + "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.js" + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/batch": { @@ -6735,9 +6735,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -6757,9 +6757,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "funding": [ { "type": "opencollective", @@ -6776,11 +6776,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -6856,14 +6856,14 @@ } }, "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" }, "engines": { @@ -6946,9 +6946,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001769", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", - "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", "funding": [ { "type": "opencollective", @@ -7079,37 +7079,33 @@ } }, "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-12.0.0.tgz", + "integrity": "sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==", "license": "Apache-2.0", "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" + "@chevrotain/cst-dts-gen": "12.0.0", + "@chevrotain/gast": "12.0.0", + "@chevrotain/regexp-to-ast": "12.0.0", + "@chevrotain/types": "12.0.0", + "@chevrotain/utils": "12.0.0" + }, + "engines": { + "node": ">=22.0.0" } }, "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.4.1.tgz", + "integrity": "sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==", "license": "MIT", "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { - "chevrotain": "^11.0.0" + "chevrotain": "^12.0.0" } }, - "node_modules/chevrotain/node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -7499,6 +7495,18 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/copy-text-to-clipboard": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.2.tgz", + "integrity": "sha512-T6SqyLd1iLuqPA90J5N4cTalrtovCySh58iiZDGJ6FGznbclKh4UI+FGacQSgFzwKG77W7XT5gwbVEbd9cIH1A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/copy-webpack-plugin": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", @@ -7567,9 +7575,9 @@ } }, "node_modules/core-js": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", - "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", + "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", "hasInstallScript": true, "license": "MIT", "funding": { @@ -7578,9 +7586,9 @@ } }, "node_modules/core-js-compat": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.48.0.tgz", - "integrity": "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "license": "MIT", "dependencies": { "browserslist": "^4.28.1" @@ -7590,17 +7598,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-pure": { - "version": "3.48.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.48.0.tgz", - "integrity": "sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -7722,9 +7719,9 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", - "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.4.0.tgz", + "integrity": "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw==", "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -7938,9 +7935,9 @@ } }, "node_modules/cssdb": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.7.1.tgz", - "integrity": "sha512-+F6LKx48RrdGOtE4DT5jz7Uo+VeyKXpK797FAevIkzjV8bMHz6xTO5F7gNDcRCHmPgD5jj2g6QCsY9zmVrh38A==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.8.0.tgz", + "integrity": "sha512-QbLeyz2Bgso1iRlh7IpWk6OKa3lLNGXsujVjDMPl9rOZpxKeiG69icLpbLCFxeURwmcdIfZqQyhlooKJYM4f8Q==", "funding": [ { "type": "opencollective", @@ -8102,9 +8099,9 @@ "license": "MIT" }, "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "version": "3.33.2", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.2.tgz", + "integrity": "sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==", "license": "MIT", "engines": { "node": ">=0.10" @@ -8600,9 +8597,9 @@ } }, "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", "license": "MIT", "dependencies": { "d3": "^7.9.0", @@ -8610,9 +8607,9 @@ } }, "node_modules/dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", "license": "MIT" }, "node_modules/debounce": { @@ -8777,9 +8774,9 @@ } }, "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", "license": "ISC", "dependencies": { "robust-predicates": "^3.0.2" @@ -8924,9 +8921,9 @@ } }, "node_modules/dompurify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", - "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -9013,9 +9010,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "version": "1.5.335", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", + "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -9059,9 +9056,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", - "integrity": "sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -9512,9 +9509,9 @@ "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==", "license": "MIT" }, "node_modules/express/node_modules/range-parser": { @@ -9634,30 +9631,6 @@ "node": ">=0.4.0" } }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-loader": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", @@ -9679,9 +9652,9 @@ } }, "node_modules/file-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -9882,9 +9855,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -11071,9 +11044,9 @@ } }, "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", + "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", "license": "MIT", "engines": { "node": ">=16" @@ -11356,9 +11329,9 @@ } }, "node_modules/katex": { - "version": "0.16.28", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", - "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==", + "version": "0.16.45", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.45.tgz", + "integrity": "sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -11413,19 +11386,21 @@ } }, "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.2.tgz", + "integrity": "sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==", "license": "MIT", "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", + "@chevrotain/regexp-to-ast": "~12.0.0", + "chevrotain": "~12.0.0", + "chevrotain-allstar": "~0.4.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" + "vscode-uri": "~3.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=20.10.0", + "npm": ">=10.2.3" } }, "node_modules/latest-version": { @@ -11444,9 +11419,9 @@ } }, "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.2.tgz", + "integrity": "sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==", "license": "MIT", "dependencies": { "picocolors": "^1.1.1", @@ -11529,15 +11504,15 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, "node_modules/lodash.debounce": { @@ -11703,9 +11678,9 @@ } }, "node_modules/mdast-util-from-markdown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", - "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -12071,19 +12046,19 @@ } }, "node_modules/memfs": { - "version": "4.56.10", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", - "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.57.1.tgz", + "integrity": "sha512-WvzrWPwMQT+PtbX2Et64R4qXKK0fj/8pO85MrUCzymX3twwCiJCdvntW3HdhG1teLJcHDDLIKx5+c3HckWYZtQ==", "license": "Apache-2.0", "dependencies": { - "@jsonjoy.com/fs-core": "4.56.10", - "@jsonjoy.com/fs-fsa": "4.56.10", - "@jsonjoy.com/fs-node": "4.56.10", - "@jsonjoy.com/fs-node-builtins": "4.56.10", - "@jsonjoy.com/fs-node-to-fsa": "4.56.10", - "@jsonjoy.com/fs-node-utils": "4.56.10", - "@jsonjoy.com/fs-print": "4.56.10", - "@jsonjoy.com/fs-snapshot": "4.56.10", + "@jsonjoy.com/fs-core": "4.57.1", + "@jsonjoy.com/fs-fsa": "4.57.1", + "@jsonjoy.com/fs-node": "4.57.1", + "@jsonjoy.com/fs-node-builtins": "4.57.1", + "@jsonjoy.com/fs-node-to-fsa": "4.57.1", + "@jsonjoy.com/fs-node-utils": "4.57.1", + "@jsonjoy.com/fs-print": "4.57.1", + "@jsonjoy.com/fs-snapshot": "4.57.1", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", @@ -12124,27 +12099,28 @@ } }, "node_modules/mermaid": { - "version": "11.12.2", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", - "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz", + "integrity": "sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==", "license": "MIT", "dependencies": { "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.0", "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", "khroma": "^2.1.0", - "lodash-es": "^4.17.21", - "marked": "^16.2.1", + "lodash-es": "^4.17.23", + "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", @@ -14012,9 +13988,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.0.tgz", - "integrity": "sha512-540P2c5dYnJlyJxTaSloliZexv8rji6rY8FhQN+WF/82iHQfA23j/xtJx97L+mXOML27EqksSek/g4eK7jaL3g==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", "license": "MIT", "dependencies": { "schema-utils": "^4.0.0", @@ -14038,9 +14014,9 @@ "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -14059,15 +14035,15 @@ } }, "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", "license": "MIT", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", - "ufo": "^1.6.1" + "ufo": "^1.6.3" } }, "node_modules/mrmime": { @@ -14157,9 +14133,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "license": "MIT" }, "node_modules/normalize-path": { @@ -14234,9 +14210,9 @@ } }, "node_modules/null-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -14726,9 +14702,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -14764,9 +14740,9 @@ } }, "node_modules/pkijs": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", - "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", "license": "BSD-3-Clause", "dependencies": { "@noble/hashes": "1.4.0", @@ -14797,9 +14773,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", "funding": [ { "type": "opencollective", @@ -16411,9 +16387,9 @@ } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -16542,24 +16518,24 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.5" } }, "node_modules/react-fast-compare": { @@ -16618,9 +16594,9 @@ } }, "node_modules/react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.3.tgz", + "integrity": "sha512-GXfh9VLwB5ERaCsU6RULh7tkemeX15aNh6wuMEBtfdyMa7fFG8TXrhXlx1SoEK2Ty/l6XIkzzYIQmyaWW3JgdQ==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.3" @@ -16852,9 +16828,9 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -17130,15 +17106,6 @@ "entities": "^2.0.0" } }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -17163,11 +17130,12 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "license": "MIT", "dependencies": { + "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" @@ -17238,9 +17206,9 @@ } }, "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", "license": "Unlicense" }, "node_modules/roughjs": { @@ -17341,9 +17309,9 @@ "license": "MIT" }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" @@ -17504,15 +17472,15 @@ } }, "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz", + "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==", "license": "MIT", "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", - "minimatch": "3.1.2", + "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" @@ -17725,13 +17693,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -17804,9 +17772,9 @@ "license": "MIT" }, "node_modules/sitemap": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.2.tgz", - "integrity": "sha512-ARCqzHJ0p4gWt+j7NlU5eDlIO9+Rkr/JhPFZKKQ1l5GCus7rJH4UdrlVAh0xC/gDS/Qir2UMxqYNHtsKr2rpCw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-7.1.3.tgz", + "integrity": "sha512-tAjEd+wt/YwnEbfNB2ht51ybBJxbEWwe5ki/Z//Wh0rpBFTCUSj46GnxUKEWzhfuJTsee8x3lybHxFgUMig2hw==", "license": "MIT", "dependencies": { "@types/node": "^17.0.5", @@ -18037,12 +18005,12 @@ } }, "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -18192,18 +18160,18 @@ "license": "MIT" }, "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.3.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.0.0", + "sax": "^1.5.0" }, "bin": { "svgo": "bin/svgo" @@ -18226,9 +18194,9 @@ } }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", "license": "MIT", "engines": { "node": ">=6" @@ -18239,9 +18207,9 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -18257,15 +18225,14 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", - "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", + "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -18326,9 +18293,9 @@ "license": "MIT" }, "node_modules/thingies": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", - "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.6.0.tgz", + "integrity": "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg==", "license": "MIT", "engines": { "node": ">=10.18" @@ -18360,9 +18327,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", + "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", "license": "MIT", "engines": { "node": ">=18" @@ -18552,9 +18519,9 @@ "license": "MIT" }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { @@ -18880,9 +18847,9 @@ } }, "node_modules/url-loader/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -19093,9 +19060,9 @@ "license": "MIT" }, "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", "license": "MIT" }, "node_modules/watchpack": { @@ -19131,9 +19098,9 @@ } }, "node_modules/webpack": { - "version": "5.105.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.0.tgz", - "integrity": "sha512-gX/dMkRQc7QOMzgTe6KsYFM7DxeIONQSui1s0n/0xht36HvrgbxtM1xBlgx596NbpHuQU8P7QpKwrZYwUX48nw==", + "version": "5.106.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.1.tgz", + "integrity": "sha512-EW8af29ak8Oaf4T8k8YsajjrDBDYgnKZ5er6ljWFJsXABfTNowQfvHLftwcepVgdz+IoLSdEAbBiM9DFXoll9w==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -19142,11 +19109,11 @@ "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", + "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.19.0", + "enhanced-resolve": "^5.20.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -19158,9 +19125,9 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.16", + "terser-webpack-plugin": "^5.3.17", "watchpack": "^2.5.1", - "webpack-sources": "^3.3.3" + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -19364,9 +19331,9 @@ } }, "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", - "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -19399,9 +19366,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", + "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", "engines": { "node": ">=10.13.0" @@ -19429,75 +19396,30 @@ } }, "node_modules/webpackbar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", - "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-7.0.0.tgz", + "integrity": "sha512-aS9soqSO2iCHgqHoCrj4LbfGQUboDCYJPSFOAchEK+9psIjNrfSWW4Y0YEz67MKURNvMmfo0ycOg9d/+OOf9/Q==", "license": "MIT", "dependencies": { - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", + "ansis": "^3.2.0", "consola": "^3.2.3", - "figures": "^3.2.0", - "markdown-table": "^2.0.0", "pretty-time": "^1.1.0", - "std-env": "^3.7.0", - "wrap-ansi": "^7.0.0" + "std-env": "^3.7.0" }, "engines": { "node": ">=14.21.3" }, "peerDependencies": { + "@rspack/core": "*", "webpack": "3 || 4 || 5" - } - }, - "node_modules/webpackbar/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/webpackbar/node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpackbar/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpackbar/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } } }, "node_modules/websocket-driver": { @@ -19601,12 +19523,12 @@ } }, "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -19664,9 +19586,9 @@ } }, "node_modules/wsl-utils/node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" diff --git a/artemis-docs/package.json b/artemis-docs/package.json index 7eaf4b7d..8468d13f 100644 --- a/artemis-docs/package.json +++ b/artemis-docs/package.json @@ -15,17 +15,20 @@ "typecheck": "tsc" }, "dependencies": { - "@docusaurus/core": "^3.9.2", - "@docusaurus/preset-classic": "^3.9.2", - "@docusaurus/theme-mermaid": "^3.9.2", + "@docusaurus/core": "^3.10.0", + "@docusaurus/preset-classic": "^3.10.0", + "@docusaurus/theme-mermaid": "^3.10.0", "@mdx-js/react": "^3.1.1", "clsx": "^2.1.1", "prism-react-renderer": "^2.4.1", "react": "^19.2.0", "react-dom": "^19.2.0" }, + "overrides": { + "webpackbar": "^7.0.0" +}, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.9.2", + "@docusaurus/module-type-aliases": "^3.10.0", "@tsconfig/docusaurus": "^2.0.7", "typescript": "~5.9.3" }, @@ -44,4 +47,4 @@ "engines": { "node": ">=16.14" } -} \ No newline at end of file +} diff --git a/mod.ts b/mod.ts index b4684571..f4da8a6c 100644 --- a/mod.ts +++ b/mod.ts @@ -59,6 +59,7 @@ export { parseCookies } from "./src/macos/safari/cookies"; export { Safari } from "./src/macos/safari/safari"; export { authorizations } from "./src/macos/sqlite/authd"; export { parseSystemStats } from "./src/macos/systemstats"; +export { recentFilesMacos } from "./src/macos/plist/sharefilelist"; /** * Unix exported functions @@ -67,6 +68,7 @@ export { getCron } from "./src/unix/cron"; export { getBashHistory, getZshHistory, + getAshHistory, } from "./src/unix/shell_history"; export { listKnownHosts } from "./src/unix/ssh"; @@ -101,6 +103,9 @@ export { OneDrive } from "./src/applications/onedrive/onedrive"; export { Comet } from "./src/applications/comet"; export { Brave } from "./src/applications/brave"; export { Syncthing } from "./src/applications/syncthing"; +export { officeMruFiles } from "./src/applications/office"; +export { RustDesk } from "./src/applications/rustdesk/rmm"; + /** * Windows exported functions @@ -157,12 +162,25 @@ export { rdpLogons } from "./src/windows/eventlogs/rdp"; export { backgroundActivitiesManager } from "./src/windows/registry/bam"; export { parsePca } from "./src/windows/pca"; export { defenderQuarantineEventLog } from "./src/windows/eventlogs/defender"; +export { msiInstalled } from "./src/windows/eventlogs/msi"; +export { bitsEvents } from "./src/windows/eventlogs/bits"; +export { veloCommands } from "./src/windows/eventlogs/velociraptor"; +export { crashEvents } from "./src/windows/eventlogs/crash"; +export { extractAppCrash } from "./src/windows/appcrash"; /** * FreeBSD */ export { getPkgs } from "./src/freebsd/packages"; +/** + * ESXi + */ +export { shellLogHistory } from "./src/esxi/logs/shell"; +export { getVibs } from "./src/esxi/vib"; +export { sysLogEsxi } from "./src/esxi/logs/syslog"; +export { esxiAccounts } from "./src/esxi/accounts"; + /** * Timelining */ diff --git a/package-lock.json b/package-lock.json index 665b8da5..a066c474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1558 +1,1280 @@ -{ - "name": "artemis-api", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "devDependencies": { - "@eslint/js": "^9.39.2", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^9.39.2", - "globals": "^17.3.0", - "jiti": "^2.6.1", - "typescript-eslint": "^8.54.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", - "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/type-utils": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", - "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", - "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.54.0", - "@typescript-eslint/types": "^8.54.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", - "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", - "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", - "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", - "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", - "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.54.0", - "@typescript-eslint/tsconfig-utils": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/visitor-keys": "8.54.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", - "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.54.0", - "@typescript-eslint/types": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", - "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.54.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.3.0.tgz", - "integrity": "sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "jiti": "lib/jiti-cli.mjs" - } - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", - "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.54.0", - "@typescript-eslint/parser": "8.54.0", - "@typescript-eslint/typescript-estree": "8.54.0", - "@typescript-eslint/utils": "8.54.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} +{ + "name": "artemis-api", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@eslint/js": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^8.58.1", + "@typescript-eslint/parser": "^8.58.1", + "eslint": "^10.2.0", + "globals": "^17.5.0", + "jiti": "^2.6.1", + "typescript-eslint": "^8.58.1" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", + "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/type-utils": "8.58.1", + "@typescript-eslint/utils": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", + "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", + "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.1", + "@typescript-eslint/types": "^8.58.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", + "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", + "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", + "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1", + "@typescript-eslint/utils": "8.58.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", + "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", + "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.1", + "@typescript-eslint/tsconfig-utils": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/visitor-keys": "8.58.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", + "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.1", + "@typescript-eslint/types": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", + "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.2.0.tgz", + "integrity": "sha512-+L0vBFYGIpSNIt/KWTpFonPrqYvgKw1eUI5Vn7mEogrQcWtWYtNQ7dNqC+px/J0idT3BAkiWrhfS7k+Tum8TUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.4", + "@eslint/config-helpers": "^0.5.4", + "@eslint/core": "^1.2.0", + "@eslint/plugin-kit": "^0.7.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "17.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", + "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.58.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz", + "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.1", + "@typescript-eslint/parser": "8.58.1", + "@typescript-eslint/typescript-estree": "8.58.1", + "@typescript-eslint/utils": "8.58.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index b98ca7e2..9a90dad4 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,18 @@ -{ - "scripts": { - "lint": "npx eslint --ext .ts ", - "test_linux": "cd tests/linux && python compile_tests.py", - "test_windows": "cd tests/windows && powershell compile_tests.ps1", - "test_macos": "cd tests/macos && python compile_tests.py", - "test_application": "cd tests/applications && python compile_tests.py" - }, - "devDependencies": { - "@eslint/js": "^9.39.2", - "@typescript-eslint/eslint-plugin": "^8.54.0", - "@typescript-eslint/parser": "^8.54.0", - "eslint": "^9.39.2", - "globals": "^17.3.0", - "jiti": "^2.6.1", - "typescript-eslint": "^8.54.0" - } +{ + "scripts": { + "lint": "npx eslint --ext .ts ", + "test_linux": "cd tests/linux && python compile_tests.py", + "test_windows": "cd tests/windows && powershell compile_tests.ps1", + "test_macos": "cd tests/macos && python compile_tests.py", + "test_application": "cd tests/applications && python compile_tests.py" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@typescript-eslint/eslint-plugin": "^8.58.1", + "@typescript-eslint/parser": "^8.58.1", + "eslint": "^10.2.0", + "globals": "^17.5.0", + "jiti": "^2.6.1", + "typescript-eslint": "^8.58.1" + } } \ No newline at end of file diff --git a/src/applications/anydesk/conf.ts b/src/applications/anydesk/conf.ts index 917a13bf..e9e5fa13 100644 --- a/src/applications/anydesk/conf.ts +++ b/src/applications/anydesk/conf.ts @@ -1,68 +1,69 @@ -import { AnyDeskUsers, Config } from "../../../types/applications/anydesk"; -import { FileError } from "../../filesystem/errors"; -import { readLines, stat } from "../../filesystem/files"; -import { ApplicationError } from "../errors"; - -/** - * Function to parse AnyDesk config files - * @param path Path to conf file - * @param user User profile info associated with AnyDesk - * @returns `Config` object - */ -export function readConfig(path: string, user: AnyDeskUsers): Config | ApplicationError { - // Config should be really small. Less than 100 lines - const limit = 500; - const results = readLines(path, 0, limit); - if (results instanceof FileError) { - return new ApplicationError(`ANYDESK`, `could not read ${path}: ${results}`); - } - - let created = "1970-01-01T00:00:00.000Z"; - const meta = stat(path); - if (!(meta instanceof FileError)) { - created = meta.created; - } - const value: Config = { - message: path, - datetime: created, - timestamp_desc: "Config Created", - artifact: "AnyDesk Config", - data_type: "applications:anydesk:config:entry", - account: user.account, - version: user.version, - id: user.id - }; - for (const entry of results) { - const key_value = entry.split("=", 2); - const key = key_value.at(0); - if (key === undefined) { - continue; - } - value[ key ] = key_value.at(1) ?? ""; - } - - return value; -} - -/** - * Function to test the AnyDesk config file parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the AnyDesk config parsing - */ -export function testReadConfig(): void { - const test = "../../test_data/anydesk/user.conf"; - const results = readConfig(test, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); - - if (results[ "ad.roster.discovered.view_type" ] !== "2") { - throw `Got view_type ${results[ "ad.roster.discovered.view_type" ]} expected 2.......readConfig ❌`; - } - - const test2 = "../../test_data/anydesk/system.conf"; - const results2 = readConfig(test2, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); - - if (results2[ "ad.inst.id" ] !== "34b44fe840517fc112b20e806a28ec18") { - throw `Got ID ${results2[ "ad.inst.id" ]} expected 34b44fe840517fc112b20e806a28ec18.......readConfig ❌`; - } - - console.info(` Function readConfig ✅`); +import { AnyDeskUsers, Config } from "../../../types/applications/anydesk"; +import { FileError } from "../../filesystem/errors"; +import { readLines, stat } from "../../filesystem/files"; +import { ApplicationError } from "../errors"; + +/** + * Function to parse AnyDesk config files + * @param path Path to conf file + * @param user User profile info associated with AnyDesk + * @returns `Config` object + */ +export function readConfig(path: string, user: AnyDeskUsers): Config | ApplicationError { + // Config should be really small. Less than 100 lines + const limit = 500; + const results = readLines(path, 0, limit); + if (results instanceof FileError) { + return new ApplicationError(`ANYDESK`, `could not read ${path}: ${results}`); + } + + let created = "1970-01-01T00:00:00.000Z"; + const meta = stat(path); + if (!(meta instanceof FileError)) { + created = meta.created; + } + const value: Config = { + message: path, + datetime: created, + timestamp_desc: "Config Created", + artifact: "AnyDesk Config", + data_type: "applications:anydesk:config:entry", + account: user.account, + version: user.version, + id: user.id, + evidence: path, + }; + for (const entry of results) { + const key_value = entry.split("=", 2); + const key = key_value.at(0); + if (key === undefined) { + continue; + } + value[ key ] = key_value.at(1) ?? ""; + } + + return value; +} + +/** + * Function to test the AnyDesk config file parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the AnyDesk config parsing + */ +export function testReadConfig(): void { + const test = "../../test_data/anydesk/user.conf"; + const results = readConfig(test, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); + + if (results[ "ad.roster.discovered.view_type" ] !== "2") { + throw `Got view_type ${results[ "ad.roster.discovered.view_type" ]} expected 2.......readConfig ❌`; + } + + const test2 = "../../test_data/anydesk/system.conf"; + const results2 = readConfig(test2, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); + + if (results2[ "ad.inst.id" ] !== "34b44fe840517fc112b20e806a28ec18") { + throw `Got ID ${results2[ "ad.inst.id" ]} expected 34b44fe840517fc112b20e806a28ec18.......readConfig ❌`; + } + + console.info(` Function readConfig ✅`); } \ No newline at end of file diff --git a/src/applications/anydesk/rmm.ts b/src/applications/anydesk/rmm.ts index 83e2169d..77e84f21 100644 --- a/src/applications/anydesk/rmm.ts +++ b/src/applications/anydesk/rmm.ts @@ -1,383 +1,382 @@ -import { glob, PlatformType, readTextFile } from "../../../mod"; -import { AnyDeskUsers, Config, TraceEntry } from "../../../types/applications/anydesk"; -import { GlobInfo } from "../../../types/filesystem/globs"; -import { FileError } from "../../filesystem/errors"; -import { ApplicationError } from "../errors"; -import { readConfig } from "./conf"; -import { readTrace } from "./trace"; - -/** - * Class to parse AnyDesk data - */ -export class AnyDesk { - private paths: AnyDeskUsers[] = []; - private platform: PlatformType; - - /** - * Construct a `AnyDesk` object that can be used to parse AnyDesk data - * @param platform OS `PlatformType` - * @param alt_path Optional alternative directory that contains all AnyDesk related files including configs - * @returns `AnyDesk` instance class - */ - constructor (platform: PlatformType, alt_path?: string) { - this.platform = platform; - - // Get AnyDesk data based on PlatformType - if (alt_path === undefined) { - const results = this.profiles(platform); - if (results instanceof ApplicationError) { - return; - } - - this.paths = results; - return; - } - - // Use provided directory any get some info - const client_version = this.version(this.platform, alt_path); - if (client_version instanceof ApplicationError) { - return; - } - const client_account = this.account(this.platform, alt_path); - if (client_account instanceof ApplicationError) { - return; - } - const client_id = this.id(this.platform, alt_path); - if (client_id instanceof ApplicationError) { - return; - } - - this.paths = [ { user_path: alt_path, version: client_version, account: client_account, id: client_id } ]; - } - - /** - * Function to parse AnyDesk trace files. By default parses trace files at AnyDesk default paths - * @param is_alt If you have provided an optional alternative directory containing the AnyDesk files. Set this to true - * @returns Array of `TraceEntry` - */ - public traceFiles(is_alt = false): TraceEntry[] { - let system = "/var/log/*.trace"; - - let separator = "/"; - if (this.platform === PlatformType.Windows) { - separator = "\\"; - system = "TODO"; - } - - let hits: TraceEntry[] = []; - // If alternative directory was provided then parse all trace files - if (is_alt && this.paths[ 0 ] !== undefined) { - const entries = glob(`${this.paths[ 0 ].user_path}${separator}*.trace`); - if (entries instanceof FileError) { - console.error(entries); - return hits; - } - for (const entry of entries) { - if (!entry.is_file) { - continue; - } - const values = readTrace(entry.full_path, this.paths[ 0 ]); - hits = hits.concat(values); - } - return hits; - } - - // Try parsing trace files at default paths - for (const entry of this.paths) { - const path = `${entry.user_path}${separator}*.trace`; - const entries = glob(path); - if (entries instanceof FileError) { - console.error(entries); - continue; - } - for (const trace_file of entries) { - if (!trace_file.is_file) { - continue; - } - const values = readTrace(trace_file.full_path, entry); - hits = hits.concat(values); - } - } - // Get system trace file(s) - if (this.paths[ 0 ] !== undefined) { - const entries = glob(system); - if (entries instanceof FileError) { - console.error(entries); - return hits; - } - for (const entry of entries) { - if (!entry.is_file) { - continue; - } - const values = readTrace(entry.full_path, this.paths[ 0 ]); - hits = hits.concat(values); - } - } - - return hits; - - } - - /** - * Function to parse AnyDesk config files. By default parses config files at AnyDesk default paths - * @param is_alt If you have provided an optional alternative directory containing the AnyDesk files. Set this to true - * @returns Array of `Config` - */ - public configs(is_alt = false): Config[] { - const hits: Config[] = []; - - let separator = "/"; - if (this.platform === PlatformType.Windows) { - separator = "\\"; - } - - // If alternative directory was provided then parse all trace files - if (is_alt && this.paths[ 0 ] !== undefined) { - const entries = glob(`${this.paths[ 0 ].user_path}${separator}*.conf`); - if (entries instanceof FileError) { - console.error(entries); - return hits; - } - for (const entry of entries) { - if (!entry.is_file) { - continue; - } - const values = readConfig(entry.full_path, this.paths[ 0 ]); - if (values instanceof ApplicationError) { - continue; - } - hits.push(values); - } - return hits; - } - - // Try parsing conf files at default paths - for (const entry of this.paths) { - const path = `${entry.user_path}${separator}*.conf`; - const entries = glob(path); - if (entries instanceof FileError) { - console.error(entries); - continue; - } - for (const trace_file of entries) { - if (!trace_file.is_file) { - continue; - } - const values = readConfig(trace_file.full_path, entry); - if (values instanceof ApplicationError) { - continue; - } - hits.push(values); - } - } - // Get system conf file(s) - if (this.paths[ 0 ] !== undefined && this.platform === PlatformType.Linux) { - const system = "/etc/anydesk/*.conf"; - const entries = glob(system); - if (entries instanceof FileError) { - console.error(entries); - return hits; - } - for (const entry of entries) { - if (!entry.is_file) { - continue; - } - // Get user config - const values = readConfig(entry.full_path, this.paths[ 0 ]); - if (values instanceof ApplicationError) { - continue; - } - hits.push(values); - } - } - - return hits; - } - - /** - * Get base path for all AnyDesk data - * @param platform OS `PlatformType` - * @returns Array of `AnyDeskUsers` or `ApplicationError` - */ - private profiles(platform: PlatformType): AnyDeskUsers[] | ApplicationError { - let paths: GlobInfo[] = []; - switch (platform) { - case PlatformType.Linux: { - const linux_paths = glob("/home/*/.anydesk"); - if (linux_paths instanceof FileError) { - return new ApplicationError( - "ANYDESK", - `failed to glob linux paths: ${linux_paths}`, - ); - } - paths = linux_paths; - break; - } - case PlatformType.Darwin: { - const mac_paths = glob("/Users/*/.anydesk"); - if (mac_paths instanceof FileError) { - return new ApplicationError( - "ANYDESK", - `failed to glob macOS paths: ${mac_paths}`, - ); - } - paths = mac_paths; - break; - } - case PlatformType.Windows: { - const win_paths = glob("C:\\Users\\*\\AppData\\Roaming\\AnyDesk"); - if (win_paths instanceof FileError) { - return new ApplicationError( - "ANYDESK", - `failed to glob Windows paths: ${win_paths}`, - ); - } - paths = win_paths; - break; - } - default: { - console.warn(`Unsupported platform: ${platform}`); - return []; - } - } - - const clients: AnyDeskUsers[] = []; - for (const entry of paths) { - if (!entry.is_directory) { - continue; - } - const client_version = this.version(this.platform, entry.full_path); - if (client_version instanceof ApplicationError) { - continue; - } - - const client_account = this.account(this.platform, entry.full_path); - if (client_account instanceof ApplicationError) { - continue; - } - - const client_id = this.id(this.platform, entry.full_path); - if (client_id instanceof ApplicationError) { - continue; - } - - const profile: AnyDeskUsers = { - user_path: entry.full_path, - version: client_version, - account: client_account, - id: client_id - }; - - clients.push(profile); - } - return clients; - } - - /** - * Function to determine AnyDesk client version - * @param platform OS `PlatformType` - * @param path Path to base AnyDesk user config - * @returns Version string or `ApplicationError` - */ - private version(platform: PlatformType, path: string): string | ApplicationError { - let version_path = `${path}/user.conf`; - if (platform === PlatformType.Windows) { - version_path = `${path}\\user.conf`; - } - - const text_data = readTextFile(version_path); - if (text_data instanceof FileError) { - return new ApplicationError(`ANYDESK`, `could not read ${version_path}: ${text_data}`); - } - - const version_regex = /ad\.ui\.install\.new_update=.*/; - const match = text_data.match(version_regex); - if (match === null) { - return new ApplicationError(`ANYDESK`, `could not match version regex`); - } - - const version_line = match?.at(0); - if (version_line === undefined) { - return new ApplicationError(`ANYDESK`, `could not find version line got undefined`); - } - - const version_string = version_line.split("=").at(1); - if (version_string === undefined) { - return new ApplicationError(`ANYDESK`, `could not find version got undefined`); - } - - return version_string; - } - - /** - * Function to determine AnyDesk client account - * @param platform OS `PlatformType` - * @param path Path to base AnyDesk user config - * @returns Account string or `ApplicationError` - */ - private account(platform: PlatformType, path: string): string | ApplicationError { - let account_path = `${path}/user.conf`; - if (platform === PlatformType.Windows) { - account_path = `${path}\\user.conf`; - } - - const text_data = readTextFile(account_path); - if (text_data instanceof FileError) { - return new ApplicationError(`ANYDESK`, `could not read ${account_path}: ${text_data}`); - } - - const account_regex = /ad\.account\.recent_logged_in_user=.*/; - const match = text_data.match(account_regex); - if (match === null) { - return new ApplicationError(`ANYDESK`, `could not match account regex`); - } - - const account_line = match?.at(0); - if (account_line === undefined) { - return new ApplicationError(`ANYDESK`, `could not find account line got undefined`); - } - - const account_string = account_line.split("=").at(1); - if (account_string === undefined) { - return new ApplicationError(`ANYDESK`, `could not find account got undefined`); - } - - return account_string; - } - - /** - * Function to determine AnyDesk client ID - * @param platform OS `PlatformType` - * @param path Path to base AnyDesk user config - * @returns ID string or `ApplicationError` - */ - private id(platform: PlatformType, path: string): string | ApplicationError { - let id_path = `${path}/system.conf`; - if (platform === PlatformType.Windows) { - id_path = `${path}\\system.conf`; - } - - const text_data = readTextFile(id_path); - if (text_data instanceof FileError) { - return new ApplicationError(`ANYDESK`, `could not read ${id_path}: ${text_data}`); - } - - const id_regex = /ad\.anynet\.id=.*/; - const match = text_data.match(id_regex); - if (match === null) { - return new ApplicationError(`ANYDESK`, `could not match id regex`); - } - - const id_line = match?.at(0); - if (id_line === undefined) { - return new ApplicationError(`ANYDESK`, `could not find id line got undefined`); - } - - const id_string = id_line.split("=").at(1); - if (id_string === undefined) { - return new ApplicationError(`ANYDESK`, `could not find id got undefined`); - } - - return id_string; - } +import { glob, PlatformType, readTextFile } from "../../../mod"; +import { AnyDeskUsers, Config, TraceEntry } from "../../../types/applications/anydesk"; +import { FileError } from "../../filesystem/errors"; +import { ApplicationError } from "../errors"; +import { readConfig } from "./conf"; +import { readTrace } from "./trace"; + +/** + * Class to parse AnyDesk data + */ +export class AnyDesk { + private paths: AnyDeskUsers[] = []; + private platform: PlatformType; + + /** + * Construct a `AnyDesk` object that can be used to parse AnyDesk data + * @param platform OS `PlatformType` + * @param alt_path Optional alternative directory that contains all AnyDesk related files including configs + * @returns `AnyDesk` instance class + */ + constructor (platform: PlatformType, alt_path?: string) { + this.platform = platform; + + // Get AnyDesk data based on PlatformType + if (alt_path === undefined) { + const results = this.profiles(platform); + if (results instanceof ApplicationError) { + return; + } + + this.paths = results; + return; + } + + // Use provided directory any get some info + const client_version = this.version(this.platform, alt_path); + if (client_version instanceof ApplicationError) { + return; + } + const client_account = this.account(this.platform, alt_path); + if (client_account instanceof ApplicationError) { + return; + } + const client_id = this.id(this.platform, alt_path); + if (client_id instanceof ApplicationError) { + return; + } + + this.paths = [ { user_path: alt_path, version: client_version, account: client_account, id: client_id } ]; + } + + /** + * Function to parse AnyDesk trace files. By default parses trace files at AnyDesk default paths + * @param is_alt If you have provided an optional alternative directory containing the AnyDesk files. Set this to true + * @returns Array of `TraceEntry` + */ + public traceFiles(is_alt = false): TraceEntry[] { + let system = "/var/log/*.trace"; + + let separator = "/"; + if (this.platform === PlatformType.Windows) { + separator = "\\"; + system = "TODO"; + } + + let hits: TraceEntry[] = []; + // If alternative directory was provided then parse all trace files + if (is_alt && this.paths[ 0 ] !== undefined) { + const entries = glob(`${this.paths[ 0 ].user_path}${separator}*.trace`); + if (entries instanceof FileError) { + console.error(entries); + return hits; + } + for (const entry of entries) { + if (!entry.is_file) { + continue; + } + const values = readTrace(entry.full_path, this.paths[ 0 ]); + hits = hits.concat(values); + } + return hits; + } + + // Try parsing trace files at default paths + for (const entry of this.paths) { + const path = `${entry.user_path}${separator}*.trace`; + const entries = glob(path); + if (entries instanceof FileError) { + console.error(entries); + continue; + } + for (const trace_file of entries) { + if (!trace_file.is_file) { + continue; + } + const values = readTrace(trace_file.full_path, entry); + hits = hits.concat(values); + } + } + // Get system trace file(s) + if (this.paths[ 0 ] !== undefined) { + const entries = glob(system); + if (entries instanceof FileError) { + console.error(entries); + return hits; + } + for (const entry of entries) { + if (!entry.is_file) { + continue; + } + const values = readTrace(entry.full_path, this.paths[ 0 ]); + hits = hits.concat(values); + } + } + + return hits; + + } + + /** + * Function to parse AnyDesk config files. By default parses config files at AnyDesk default paths + * @param is_alt If you have provided an optional alternative directory containing the AnyDesk files. Set this to true + * @returns Array of `Config` + */ + public configs(is_alt = false): Config[] { + const hits: Config[] = []; + + let separator = "/"; + if (this.platform === PlatformType.Windows) { + separator = "\\"; + } + + // If alternative directory was provided then parse all trace files + if (is_alt && this.paths[ 0 ] !== undefined) { + const entries = glob(`${this.paths[ 0 ].user_path}${separator}*.conf`); + if (entries instanceof FileError) { + console.error(entries); + return hits; + } + for (const entry of entries) { + if (!entry.is_file) { + continue; + } + const values = readConfig(entry.full_path, this.paths[ 0 ]); + if (values instanceof ApplicationError) { + continue; + } + hits.push(values); + } + return hits; + } + + // Try parsing conf files at default paths + for (const entry of this.paths) { + const path = `${entry.user_path}${separator}*.conf`; + const entries = glob(path); + if (entries instanceof FileError) { + console.error(entries); + continue; + } + for (const trace_file of entries) { + if (!trace_file.is_file) { + continue; + } + const values = readConfig(trace_file.full_path, entry); + if (values instanceof ApplicationError) { + continue; + } + hits.push(values); + } + } + // Get system conf file(s) + if (this.paths[ 0 ] !== undefined && this.platform === PlatformType.Linux) { + const system = "/etc/anydesk/*.conf"; + const entries = glob(system); + if (entries instanceof FileError) { + console.error(entries); + return hits; + } + for (const entry of entries) { + if (!entry.is_file) { + continue; + } + // Get user config + const values = readConfig(entry.full_path, this.paths[ 0 ]); + if (values instanceof ApplicationError) { + continue; + } + hits.push(values); + } + } + + return hits; + } + + /** + * Get base path for all AnyDesk data + * @param platform OS `PlatformType` + * @returns Array of `AnyDeskUsers` or `ApplicationError` + */ + private profiles(platform: PlatformType): AnyDeskUsers[] | ApplicationError { + let paths; + switch (platform) { + case PlatformType.Linux: { + const linux_paths = glob("/home/*/.anydesk"); + if (linux_paths instanceof FileError) { + return new ApplicationError( + "ANYDESK", + `failed to glob linux paths: ${linux_paths}`, + ); + } + paths = linux_paths; + break; + } + case PlatformType.Darwin: { + const mac_paths = glob("/Users/*/.anydesk"); + if (mac_paths instanceof FileError) { + return new ApplicationError( + "ANYDESK", + `failed to glob macOS paths: ${mac_paths}`, + ); + } + paths = mac_paths; + break; + } + case PlatformType.Windows: { + const win_paths = glob("C:\\Users\\*\\AppData\\Roaming\\AnyDesk"); + if (win_paths instanceof FileError) { + return new ApplicationError( + "ANYDESK", + `failed to glob Windows paths: ${win_paths}`, + ); + } + paths = win_paths; + break; + } + default: { + console.warn(`Unsupported platform: ${platform}`); + return []; + } + } + + const clients: AnyDeskUsers[] = []; + for (const entry of paths) { + if (!entry.is_directory) { + continue; + } + const client_version = this.version(this.platform, entry.full_path); + if (client_version instanceof ApplicationError) { + continue; + } + + const client_account = this.account(this.platform, entry.full_path); + if (client_account instanceof ApplicationError) { + continue; + } + + const client_id = this.id(this.platform, entry.full_path); + if (client_id instanceof ApplicationError) { + continue; + } + + const profile: AnyDeskUsers = { + user_path: entry.full_path, + version: client_version, + account: client_account, + id: client_id + }; + + clients.push(profile); + } + return clients; + } + + /** + * Function to determine AnyDesk client version + * @param platform OS `PlatformType` + * @param path Path to base AnyDesk user config + * @returns Version string or `ApplicationError` + */ + private version(platform: PlatformType, path: string): string | ApplicationError { + let version_path = `${path}/user.conf`; + if (platform === PlatformType.Windows) { + version_path = `${path}\\user.conf`; + } + + const text_data = readTextFile(version_path); + if (text_data instanceof FileError) { + return new ApplicationError(`ANYDESK`, `could not read ${version_path}: ${text_data}`); + } + + const version_regex = /ad\.ui\.install\.new_update=.*/; + const match = text_data.match(version_regex); + if (match === null) { + return new ApplicationError(`ANYDESK`, `could not match version regex`); + } + + const version_line = match?.at(0); + if (version_line === undefined) { + return new ApplicationError(`ANYDESK`, `could not find version line got undefined`); + } + + const version_string = version_line.split("=").at(1); + if (version_string === undefined) { + return new ApplicationError(`ANYDESK`, `could not find version got undefined`); + } + + return version_string; + } + + /** + * Function to determine AnyDesk client account + * @param platform OS `PlatformType` + * @param path Path to base AnyDesk user config + * @returns Account string or `ApplicationError` + */ + private account(platform: PlatformType, path: string): string | ApplicationError { + let account_path = `${path}/user.conf`; + if (platform === PlatformType.Windows) { + account_path = `${path}\\user.conf`; + } + + const text_data = readTextFile(account_path); + if (text_data instanceof FileError) { + return new ApplicationError(`ANYDESK`, `could not read ${account_path}: ${text_data}`); + } + + const account_regex = /ad\.account\.recent_logged_in_user=.*/; + const match = text_data.match(account_regex); + if (match === null) { + return new ApplicationError(`ANYDESK`, `could not match account regex`); + } + + const account_line = match?.at(0); + if (account_line === undefined) { + return new ApplicationError(`ANYDESK`, `could not find account line got undefined`); + } + + const account_string = account_line.split("=").at(1); + if (account_string === undefined) { + return new ApplicationError(`ANYDESK`, `could not find account got undefined`); + } + + return account_string; + } + + /** + * Function to determine AnyDesk client ID + * @param platform OS `PlatformType` + * @param path Path to base AnyDesk user config + * @returns ID string or `ApplicationError` + */ + private id(platform: PlatformType, path: string): string | ApplicationError { + let id_path = `${path}/system.conf`; + if (platform === PlatformType.Windows) { + id_path = `${path}\\system.conf`; + } + + const text_data = readTextFile(id_path); + if (text_data instanceof FileError) { + return new ApplicationError(`ANYDESK`, `could not read ${id_path}: ${text_data}`); + } + + const id_regex = /ad\.anynet\.id=.*/; + const match = text_data.match(id_regex); + if (match === null) { + return new ApplicationError(`ANYDESK`, `could not match id regex`); + } + + const id_line = match?.at(0); + if (id_line === undefined) { + return new ApplicationError(`ANYDESK`, `could not find id line got undefined`); + } + + const id_string = id_line.split("=").at(1); + if (id_string === undefined) { + return new ApplicationError(`ANYDESK`, `could not find id got undefined`); + } + + return id_string; + } } \ No newline at end of file diff --git a/src/applications/anydesk/trace.ts b/src/applications/anydesk/trace.ts index 278f0178..08aaac50 100644 --- a/src/applications/anydesk/trace.ts +++ b/src/applications/anydesk/trace.ts @@ -1,188 +1,188 @@ -import { AnyDeskUsers, TraceEntry } from "../../../types/applications/anydesk"; -import { FileError } from "../../filesystem/errors"; -import { readLines } from "../../filesystem/files"; -import { NomError } from "../../nom/error"; -import { takeUntil } from "../../nom/parsers"; - -/** - * Function to parse AnyDesk trace files - * @param path Path to trace file - * @param user User profile info associated with AnyDesk - * @returns Array of `TraceEntry` - */ -export function readTrace(path: string, user: AnyDeskUsers): TraceEntry[] { - const hits: TraceEntry[] = []; - let offset = 0; - const limit = 100; - while (true) { - const results = readLines(path, offset, limit); - if (results instanceof FileError) { - break; - } - offset += 100; - - for (let entry of results) { - if (entry.startsWith("*") || !entry.includes(" ")) { - continue; - } - - entry = entry.replaceAll("\u0000", " "); - entry = entry.trim(); - let info = takeUntil(entry, " "); - if (info instanceof NomError) { - continue; - } - - const level = info.nommed as string; - info.remaining = (info.remaining as string).trimStart(); - - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - const timestamp = `${(info.nommed as string).replace(" ", "T")}Z`; - info.remaining = (info.remaining as string).trimStart(); - - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - const component = info.nommed as string; - info.remaining = (info.remaining as string).trimStart(); - - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - const code_function = info.nommed as string; - info.remaining = (info.remaining as string).trimStart(); - - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - const pid = Number(info.nommed as string); - info.remaining = (info.remaining as string).trimStart(); - - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - const ppid = Number(info.nommed as string); - info.remaining = (info.remaining as string).trimStart(); - - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - let subfunction = info.nommed as string; - - // Sometimes another number appears after ppid? - // Unsure what it is. Seen only in system anydesk.trace - // Subfunction appears after it - if (!isNaN(+subfunction)) { - info.remaining = (info.remaining as string).trimStart(); - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - subfunction = info.nommed as string; - } - info.remaining = (info.remaining as string).trimStart(); - - let log_message = ""; - if (!(info.remaining as string).endsWith("-")) { - // Skip '-' - info = takeUntil(info.remaining, " "); - if (info instanceof NomError) { - continue; - } - info.remaining = (info.remaining as string).trim(); - log_message = info.remaining as string; - } - - const value: TraceEntry = { - message: log_message, - datetime: timestamp, - timestamp_desc: "Trace Entry", - artifact: "AnyDesk Trace Log", - data_type: "applications:anydesk:trace:entry", - path, - level, - entry_timestamp: timestamp, - component, - code_function, - pid, - ppid, - subfunction, - log_message, - account: user.account, - version: user.version, - id: user.id - }; - hits.push(value); - } - - // We have reached the end - if (results.length < limit) { - break; - } - } - - return hits; -} - -/** - * Function to test the AnyDesk trace file parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the AnyDesk trace parsing - */ -export function testReadTrace(): void { - const test = "../../test_data/anydesk/anydesk.trace"; - const results = readTrace(test, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); - - if (results.length !== 790) { - throw `Got ${results.length} rows expected 790.......readTrace ❌`; - } - - if (results[22] === undefined) { - throw `Got undefined rows expected 'user_data_dir = /home/ubu/.anydesk, logged_in_user_data_dir = /home/ubu/.anydesk, current_home = /home/ubu'.......readTrace ❌`; - } - - if (results[22].message !== "user_data_dir = /home/ubu/.anydesk, logged_in_user_data_dir = /home/ubu/.anydesk, current_home = /home/ubu") { - throw `Got '${results[22].message}' rows expected 'user_data_dir = /home/ubu/.anydesk, logged_in_user_data_dir = /home/ubu/.anydesk, current_home = /home/ubu'.......readTrace ❌`; - } - - if (results[444] === undefined) { - throw `Got undefined rows expected '2025-09-22T00:53:51.166Z'.......readTrace ❌`; - } - - if (results[444].datetime !== "2025-09-22T00:53:51.166Z") { - throw `Got '${results[444].datetime}' expected 2025-09-22T00:53:51.166Z.......readTrace ❌`; - } - - const test2 = "../../test_data/anydesk/anydesk_var_log.trace"; - const results2 = readTrace(test2, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); - - if (results2.length !== 2082) { - throw `Got ${results2.length} rows expected 2082.......readTrace ❌`; - } - - if (results2[444] === undefined) { - throw `Got undefined rows expected '2025-09-22T00:46:40.998Z'.......readTrace ❌`; - } - - if (results2[22] === undefined) { - throw `Got undefined rows expected 'Global: yes'.......readTrace ❌`; - } - - if (results2[22].message !== "Global: yes") { - throw `Got '${results2[22].message}' expected 'Global: yes'.......readTrace ❌`; - } - - if (results2[444].datetime !== "2025-09-22T00:46:40.998Z") { - throw `Got '${results2[444].datetime}' expected 2025-09-22T00:46:40.998Z.......readTrace ❌`; - } - - console.info(` Function readTrace ✅`); +import { AnyDeskUsers, TraceEntry } from "../../../types/applications/anydesk"; +import { FileError } from "../../filesystem/errors"; +import { readLines } from "../../filesystem/files"; +import { NomError } from "../../nom/error"; +import { takeUntil } from "../../nom/parsers"; + +/** + * Function to parse AnyDesk trace files + * @param path Path to trace file + * @param user User profile info associated with AnyDesk + * @returns Array of `TraceEntry` + */ +export function readTrace(path: string, user: AnyDeskUsers): TraceEntry[] { + const hits: TraceEntry[] = []; + let offset = 0; + const limit = 100; + while (true) { + const results = readLines(path, offset, limit); + if (results instanceof FileError) { + break; + } + offset += 100; + + for (let entry of results) { + if (entry.startsWith("*") || !entry.includes(" ")) { + continue; + } + + entry = entry.replaceAll("\u0000", " "); + entry = entry.trim(); + let info = takeUntil(entry, " "); + if (info instanceof NomError) { + continue; + } + + const level = info.nommed as string; + info.remaining = (info.remaining as string).trimStart(); + + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + const timestamp = `${(info.nommed as string).replace(" ", "T")}Z`; + info.remaining = (info.remaining as string).trimStart(); + + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + const component = info.nommed as string; + info.remaining = (info.remaining as string).trimStart(); + + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + const code_function = info.nommed as string; + info.remaining = (info.remaining as string).trimStart(); + + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + const pid = Number(info.nommed as string); + info.remaining = (info.remaining as string).trimStart(); + + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + const ppid = Number(info.nommed as string); + info.remaining = (info.remaining as string).trimStart(); + + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + let subfunction = info.nommed as string; + + // Sometimes another number appears after ppid? + // Unsure what it is. Seen only in system anydesk.trace + // Subfunction appears after it + if (!isNaN(+subfunction)) { + info.remaining = (info.remaining as string).trimStart(); + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + subfunction = info.nommed as string; + } + info.remaining = (info.remaining as string).trimStart(); + + let log_message = ""; + if (!(info.remaining as string).endsWith("-")) { + // Skip '-' + info = takeUntil(info.remaining, " "); + if (info instanceof NomError) { + continue; + } + info.remaining = (info.remaining as string).trim(); + log_message = info.remaining as string; + } + + const value: TraceEntry = { + message: log_message, + datetime: timestamp, + timestamp_desc: "Trace Entry", + artifact: "AnyDesk Trace Log", + data_type: "applications:anydesk:trace:entry", + evidence: path, + level, + entry_timestamp: timestamp, + component, + code_function, + pid, + ppid, + subfunction, + log_message, + account: user.account, + version: user.version, + id: user.id + }; + hits.push(value); + } + + // We have reached the end + if (results.length < limit) { + break; + } + } + + return hits; +} + +/** + * Function to test the AnyDesk trace file parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the AnyDesk trace parsing + */ +export function testReadTrace(): void { + const test = "../../test_data/anydesk/anydesk.trace"; + const results = readTrace(test, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); + + if (results.length !== 790) { + throw `Got ${results.length} rows expected 790.......readTrace ❌`; + } + + if (results[22] === undefined) { + throw `Got undefined rows expected 'user_data_dir = /home/ubu/.anydesk, logged_in_user_data_dir = /home/ubu/.anydesk, current_home = /home/ubu'.......readTrace ❌`; + } + + if (results[22].message !== "user_data_dir = /home/ubu/.anydesk, logged_in_user_data_dir = /home/ubu/.anydesk, current_home = /home/ubu") { + throw `Got '${results[22].message}' rows expected 'user_data_dir = /home/ubu/.anydesk, logged_in_user_data_dir = /home/ubu/.anydesk, current_home = /home/ubu'.......readTrace ❌`; + } + + if (results[444] === undefined) { + throw `Got undefined rows expected '2025-09-22T00:53:51.166Z'.......readTrace ❌`; + } + + if (results[444].datetime !== "2025-09-22T00:53:51.166Z") { + throw `Got '${results[444].datetime}' expected 2025-09-22T00:53:51.166Z.......readTrace ❌`; + } + + const test2 = "../../test_data/anydesk/anydesk_var_log.trace"; + const results2 = readTrace(test2, { user_path: "", id: "1234", account: "adfasdf@adfads.com", version: "7.1.0" }); + + if (results2.length !== 2082) { + throw `Got ${results2.length} rows expected 2082.......readTrace ❌`; + } + + if (results2[444] === undefined) { + throw `Got undefined rows expected '2025-09-22T00:46:40.998Z'.......readTrace ❌`; + } + + if (results2[22] === undefined) { + throw `Got undefined rows expected 'Global: yes'.......readTrace ❌`; + } + + if (results2[22].message !== "Global: yes") { + throw `Got '${results2[22].message}' expected 'Global: yes'.......readTrace ❌`; + } + + if (results2[444].datetime !== "2025-09-22T00:46:40.998Z") { + throw `Got '${results2[444].datetime}' expected 2025-09-22T00:46:40.998Z.......readTrace ❌`; + } + + console.info(` Function readTrace ✅`); } \ No newline at end of file diff --git a/src/applications/brave.ts b/src/applications/brave.ts index 472a3dee..08e58897 100644 --- a/src/applications/brave.ts +++ b/src/applications/brave.ts @@ -1,213 +1,219 @@ -import { BrowserType, ChromiumAutofill, ChromiumBookmarks, ChromiumCookies, ChromiumDips, ChromiumDownloads, ChromiumFavicons, ChromiumHistory, ChromiumLocalStorage, ChromiumLogins, ChromiumSession, ChromiumShortcuts, Extension, Preferences } from "../../types/applications/chromium"; -import { PlatformType } from "../system/systeminfo"; -import { Chromium } from "./chromium/cr"; -import { chromiumBookmarks, chromiumExtensions } from "./chromium/json"; -import { chromiumLocalStorage } from "./chromium/level"; -import { chromiumPreferences } from "./chromium/preferences"; -import { chromiumSessions } from "./chromium/sessions"; -import { chromiumAutofill, chromiumCookies, chromiumDips, chromiumDownloads, chromiumFavicons, chromiumHistory, chromiumLogins, chromiumShortcuts } from "./chromium/sqlite"; - -type BraveHistory = ChromiumHistory; -type BraveDownloads = ChromiumDownloads; -type BraveCookies = ChromiumCookies; -type BraveAutofill = ChromiumAutofill; -type BraveBookmarks = ChromiumBookmarks; -type BraveLogins = ChromiumLogins; -type BraveDips = ChromiumDips; -type BraveLocalStorage = ChromiumLocalStorage; -type BraveSession = ChromiumSession; -type BraveFavicons = ChromiumFavicons; -type BraveShortcuts = ChromiumShortcuts; - -/** - * Class to extract Brave browser information. Since Brave is based on Chromium we can leverage the existing Chromium artifacts to parse Brave info - */ -export class Brave extends Chromium { - - /** - * Construct a `Brave` object that can be used to parse browser data - * @param platform OS `PlatformType` - * @param unfold Attempt to parse URLs. Default is `false` - * @param alt_path Optional alternative path to directory contain Brave data - * @returns `Brave` instance class - */ - constructor(platform: PlatformType, unfold = false, alt_path?: string) { - super(platform, unfold, BrowserType.BRAVE, alt_path); - } - - /** - * Extract Brave browser history. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of browser history - */ - public override history(offset = 0, limit = 100): BraveHistory[] { - const query = `SELECT - urls.id AS id, - urls.url AS url, - title, - visit_count, - typed_count, - last_visit_time, - hidden, - visits.id AS visits_id, - from_visit, - transition, - segment_id, - visit_duration, - opener_visit - FROM - urls - JOIN visits ON urls.id = visits.url LIMIT ${limit} OFFSET ${offset}`; - return chromiumHistory(this.paths, this.platform, this.unfold, query); - } - - /** - * Extract Brave browser downloads. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of browser downloads - */ - public override downloads(offset = 0, limit = 100): BraveDownloads[] { - const query = `SELECT - downloads.id AS downloads_id, - guid, - current_path, - target_path, - start_time, - received_bytes, - total_bytes, - state, - danger_type, - interrupt_reason, - hash, - end_time, - opened, - last_access_time, - transient, - referrer, - site_url, - tab_url, - tab_referrer_url, - http_method, - by_ext_id, - by_ext_name, - etag, - last_modified, - mime_type, - original_mime_type, - downloads_url_chains.id AS downloads_url_chain_id, - chain_index, - url - FROM - downloads - JOIN downloads_url_chains ON downloads_url_chains.id = downloads.id LIMIT ${limit} OFFSET ${offset}`; - return chromiumDownloads(this.paths, this.platform, query); - } - - /** - * Extract Brave browser cookies - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `BraveCookies` - */ - public override cookies(offset = 0, limit = 100): BraveCookies[] { - const query = `SELECT * FROM cookies LIMIT ${limit} OFFSET ${offset}`; - return chromiumCookies(this.paths, this.platform, query); - } - - /** - * Get installed Brave extensions - * @returns Array of parsed extensions - */ - public override extensions(): Extension[] { - return chromiumExtensions(this.paths, this.platform); - } - - /** - * Get Brave Preferences - * @returns Array of `Preferences` for each user - */ - public override preferences(): Preferences[] { - return chromiumPreferences(this.paths, this.platform); - } - - /** - * Function to parse Brave AutoFill information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `BraveAutofill` - */ - public override autofill(offset = 0, limit = 100): BraveAutofill[] { - const query = `SELECT name, value, date_created, date_last_used, count, value_lower from autofill LIMIT ${limit} OFFSET ${offset}`; - return chromiumAutofill(this.paths, this.platform, query); - } - - /** - * Get Brave Bookmarks - * @returns Array of `BraveBookmarks` for each user - */ - public override bookmarks(): BraveBookmarks[] { - return chromiumBookmarks(this.paths, this.platform); - } - - /** - * Function to parse Brave Favicons information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `BraveFavicons` - */ - public override favicons(offset?: number, limit?: number): BraveFavicons[] { - const query = `SELECT url, last_updated, page_url FROM favicons JOIN favicon_bitmaps ON favicons.id = favicon_bitmaps.id JOIN icon_mapping ON icon_mapping.icon_id = favicons.id LIMIT ${limit} OFFSET ${offset}`; - return chromiumFavicons(this.paths, this.platform, query); - } - - /** - * Function to parse Brave Shortcut information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `BraveShortcuts` - */ - public override shortcuts(offset = 0, limit = 100): BraveShortcuts[] { - const query = `SELECT id, text, fill_into_edit, url, contents, description, type, keyword, last_access_time FROM omni_box_shortcuts LIMIT ${limit} OFFSET ${offset}`; - return chromiumShortcuts(this.paths, this.platform, query); - } - - /** - * Get Brave Local Storage - * @returns Array of `BraveLocalStorage` for each user - */ - public override localStorage(): BraveLocalStorage[] { - return chromiumLocalStorage(this.paths, this.platform); - } - - /** - * Get Brave Sessions - * @returns Array of `BraveSession` for each user - */ - public override sessions(): BraveSession[] { - return chromiumSessions(this.paths, this.platform); - } - - /** - * Function to parse Brave Login information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `BraveLogins` - */ - public override logins(offset = 0, limit = 100): BraveLogins[] { - const query = `SELECT * from logins LIMIT ${limit} OFFSET ${offset}`; - return chromiumLogins(this.paths, this.platform, query); - } - - /** - * Function to parse Brave Detect Incidental Party State (DIPS) information - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `BraveDips` - */ - public override dips(offset = 0, limit = 100): BraveDips[] { - const query = `SELECT * from bounces LIMIT ${limit} OFFSET ${offset}`; - return chromiumDips(this.paths, this.platform, query); - } +import { BrowserType, ChromiumAutofill, ChromiumBookmarks, ChromiumCache, ChromiumCookies, ChromiumDips, ChromiumDownloads, ChromiumFavicons, ChromiumHistory, ChromiumLocalStorage, ChromiumLogins, ChromiumSession, ChromiumShortcuts, Extension, Preferences } from "../../types/applications/chromium"; +import { PlatformType } from "../system/systeminfo"; +import { chromiumCache } from "./chromium/cache"; +import { Chromium } from "./chromium/cr"; +import { chromiumBookmarks, chromiumExtensions } from "./chromium/json"; +import { chromiumLocalStorage } from "./chromium/level"; +import { chromiumPreferences } from "./chromium/preferences"; +import { chromiumSessions } from "./chromium/sessions"; +import { chromiumAutofill, chromiumCookies, chromiumDips, chromiumDownloads, chromiumFavicons, chromiumHistory, chromiumLogins, chromiumShortcuts } from "./chromium/sqlite"; + +type BraveHistory = ChromiumHistory; +type BraveDownloads = ChromiumDownloads; +type BraveCookies = ChromiumCookies; +type BraveAutofill = ChromiumAutofill; +type BraveBookmarks = ChromiumBookmarks; +type BraveLogins = ChromiumLogins; +type BraveDips = ChromiumDips; +type BraveLocalStorage = ChromiumLocalStorage; +type BraveSession = ChromiumSession; +type BraveFavicons = ChromiumFavicons; +type BraveShortcuts = ChromiumShortcuts; +type BraveCache = ChromiumCache; + +/** + * Class to extract Brave browser information. Since Brave is based on Chromium we can leverage the existing Chromium artifacts to parse Brave info + */ +export class Brave extends Chromium { + + /** + * Construct a `Brave` object that can be used to parse browser data + * @param platform OS `PlatformType` + * @param unfold Attempt to parse URLs. Default is `false` + * @param alt_path Optional alternative path to directory contain Brave data + * @returns `Brave` instance class + */ + constructor(platform: PlatformType, unfold = false, alt_path?: string) { + super(platform, unfold, BrowserType.BRAVE, alt_path); + } + + /** + * Extract Brave browser history. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of browser history + */ + public override history(offset = 0, limit = 100): BraveHistory[] { + const query = `SELECT + urls.id AS id, + urls.url AS url, + title, + visit_count, + typed_count, + last_visit_time, + hidden, + visits.id AS visits_id, + from_visit, + transition, + segment_id, + visit_duration, + opener_visit + FROM + urls + JOIN visits ON urls.id = visits.url LIMIT ${limit} OFFSET ${offset}`; + return chromiumHistory(this.paths, this.platform, this.unfold, query); + } + + /** + * Extract Brave browser downloads. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of browser downloads + */ + public override downloads(offset = 0, limit = 100): BraveDownloads[] { + const query = `SELECT + downloads.id AS downloads_id, + guid, + current_path, + target_path, + start_time, + received_bytes, + total_bytes, + state, + danger_type, + interrupt_reason, + hash, + end_time, + opened, + last_access_time, + transient, + referrer, + site_url, + tab_url, + tab_referrer_url, + http_method, + by_ext_id, + by_ext_name, + etag, + last_modified, + mime_type, + original_mime_type, + downloads_url_chains.id AS downloads_url_chain_id, + chain_index, + url + FROM + downloads + JOIN downloads_url_chains ON downloads_url_chains.id = downloads.id LIMIT ${limit} OFFSET ${offset}`; + return chromiumDownloads(this.paths, this.platform, query); + } + + /** + * Extract Brave browser cookies + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `BraveCookies` + */ + public override cookies(offset = 0, limit = 100): BraveCookies[] { + const query = `SELECT * FROM cookies LIMIT ${limit} OFFSET ${offset}`; + return chromiumCookies(this.paths, this.platform, query); + } + + /** + * Get installed Brave extensions + * @returns Array of parsed extensions + */ + public override extensions(): Extension[] { + return chromiumExtensions(this.paths, this.platform); + } + + /** + * Get Brave Preferences + * @returns Array of `Preferences` for each user + */ + public override preferences(): Preferences[] { + return chromiumPreferences(this.paths, this.platform); + } + + /** + * Function to parse Brave AutoFill information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `BraveAutofill` + */ + public override autofill(offset = 0, limit = 100): BraveAutofill[] { + const query = `SELECT name, value, date_created, date_last_used, count, value_lower from autofill LIMIT ${limit} OFFSET ${offset}`; + return chromiumAutofill(this.paths, this.platform, query); + } + + /** + * Get Brave Bookmarks + * @returns Array of `BraveBookmarks` for each user + */ + public override bookmarks(): BraveBookmarks[] { + return chromiumBookmarks(this.paths, this.platform); + } + + /** + * Function to parse Brave Favicons information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `BraveFavicons` + */ + public override favicons(offset?: number, limit?: number): BraveFavicons[] { + const query = `SELECT url, last_updated, page_url FROM favicons JOIN favicon_bitmaps ON favicons.id = favicon_bitmaps.id JOIN icon_mapping ON icon_mapping.icon_id = favicons.id LIMIT ${limit} OFFSET ${offset}`; + return chromiumFavicons(this.paths, this.platform, query); + } + + /** + * Function to parse Brave Shortcut information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `BraveShortcuts` + */ + public override shortcuts(offset = 0, limit = 100): BraveShortcuts[] { + const query = `SELECT id, text, fill_into_edit, url, contents, description, type, keyword, last_access_time FROM omni_box_shortcuts LIMIT ${limit} OFFSET ${offset}`; + return chromiumShortcuts(this.paths, this.platform, query); + } + + /** + * Get Brave Local Storage + * @returns Array of `BraveLocalStorage` for each user + */ + public override localStorage(): BraveLocalStorage[] { + return chromiumLocalStorage(this.paths, this.platform); + } + + /** + * Get Brave Sessions + * @returns Array of `BraveSession` for each user + */ + public override sessions(): BraveSession[] { + return chromiumSessions(this.paths, this.platform); + } + + /** + * Function to parse Brave Login information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `BraveLogins` + */ + public override logins(offset = 0, limit = 100): BraveLogins[] { + const query = `SELECT * from logins LIMIT ${limit} OFFSET ${offset}`; + return chromiumLogins(this.paths, this.platform, query); + } + + /** + * Function to parse Brave Detect Incidental Party State (DIPS) information + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `BraveDips` + */ + public override dips(offset = 0, limit = 100): BraveDips[] { + const query = `SELECT * from bounces LIMIT ${limit} OFFSET ${offset}`; + return chromiumDips(this.paths, this.platform, query); + } + + public override cache(): BraveCache[] { + return chromiumCache(this.paths, this.platform); + } } \ No newline at end of file diff --git a/src/applications/chromium/cache.ts b/src/applications/chromium/cache.ts new file mode 100644 index 00000000..9bb58519 --- /dev/null +++ b/src/applications/chromium/cache.ts @@ -0,0 +1,978 @@ +import { glob, readFile, PlatformType, } from "../../../mod"; +import { BrowserType, CacheFlag, CacheState, ChromiumCache, ChromiumProfiles } from "../../../types/applications/chromium"; +import { extractUtf8String } from "../../encoding/strings"; +import { FileError } from "../../filesystem/errors"; +import { BufReader } from "../../filesystem/reader"; +import { NomError } from "../../nom/error"; +import { Endian, nomUnsignedEightBytes, nomUnsignedFourBytes, nomUnsignedOneBytes, nomUnsignedTwoBytes } from "../../nom/helpers"; +import { take, takeUntil } from "../../nom/parsers"; +import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; +import { ApplicationError } from "../errors"; + +/** + * Extract Chromium cache data + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @returns + */ +export function chromiumCache(paths: ChromiumProfiles[], platform: PlatformType): ChromiumCache[] { + let values: ChromiumCache[] = []; + let index: Index | undefined | ApplicationError = undefined; + const data = {} as Record; + + let cache_paths = ["/*/GPUCache/*", "/*/Cache/Cache_Data/*"]; + if (platform === PlatformType.Windows) { + cache_paths = ["\\*\\GPUCache\\*", "\\*\\Cache\\Cache_Data\\*"]; + } + + for (const path of paths) { + for (const cache_path of cache_paths) { + let full_path = `${path.full_path}${cache_path}`; + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}${cache_path}`; + } + + const caches = glob(full_path); + if (caches instanceof FileError) { + continue; + } + + let evidence = full_path; + for (const entry of caches) { + if (!entry.is_file) { + continue; + } + + if (entry.filename === "index") { + index = parseIndex(entry.full_path); + if (index instanceof ApplicationError) { + continue; + } + evidence = entry.full_path; + } else if (entry.filename.includes("data_")) { + const data_block = parseData(entry.full_path); + if (data_block instanceof ApplicationError) { + continue; + } + if (entry.filename === "data_0") { + data[FileType.Ranking] = data_block; + } else if (entry.filename === "data_1") { + data[FileType.Block256] = data_block; + } else if (entry.filename === "data_2") { + data[FileType.Block1k] = data_block; + } else if (entry.filename === "data_3") { + data[FileType.Block4k] = data_block; + } + } + } + + if (index === undefined || index instanceof ApplicationError) { + continue; + } + + // Now parse each cache entry + const cache = extractCache(index, data, path, evidence); + if (cache instanceof ApplicationError) { + continue; + } + values = values.concat(cache); + } + + } + + return values; +} + +interface Index { + sig: number; + minor_version: number; + major_version: number; + entries: number; + data_size: number; + last_created_file_number: number; + dirty: number; + statistics_cache: CacheAddress; + table_size: number; + crash: number; + experiment_id: number; + created: string; + filled: number; + sizes: number[]; + head_cache: CacheAddress[]; + tail_cache: CacheAddress[]; + transaction_cache: CacheAddress; + operation: number; + operation_list: number; + // Only contains initialized entries (CacheAddress.initialized === true) + cache_entries: CacheAddress[]; + index_path: string; +} + +interface CacheAddress { + file_number: number; + file_type: FileType; + file_type_number: number; + block_number: number; + contiguous_blocks: number; + reserved: number; + initialized: boolean; + file_selector: number; +} + +enum FileType { + External = "External", + Ranking = "Ranking", + Block256 = "Block 256", + Block1k = "Block 1K", + Block4k = "Block 4K", + BlockFiles = "Block Files", + BlockEntries = "Block Entries", + BlockEvicted = "Block Evicted", + Unknown = "Unknown", +} + +/** + * + * @param path Path to the cache index file + * @param platform `PlatformType`. On Windows these files may be locked + * @returns + */ +function parseIndex(path: string): Index | ApplicationError { + const bytes = readFile(path); + if (bytes instanceof FileError) { + return new ApplicationError(`CHROMIUM`, `Failed to read cache index file ${path}: ${bytes}`); + } + + const sig = nomUnsignedFourBytes(bytes, Endian.Le); + if (sig instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file sig ${path}: ${sig}`); + } + + const minor = nomUnsignedTwoBytes(sig.remaining, Endian.Le); + if (minor instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file minor version ${path}: ${minor}`); + } + + const major = nomUnsignedTwoBytes(minor.remaining, Endian.Le); + if (major instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file major version ${path}: ${major}`); + } + + const entries = nomUnsignedFourBytes(major.remaining, Endian.Le); + if (entries instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file entries ${path}: ${entries}`); + } + + const data_size = nomUnsignedFourBytes(entries.remaining, Endian.Le); + if (data_size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file data size ${path}: ${data_size}`); + } + const last_created_file_number = nomUnsignedFourBytes(data_size.remaining, Endian.Le); + if (last_created_file_number instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file last created ${path}: ${last_created_file_number}`); + } + const dirty = nomUnsignedFourBytes(last_created_file_number.remaining, Endian.Le); + if (dirty instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file dirty ${path}: ${dirty}`); + } + const stats = nomUnsignedFourBytes(dirty.remaining, Endian.Le); + if (stats instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file stats ${path}: ${stats}`); + } + const table = nomUnsignedFourBytes(stats.remaining, Endian.Le); + if (table instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file table ${path}: ${table}`); + } + const crash = nomUnsignedFourBytes(table.remaining, Endian.Le); + if (crash instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file crash ${path}: ${crash}`); + } + const experiment_id = nomUnsignedFourBytes(crash.remaining, Endian.Le); + if (experiment_id instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file experiment ${path}: ${experiment_id}`); + } + const created = nomUnsignedEightBytes(experiment_id.remaining, Endian.Le); + if (created instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file created ${path}: ${created}`); + } + + // Padding for header = 208 bytes + // Padding for start of LRU data = 8 bytes + let padding = 216; + let remaining = take(created.remaining, padding); + if (remaining instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file ${path}: ${remaining}`); + } + + const filled = nomUnsignedFourBytes(remaining.remaining as Uint8Array, Endian.Le); + if (filled instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file filled ${path}: ${filled}`); + } + + const array_size = 5; + const total_size = array_size * 3; + const sizes: number[] = []; + const head_cache: CacheAddress[] = []; + const tail_cache: CacheAddress[] = []; + + // Next 3 structures are arrays of 5 entries. Each entry is 4 bytes + for (let i = 0; i < total_size; i++) { + const value = nomUnsignedFourBytes(filled.remaining, Endian.Le); + if (value instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file array addresses ${path}: ${filled}`); + } + filled.remaining = value.remaining; + if (sizes.length !== array_size) { + sizes.push(value.value); + } else if (head_cache.length !== array_size) { + head_cache.push(getCacheAddress(value.value)); + } else if (tail_cache.length !== array_size) { + tail_cache.push(getCacheAddress(value.value)); + + } + } + + const transaction = nomUnsignedFourBytes(filled.remaining, Endian.Le); + if (transaction instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file transaction ${path}: ${transaction}`); + } + + const operation = nomUnsignedFourBytes(transaction.remaining, Endian.Le); + if (operation instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file operation ${path}: ${operation}`); + } + + const operation_list = nomUnsignedFourBytes(operation.remaining, Endian.Le); + if (operation_list instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file operation list ${path}: ${operation_list}`); + } + + padding = 28; + remaining = take(operation_list.remaining, padding); + if (remaining instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file ${path}: ${remaining}`); + } + + const cache_bytes = take(remaining.remaining, table.value); + if (cache_bytes instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file cache ${path}: ${cache_bytes}`); + } + const cache_entries: CacheAddress[] = []; + const min_size = 4; + while (remaining.remaining.length > min_size) { + const value = nomUnsignedFourBytes(remaining.remaining as Uint8Array, Endian.Le); + if (value instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse index file cache entries ${path}: ${value}`); + } + remaining.remaining = value.remaining; + const cache = getCacheAddress(value.value); + // Not initialized + if (!cache.initialized) { + continue; + } + cache_entries.push(cache); + } + const adjust_time = 1000000n; + const index: Index = { + sig: sig.value, + minor_version: minor.value, + major_version: major.value, + entries: entries.value, + data_size: data_size.value, + last_created_file_number: last_created_file_number.value, + dirty: dirty.value, + statistics_cache: getCacheAddress(stats.value), + table_size: table.value, + crash: crash.value, + experiment_id: experiment_id.value, + created: unixEpochToISO(webkitToUnixEpoch(Number(created.value / adjust_time))), + filled: filled.value, + sizes, + head_cache, + tail_cache, + transaction_cache: getCacheAddress(transaction.value), + operation: operation.value, + operation_list: operation_list.value, + cache_entries, + index_path: path, + }; + + return index; +} + +/** + * Determine where the cache entry resides + * @param value File type number + * @returns `FileType` enum + */ +function getFileType(value: number): FileType { + switch (value) { + case 0: return FileType.External; + case 1: return FileType.Ranking; + case 2: return FileType.Block256; + case 3: return FileType.Block1k; + case 4: return FileType.Block4k; + case 5: return FileType.BlockFiles; + case 6: return FileType.BlockEntries; + case 7: return FileType.BlockEvicted; + default: return FileType.Unknown; + } +} + +/** + * Determine the cache address. Requires multiple bitwise operations + * @param value Cache address number + * @returns `CacheAddress` object + */ +function getCacheAddress(value: number): CacheAddress { + const initialized = Boolean(((value & 0x80000000) >>> 0) > 0); + const file_type_number = (((value & 0x70000000) >>> 0) >> 28); + const cache: CacheAddress = { + file_type: getFileType(file_type_number), + file_number: getFileType(file_type_number) === FileType.External ? value & 0x0fffffff : 0, + file_type_number, + block_number: getFileType(file_type_number) !== FileType.External ? value & 0x0000ffff : 0, + contiguous_blocks: getFileType(file_type_number) !== FileType.External ? 1 + ((value & 0x03000000) >> 24) : 0, + reserved: getFileType(file_type_number) !== FileType.External ? value & 0x0c000000 : 0, + file_selector: getFileType(file_type_number) !== FileType.External ? (value & 0x00ff0000) >> 16 : 0, + initialized + }; + return cache; +} + +interface DataBlock { + sig: number; + minor: number; + major: number; + /**Number for the data_X file */ + file_index: number; + next_index: number; + block_size: number; + number_entries: number; + max_entries: number; + empty_counters: number[]; + last_used: number[]; + updating: number; + user: number[]; + data_path: string; + reader: BufReader; +} + +/** + * Function to parse datablock files + * @param path Path to data file + * @returns `DataBlock` object or `ApplicationError` + */ +function parseData(path: string): DataBlock | ApplicationError { + const reader = new BufReader(path); + const size = 80; + + const bytes = reader.readBytes(0, size); + if (bytes instanceof FileError) { + return new ApplicationError(`CHROMIUM`, `Failed to read cache data bytes ${path}: ${bytes}`); + } + + const sig = nomUnsignedFourBytes(bytes, Endian.Le); + if (sig instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file sig ${path}: ${sig}`); + } + + const minor = nomUnsignedTwoBytes(sig.remaining, Endian.Le); + if (minor instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file minor ${path}: ${minor}`); + } + + const major = nomUnsignedTwoBytes(minor.remaining, Endian.Le); + if (major instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file major ${path}: ${major}`); + } + + const file_index = nomUnsignedTwoBytes(major.remaining, Endian.Le); + if (file_index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file index ${path}: ${file_index}`); + } + + const next_index = nomUnsignedTwoBytes(file_index.remaining, Endian.Le); + if (next_index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data next file index ${path}: ${next_index}`); + } + + const block_size = nomUnsignedFourBytes(next_index.remaining, Endian.Le); + if (block_size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file block size ${path}: ${block_size}`); + } + + const number_entries = nomUnsignedFourBytes(block_size.remaining, Endian.Le); + if (number_entries instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file number entries ${path}: ${number_entries}`); + } + + const max_entries = nomUnsignedFourBytes(number_entries.remaining, Endian.Le); + if (max_entries instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file max entries ${path}: ${max_entries}`); + } + + let limit = 4; + const total_limit = limit * 2; + const empty_counters: number[] = []; + const last_used: number[] = []; + for (let i = 0; i < total_limit; i++) { + const value = nomUnsignedFourBytes(max_entries.remaining, Endian.Le); + if (value instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file array ${path}: ${value}`); + } + max_entries.remaining = value.remaining; + if (empty_counters.length !== limit) { + empty_counters.push(value.value); + } else if (last_used.length !== limit) { + last_used.push(value.value); + } + } + + const updating = nomUnsignedFourBytes(max_entries.remaining, Endian.Le); + if (updating instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data file updating ${path}: ${updating}`); + } + + limit = 5; + const user: number[] = []; + for (let i = 0; i < limit; i++) { + const value = nomUnsignedFourBytes(updating.remaining, Endian.Le); + if (value instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data user ${path}: ${value}`); + } + updating.remaining = value.remaining; + user.push(value.value); + } + + const data: DataBlock = { + sig: sig.value, + minor: minor.value, + major: major.value, + file_index: file_index.value, + next_index: next_index.value, + block_size: block_size.value, + number_entries: number_entries.value, + max_entries: max_entries.value, + empty_counters, + last_used, + updating: updating.value, + user, + data_path: path, + reader, + }; + + return data; +} + +/** + * Function to extract browser cache entries + * @param index Parsed `Index` object + * @param data Hashmap of parsed data files + * @param path Path to the browser profile + * @param evidence Evidence path to the index file + * @returns Array of `ChromiumCache` entries or `ApplicationError` + */ +function extractCache(index: Index, data: Record, path: ChromiumProfiles, evidence: string): ChromiumCache[] | ApplicationError { + const blocks = [FileType.Block256, FileType.Block1k, FileType.Block4k]; + const values: ChromiumCache[] = []; + for (const entry of index.cache_entries) { + if (!blocks.includes(entry.file_type)) { + continue; + } + // Safe because we check to make sure its one of these values above + const file_type = entry.file_type as FileType.Block256 | FileType.Block1k | FileType.Block4k; + const block = data[file_type]; + if (block === undefined) { + continue; + } + const cache_entries = getCacheEntry(entry.block_number, block); + if (cache_entries instanceof ApplicationError) { + continue; + } + + const clean_url = cache_entries.url.split(" ", 3).at(2) ?? cache_entries.url; + const cache: ChromiumCache = { + version: path.version, + message: `URL cache '${clean_url}'`, + datetime: cache_entries.created, + browser: path.browser, + timestamp_desc: "Browser Cache Created", + artifact: "Browser Cache", + data_type: `applications:${path.browser.toLowerCase()}:cache:entry`, + hash: cache_entries.hash, + cache_state: cache_entries.state, + created: cache_entries.created, + url: clean_url, + request: "1970-01-01T00:00:00Z", + response: "1970-01-01T00:00:00Z", + response2: "1970-01-01T00:00:00Z", + response_headers: [], + cache_key: cache_entries.url, + evidence, + }; + + // Lets get the response headers cache if any + // Response headers should always be first stream cache entry + const response = cache_entries.stream_cache.at(0); + if (response !== undefined && blocks.includes(response.file_type)) { + const file_type = response.file_type as FileType.Block256 | FileType.Block1k | FileType.Block4k; + + const block = data[file_type]; + if (block !== undefined) { + const response_cache = getResponseCache(response.block_number, block); + if (!(response_cache instanceof ApplicationError)) { + cache.request = response_cache.request; + cache.response = response_cache.response; + cache.response2 = response_cache.timestamp; + cache.response_headers = response_cache.headers; + } + } + } + values.push(cache); + } + + return values; +} + +interface CacheEntry { + hash: number; + next_address: CacheAddress; + ranking: CacheAddress; + reuse: number; + refetch: number; + state: CacheState; + created: string; + key_size: number; + long_address: CacheAddress; + stream_sizes: number[]; + stream_cache: CacheAddress[]; + flag: CacheFlag; + self_hash: number; + /**Cache key */ + url: string; +} + +/** + * Function to parse and extract the cached entry + * @param block_number Block number where the cache data is located + * @param data Data block file that contains the the cached entry + * @returns `CacheEntry` object or `ApplicationError` + */ +function getCacheEntry(block_number: number, data: DataBlock): CacheEntry | ApplicationError { + const start = 8192; + const entry_offset = (block_number * data.block_size) + start; + const size = 256; + const bytes = data.reader.readBytes(entry_offset, size); + if (bytes instanceof FileError) { + return new ApplicationError(`CHROMIUM`, `Failed to read cache entry ${data.data_path}: ${bytes}`); + + } + + const hash = nomUnsignedFourBytes(bytes, Endian.Le); + if (hash instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file hash ${data.data_path}: ${hash}`); + } + + const next_address = nomUnsignedFourBytes(hash.remaining, Endian.Le); + if (next_address instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file next address ${data.data_path}: ${next_address}`); + } + + const ranking = nomUnsignedFourBytes(next_address.remaining, Endian.Le); + if (ranking instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file ranking ${data.data_path}: ${ranking}`); + } + + const reuse = nomUnsignedFourBytes(ranking.remaining, Endian.Le); + if (reuse instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file reuse ${data.data_path}: ${reuse}`); + } + + const refetch = nomUnsignedFourBytes(reuse.remaining, Endian.Le); + if (refetch instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file refetch ${data.data_path}: ${refetch}`); + } + + const cache_state = nomUnsignedFourBytes(refetch.remaining, Endian.Le); + if (cache_state instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file cache state ${data.data_path}: ${cache_state}`); + } + + const created = nomUnsignedEightBytes(cache_state.remaining, Endian.Le); + if (created instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file created ${data.data_path}: ${created}`); + } + + const key_size = nomUnsignedFourBytes(created.remaining, Endian.Le); + if (key_size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file key size ${data.data_path}: ${key_size}`); + } + + const long_address = nomUnsignedFourBytes(key_size.remaining, Endian.Le); + if (long_address instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file long address ${data.data_path}: ${long_address}`); + } + + const limit = 4; + const total_limit = limit * 2; + const stream_sizes: number[] = []; + const stream_cache: CacheAddress[] = []; + for (let i = 0; i < total_limit; i++) { + const value = nomUnsignedFourBytes(long_address.remaining, Endian.Le); + if (value instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file array ${data.data_path}: ${value}`); + } + long_address.remaining = value.remaining; + + if (stream_sizes.length !== limit) { + stream_sizes.push(value.value); + } else if (stream_cache.length !== limit) { + stream_cache.push(getCacheAddress(value.value)); + } + } + + const cache_flags = nomUnsignedFourBytes(long_address.remaining, Endian.Le); + if (cache_flags instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file cache flag ${data.data_path}: ${cache_flags}`); + } + + const padding = 16; + const remaining = take(cache_flags.remaining, padding); + if (remaining instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file padding ${data.data_path}: ${remaining}`); + } + + const self_hash = nomUnsignedFourBytes(remaining.remaining as Uint8Array, Endian.Le); + if (self_hash instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache file self hash ${data.data_path}: ${self_hash}`); + } + + const url_size = 160; + const url = take(self_hash.remaining, url_size); + if (url instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse data cache furl ${data.data_path}: ${url}`); + } + + const adjust_time = 1000000n; + const entry: CacheEntry = { + hash: hash.value, + next_address: getCacheAddress(next_address.value), + ranking: getCacheAddress(ranking.value), + reuse: reuse.value, + refetch: refetch.value, + state: getState(cache_state.value), + created: unixEpochToISO(webkitToUnixEpoch(Number(created.value / adjust_time))), + key_size: key_size.value, + long_address: getCacheAddress(long_address.value), + stream_sizes, + stream_cache, + flag: getFlag(cache_flags.value), + self_hash: self_hash.value, + url: extractUtf8String(url.nommed as Uint8Array), + }; + + return entry; +} + +/** + * Determine the cache state value + * @param state Cache state number + * @returns `CacheState` enum + */ +function getState(state: number): CacheState { + switch (state) { + case 0: return CacheState.Normal; + case 1: return CacheState.Evicted; + case 2: return CacheState.Doomed; + default: return CacheState.Unknown; + } +} + +/** + * Determine the cache flag value + * @param flag Cache flag number + * @returns `CacheFlag` enum + */ +function getFlag(flag: number): CacheFlag { + switch (flag) { + case 1: return CacheFlag.Parent; + case 2: return CacheFlag.Child; + default: return CacheFlag.Unknown; + } +} + +interface CacheResponse { + hash: number; + request: string; + response: string; + timestamp: string; + size: number; + headers: string[]; +} + +/** + * Function to extract cache URL response headers + * @param block_number Block number associated with the response headers + * @param data `DataBlock` containing the response headers + * @returns `CacheResponse` object or `ApplicationError` + */ +function getResponseCache(block_number: number, data: DataBlock): CacheResponse | ApplicationError { + const start = 8192; + const entry_offset = (block_number * data.block_size) + start; + const initial_size = 40; + const bytes = data.reader.readBytes(entry_offset, initial_size); + if (bytes instanceof FileError) { + return new ApplicationError(`CHROMIUM`, `Failed to read cache entry ${data.data_path}: ${bytes}`); + + } + + const hash = nomUnsignedFourBytes(bytes, Endian.Le); + if (hash instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file hash ${data.data_path}: ${hash}`); + } + + // Another hash or address? + const unknown = nomUnsignedFourBytes(hash.remaining, Endian.Le); + if (unknown instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file unknowns ${data.data_path}: ${unknown}`); + } + + // Some kind of flag? + const unknown2 = nomUnsignedFourBytes(unknown.remaining, Endian.Le); + if (unknown2 instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file unknown2 ${data.data_path}: ${unknown2}`); + } + + const request = nomUnsignedEightBytes(unknown2.remaining, Endian.Le); + if (request instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file request ${data.data_path}: ${request}`); + } + + const response = nomUnsignedEightBytes(request.remaining, Endian.Le); + if (response instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file response ${data.data_path}: ${response}`); + } + + const timestamp = nomUnsignedEightBytes(response.remaining, Endian.Le); + if (timestamp instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file timestamp ${data.data_path}: ${timestamp}`); + } + + const size = nomUnsignedFourBytes(timestamp.remaining, Endian.Le); + if (size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to parse response data cache file size ${data.data_path}: ${size}`); + } + + const header_offset = initial_size + entry_offset; + let header_bytes = data.reader.readBytes(header_offset, size.value); + if (header_bytes instanceof FileError) { + return new ApplicationError(`CHROMIUM`, `Failed to read header bytes data cache file size ${data.data_path}: ${header_bytes}`); + } + + // Payload is multiple lines with end of string character + const headers: string[] = []; + while (header_bytes.length !== 0) { + const value = takeUntil(header_bytes, new Uint8Array([0])); + if (value instanceof NomError) { + break; + } + + const header = extractUtf8String(value.nommed as Uint8Array); + if (header === "") { + break; + } + headers.push(header); + const remaining = nomUnsignedOneBytes(value.remaining as Uint8Array); + if (remaining instanceof NomError) { + break; + } + header_bytes = remaining.remaining; + } + + // There might be even **more** info to parse + // Such as HTTPS cert info + // Could be interesting to parse later + + const adjust_time = 1000000n; + const cache_response: CacheResponse = { + hash: hash.value, + request: unixEpochToISO(webkitToUnixEpoch(Number(request.value / adjust_time))), + response: unixEpochToISO(webkitToUnixEpoch(Number(response.value / adjust_time))), + timestamp: unixEpochToISO(webkitToUnixEpoch(Number(timestamp.value / adjust_time))), + size: size.value, + headers, + }; + + return cache_response; +} + + +/** + * Function to test the Chromium Cache parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Chromium Cache parsing + */ +export function testChromiumCache(): void { + const path: ChromiumProfiles = { + full_path: "../../test_data/brave", + version: "143.1.85.11", + browser: BrowserType.BRAVE + }; + + + const cache = chromiumCache([path], PlatformType.Darwin); + if (cache.length !== 335) { + throw `Got length ${cache.length} expected 335.......chromiumCache ❌`; + } + + if (!cache[67]?.evidence.includes("index")) { + throw `Got evidence "${cache[67]?.evidence}" expected "index".......chromiumCache ❌`; + } + + if (cache[1]?.message !== "URL cache 'https://cdn.search.brave.com/serp/v3/_app/immutable/chunks/BEuplZ1t.js'") { + throw `Got message "${cache[1]?.message}" expected "URL cache 'https://cdn.search.brave.com/serp/v3/_app/immutable/chunks/BEuplZ1t.js'".......chromiumCache ❌`; + } + + if (cache[13]?.message != "URL cache 'https://cdn.search.brave.com/serp/v3/_app/immutable/chunks/DsnmJJEf.js'") { + throw `Got message ${cache[13]?.message} expected "URL cache 'https://cdn.search.brave.com/serp/v3/_app/immutable/chunks/DsnmJJEf.js'".......chromiumCache ❌`; + } + + if (cache[257]?.response_headers.length !== 24) { + throw `Got header count ${cache[257]?.response_headers.length} expected "24".......chromiumCache ❌`; + } + + console.info(` Function chromiumCache ✅`); + + let data_path = "../../test_data/brave/v143.1.85.11/Cache/Cache_Data/data_3"; + let data = parseData(data_path); + if (data instanceof ApplicationError) { + throw data; + } + const response_block = 408; + const headers = getResponseCache(response_block, data); + if (headers instanceof ApplicationError) { + throw headers; + } + + if (headers.hash !== 4536) { + throw `Got header hash ${headers.hash} expected "4536".......getResponseCache ❌`; + } + + if (headers.request !== "2025-12-16T22:18:30.000Z") { + throw `Got header request ${headers.request} expected "2025-12-16T22:18:30.000Z".......getResponseCache ❌`; + } + + if (headers.headers.length !== 24) { + throw `Got header length ${headers.request.length} expected "24".......getResponseCache ❌`; + } + + console.info(` Function getResponseCache ✅`); + + if (getFlag(1) !== CacheFlag.Parent) { + throw `Got cache flag ${getFlag(1)} expected "Parent".......getFlag ❌`; + } + + if (getFlag(2) !== CacheFlag.Child) { + throw `Got cache flag ${getFlag(2)} expected "Child".......getFlag ❌`; + } + + console.info(` Function getFlag ✅`); + + if (getState(0) !== CacheState.Normal) { + throw `Got cache state ${getState(0)} expected "Normal".......getState ❌`; + } + + if (getState(1) !== CacheState.Evicted) { + throw `Got cache state ${getState(1)} expected "Evicted".......getState ❌`; + } + + if (getState(2) !== CacheState.Doomed) { + throw `Got cache state ${getState(2)} expected "Doomed".......getState ❌`; + } + + console.info(` Function getState ✅`); + + data_path = "../../test_data/brave/v143.1.85.11/Cache/Cache_Data/data_1"; + data = parseData(data_path); + if (data instanceof ApplicationError) { + throw data; + } + const entry_block = 249; + const entry = getCacheEntry(entry_block, data); + if (entry instanceof ApplicationError) { + throw entry; + } + + if (entry.hash !== 475660310) { + throw `Got entry hash ${entry.hash} expected "475660310".......getCacheEntry ❌`; + } + + if (entry.created !== "2025-12-16T22:18:30.000Z") { + throw `Got entry created ${entry.created} expected "2025-12-16T22:18:30.000Z".......getCacheEntry ❌`; + } + + if (entry.url !== "1/0/_dk_https://brave.com https://brave.com https://cdn.search.brave.com/serp/v3/_app/immutable/chunks/BEuplZ1t.js") { + throw `Got entry url ${entry.url} expected "1/0/_dk_https://brave.com https://brave.com https://cdn.search.brave.com/serp/v3/_app/immutable/chunks/BEuplZ1t.js".......getCacheEntry ❌`; + } + + console.info(` Function getCacheEntry ✅`); + + const index_path = "../../test_data/brave/v143.1.85.11/Cache/Cache_Data/index"; + const index = parseIndex(index_path); + if (index instanceof ApplicationError) { + throw index; + } + + const cache_data = extractCache(index, { + [FileType.Block256]: data, + [FileType.Ranking]: undefined, + [FileType.Block1k]: undefined, + [FileType.Block4k]: undefined + }, path, index_path); + if (cache_data instanceof ApplicationError) { + throw cache_data; + } + + if (cache_data.length !== 334) { + throw `Got cache length ${cache_data.length} expected "334".......extractCache ❌`; + } + + console.info(` Function extractCache ✅`); + + if (data.block_size !== 256) { + throw `Got data block size ${data.block_size} expected "256".......parseData ❌`; + } + + console.info(` Function parseData ✅`); + + const addr = getCacheAddress(3238199704); + if (addr.block_number !== 408) { + throw `Got address block size ${addr.block_number} expected "408".......getCacheAddress ❌`; + } + + if (addr.initialized !== true) { + throw `Got address initialized ${addr.initialized} expected "true".......getCacheAddress ❌`; + } + + console.info(` Function getCacheAddress ✅`); + + const file_type = [0, 1, 2, 3, 4, 5, 6, 7]; + for (const entry of file_type) { + if (getFileType(entry) === FileType.Unknown) { + throw entry; + } + } + + console.info(` Function getFileType ✅`); + + if (index.created !== "2025-12-16T22:18:27.000Z") { + throw `Got index created ${index.created} expected "2025-12-16T22:18:27.000Z".......parseIndex ❌`; + } + + if (index.entries !== 334) { + throw `Got index entries ${index.entries} expected "334".......parseIndex ❌`; + } + + console.info(` Function parseIndex ✅`); +} \ No newline at end of file diff --git a/src/applications/chromium/cr.ts b/src/applications/chromium/cr.ts index 6a5a969f..1c1efb43 100644 --- a/src/applications/chromium/cr.ts +++ b/src/applications/chromium/cr.ts @@ -1,526 +1,537 @@ -import { BrowserType, ChromiumAutofill, ChromiumBookmarks, ChromiumCookies, ChromiumDips, ChromiumDownloads, ChromiumFavicons, ChromiumHistory, ChromiumLocalStorage, ChromiumLogins, ChromiumProfiles, ChromiumSession, ChromiumShortcuts, Extension, Preferences } from "../../../types/applications/chromium"; -import { getEnvValue } from "../../environment/env"; -import { FileError } from "../../filesystem/errors"; -import { glob, readTextFile } from "../../filesystem/files"; -import { SystemError } from "../../system/error"; -import { dumpData, Output } from "../../system/output"; -import { PlatformType } from "../../system/systeminfo"; -import { ApplicationError, ErrorName } from "../errors"; -import { chromiumBookmarks, chromiumExtensions } from "./json"; -import { chromiumLocalStorage } from "./level"; -import { chromiumPreferences } from "./preferences"; -import { chromiumSessions } from "./sessions"; -import { chromiumAutofill, chromiumCookies, chromiumDips, chromiumDownloads, chromiumFavicons, chromiumHistory, chromiumLogins, chromiumShortcuts } from "./sqlite"; - -/** - * Class to extract Chromium browser information. - * Since many browsers are based on Chromium we can extend this class and reuse most of the parsers - */ -export class Chromium { - protected paths: ChromiumProfiles[] = []; - protected platform: PlatformType; - protected unfold: boolean; - protected browser: BrowserType; - - /** - * Construct a `Chromium` object that can be used to parse browser data - * @param platform OS `PlatformType` - * @param unfold Attempt to parse URLs. Default is `false` - * @param alt_path Optional alternative path to directory contain Chromium data - * @returns `Chromium` instance class - */ - constructor(platform: PlatformType, unfold = false, browser = BrowserType.CHROMIUM, alt_path?: string) { - this.platform = platform; - this.unfold = unfold; - this.browser = browser; - - let browser_error = new ApplicationError("CHROMIUM", "").name; - switch (this.browser) { - case BrowserType.CHROME: { - browser_error = new ApplicationError("CHROME", "").name; - break; - } - case BrowserType.EDGE: { - browser_error = new ApplicationError("EDGE", "").name; - break; - } - case BrowserType.CHROMIUM: { - browser_error = new ApplicationError("CHROMIUM", "").name; - break; - } - case BrowserType.COMET: { - browser_error = new ApplicationError("COMET", "").name; - break; - } - case BrowserType.BRAVE: { - browser_error = new ApplicationError("BRAVE", "").name; - break; - } - default: { - break; - } - } - - if (alt_path === undefined) { - const results = this.profiles(platform, browser_error); - if (results instanceof ApplicationError) { - return; - } - this.paths = results; - return; - } - const browser_version = this.version(this.platform, alt_path, browser_error); - if (browser_version instanceof ApplicationError) { - return; - } - - this.paths = [{ - full_path: alt_path, - version: browser_version, - browser: this.browser, - }]; - } - - /** - * Extract browser history - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumHistory` - */ - public history(offset = 0, limit = 100): ChromiumHistory[] { - const query = `SELECT - urls.id AS id, - urls.url AS url, - title, - visit_count, - typed_count, - last_visit_time, - hidden, - visits.id AS visits_id, - from_visit, - transition, - segment_id, - visit_duration, - opener_visit - FROM - urls - JOIN visits ON urls.id = visits.url LIMIT ${limit} OFFSET ${offset}`; - return chromiumHistory(this.paths, this.platform, this.unfold, query); - } - - /** - * Extract Chromium browser downloads. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of browser history - */ - public downloads(offset = 0, limit = 100): ChromiumDownloads[] { - const query = `SELECT - downloads.id AS downloads_id, - guid, - current_path, - target_path, - start_time, - received_bytes, - total_bytes, - state, - danger_type, - interrupt_reason, - hash, - end_time, - opened, - last_access_time, - transient, - referrer, - site_url, - tab_url, - tab_referrer_url, - http_method, - by_ext_id, - by_ext_name, - etag, - last_modified, - mime_type, - original_mime_type, - downloads_url_chains.id AS downloads_url_chain_id, - chain_index, - url - FROM - downloads - JOIN downloads_url_chains ON downloads_url_chains.id = downloads.id LIMIT ${limit} OFFSET ${offset}`; - return chromiumDownloads(this.paths, this.platform, query); - } - - /** - * Extract Chromium browser cookies - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumCookies` - */ - public cookies(offset = 0, limit = 100): ChromiumCookies[] { - const query = `SELECT * FROM cookies LIMIT ${limit} OFFSET ${offset}`; - return chromiumCookies(this.paths, this.platform, query); - } - - /** - * Function to parse Chromium AutoFill information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumAutofill` - */ - public autofill(offset = 0, limit = 100): ChromiumAutofill[] { - const query = `SELECT name, value, date_created, date_last_used, count, value_lower from autofill LIMIT ${limit} OFFSET ${offset}`; - return chromiumAutofill(this.paths, this.platform, query); - } - - /** - * Function to parse Chromium Favicons information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumFavicons` - */ - public favicons(offset = 0, limit = 100): ChromiumFavicons[] { - const query = `SELECT url, last_updated, page_url FROM favicons JOIN favicon_bitmaps ON favicons.id = favicon_bitmaps.id JOIN icon_mapping ON icon_mapping.icon_id = favicons.id LIMIT ${limit} OFFSET ${offset}`; - return chromiumFavicons(this.paths, this.platform, query); - } - - /** - * Function to parse Chromium Shortcut information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumShortcuts` - */ - public shortcuts(offset = 0, limit = 100): ChromiumShortcuts[] { - const query = `SELECT id, text, fill_into_edit, url, contents, description, type, keyword, last_access_time FROM omni_box_shortcuts LIMIT ${limit} OFFSET ${offset}`; - return chromiumShortcuts(this.paths, this.platform, query); - } - - /** - * Function to parse Chromium Login information. - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumLogins` - */ - public logins(offset = 0, limit = 100): ChromiumLogins[] { - const query = `SELECT * from logins LIMIT ${limit} OFFSET ${offset}`; - return chromiumLogins(this.paths, this.platform, query); - } - - /** - * Function to parse Chromium Detect Incidental Party State (DIPS) information - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `ChromiumDips` - */ - public dips(offset = 0, limit = 100): ChromiumDips[] { - const query = `SELECT * from bounces LIMIT ${limit} OFFSET ${offset}`; - return chromiumDips(this.paths, this.platform, query); - } - - /** - * Get installed Chromium extensions - * @returns Array of parsed extensions - */ - public extensions(): Extension[] { - return chromiumExtensions(this.paths, this.platform); - } - - /** - * Get Chromium Preferences - * @returns Array of `Preferences` for each user - */ - public preferences(): Preferences[] { - return chromiumPreferences(this.paths, this.platform); - } - - /** - * Get Chromium Bookmarks - * @returns Array of `ChromiumBookmarks` for each user - */ - public bookmarks(): ChromiumBookmarks[] { - return chromiumBookmarks(this.paths, this.platform); - } - - /** - * Get Chromium Local Storage - * @returns Array of `ChromiumLocalStorage` for each user - */ - public localStorage(): ChromiumLocalStorage[] { - return chromiumLocalStorage(this.paths, this.platform); - } - - /** - * Get Chromium Sessions - * @returns Array of `ChromiumSession` for each user - */ - public sessions(): ChromiumSession[] { - return chromiumSessions(this.paths, this.platform); - } - - /** - * Function to timeline all Chromium artifacts. Similar to [Hindsight](https://github.com/obsidianforensics/hindsight) - * @param output `Output` structure object. Format type should be either `JSON` or `JSONL`. `JSONL` is recommended - */ - public retrospect(output: Output): void { - let offset = 0; - const limit = 100; - while (true) { - const entries = this.history(offset, limit); - if (entries.length === 0) { - break; - } - if (!this.unfold) { - entries.forEach(x => delete x["unfold"]); - } - const status = dumpData(entries, `retrospect_${this.browser}_history`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} history: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.cookies(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_cookies`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} cookies: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.downloads(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_downloads`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} downloads: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.autofill(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_autofill`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} autofill: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.logins(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_logins`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} logins: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.dips(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_dips`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} dips: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.favicons(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_favicons`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} favicons: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.shortcuts(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_${this.browser}_shortcuts`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} shortcuts: ${status}`); - } - offset += limit; - } - - const ext = this.extensions(); - let status = dumpData(ext, `retrospect_${this.browser}_extensions`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} extensions: ${status}`); - } - - const prefs = this.preferences(); - status = dumpData(prefs, `retrospect_${this.browser}_preferences`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} preferences: ${status}`); - } - - const books = this.bookmarks(); - status = dumpData(books, `retrospect_${this.browser}_bookmarks`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} bookmarks: ${status}`); - } - - const level = this.localStorage(); - status = dumpData(level, `retrospect_${this.browser}_localstorage`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} localstorage: ${status}`); - } - - const sess = this.sessions(); - status = dumpData(sess, `retrospect_${this.browser}_sessions`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline ${this.browser} sessions: ${status}`); - } - } - - /** - * Get base path for all browser data - * @param platform OS `PlatformType` - * @returns Array of `ChromiumProfiles` or `ApplicationError` - */ - private profiles(platform: PlatformType, browser: ErrorName): ChromiumProfiles[] | ApplicationError { - const browser_path = this.browser_path(platform, this.browser, browser); - if (browser_path instanceof ApplicationError) { - return browser_path; - } - const browser_paths = glob(browser_path); - if (browser_paths instanceof FileError) { - return new ApplicationError( - `${browser}`, - `failed to glob ${platform} paths: ${browser_paths}`, - ); - } - - const browser_profiles: ChromiumProfiles[] = []; - for (const entry of browser_paths) { - if (!entry.is_directory) { - continue; - } - const browser_version = this.version(this.platform, entry.full_path, browser); - if (browser_version instanceof ApplicationError) { - continue; - } - - const profile: ChromiumProfiles = { - full_path: entry.full_path, - version: browser_version, - browser: this.browser, - }; - - browser_profiles.push(profile); - } - return browser_profiles; - } - - /** - * Function to determine browser version - * @param platform OS `PlatformType` - * @param path Path to base browser user profile - * @returns Version number or `ApplicationError` - */ - private version(platform: PlatformType, path: string, browser: ErrorName): string | ApplicationError { - let version_path = `${path}/Last Version`; - if (platform === PlatformType.Windows) { - version_path = `${path}\\Last Version`; - } - // Version is just a single line - const text_data = readTextFile(version_path); - if (text_data instanceof FileError) { - return new ApplicationError(`${browser}`, `could not read ${version_path}: ${text_data}`); - } - - return text_data; - } - - /** - * Function to identify base paths to Chromium based browsers - * @param platform OS `PlatformType` - * @param browser_type Chromium based `BrowserType` - * @param browser_error `BrowserType` ErrorName - * @returns Glob to base directory for all users associated with the browser or `ApplicationError` - */ - private browser_path(platform: PlatformType, browser_type: BrowserType, browser_error: ErrorName): string | ApplicationError { - if (platform === PlatformType.Darwin) { - if (browser_type === BrowserType.CHROME) { - return "/Users/*/Library/Application Support/Google/Chrome"; - } else if (browser_type === BrowserType.EDGE) { - return "/Users/*/Library/Application Support/Microsoft Edge"; - } else if (browser_type === BrowserType.CHROMIUM) { - return "/Users/*/Library/Application Support/Chromium"; - } else if (browser_type === BrowserType.COMET) { - return "/Users/*/Library/Application Support/Perplexity/Comet"; - } else if (browser_type === BrowserType.BRAVE) { - return "/Users/*/Library/Application Support/BraveSoftware/Brave-Browser"; - } - - return new ApplicationError(`${browser_error}`, `Unsupported macOS browser! ${browser_type}`); - } - - if (platform === PlatformType.Linux) { - if (browser_type === BrowserType.CHROME) { - return "/home/*/.config/Google/Chrome"; - } else if (browser_type === BrowserType.EDGE) { - return "/home/*/.config/Microsoft Edge"; - } else if (browser_type === BrowserType.CHROMIUM) { - return "/home/*/.config/chromium/"; - } else if (browser_type === BrowserType.COMET) { - return "/home/*/.config/Perplexity/Comet"; - } else if (browser_type === BrowserType.BRAVE) { - return "/home/*/.config/BraveSoftware/Brave-Browser"; - } - - return new ApplicationError(`${browser_error}`, `Unsupported Linux browser! ${browser_type}`); - } - - if (platform === PlatformType.Windows) { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - if (browser_type === BrowserType.CHROME) { - return `${drive}\\Users\\*\\AppData\\Local\\Google\\Chrome\\User Data`; - } else if (browser_type === BrowserType.EDGE) { - return `${drive}\\Users\\*\\AppData\\Local\\Microsoft\\Edge\\User Data`; - } else if (browser_type === BrowserType.CHROMIUM) { - return `${drive}\\Users\\*\\AppData\\Local\\Chromium\\User Data`; - } else if (browser_type === BrowserType.COMET) { - return `${drive}\\Users\\*\\AppData\\Local\\Perplexity\\Comet\\User Data`; - } else if (browser_type === BrowserType.BRAVE) { - return `${drive}\\Users\\*\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data`; - } - - return new ApplicationError(`${browser_error}`, `Unsupported Windows browser! ${browser_type}`); - } - - return new ApplicationError(`${browser_error}`, `Unsupported ${platform} browser! ${browser_type}`); - - } +import { BrowserType, ChromiumAutofill, ChromiumBookmarks, ChromiumCache, ChromiumCookies, ChromiumDips, ChromiumDownloads, ChromiumFavicons, ChromiumHistory, ChromiumLocalStorage, ChromiumLogins, ChromiumProfiles, ChromiumSession, ChromiumShortcuts, Extension, Preferences } from "../../../types/applications/chromium"; +import { getEnvValue } from "../../environment/env"; +import { FileError } from "../../filesystem/errors"; +import { glob, readTextFile } from "../../filesystem/files"; +import { SystemError } from "../../system/error"; +import { dumpData, Output } from "../../system/output"; +import { PlatformType } from "../../system/systeminfo"; +import { ApplicationError, ErrorName } from "../errors"; +import { chromiumCache } from "./cache"; +import { chromiumBookmarks, chromiumExtensions } from "./json"; +import { chromiumLocalStorage } from "./level"; +import { chromiumPreferences } from "./preferences"; +import { chromiumSessions } from "./sessions"; +import { chromiumAutofill, chromiumCookies, chromiumDips, chromiumDownloads, chromiumFavicons, chromiumHistory, chromiumLogins, chromiumShortcuts } from "./sqlite"; + +/** + * Class to extract Chromium browser information. + * Since many browsers are based on Chromium we can extend this class and reuse most of the parsers + */ +export class Chromium { + protected paths: ChromiumProfiles[] = []; + protected platform: PlatformType; + protected unfold: boolean; + protected browser: BrowserType; + + /** + * Construct a `Chromium` object that can be used to parse browser data + * @param platform OS `PlatformType` + * @param unfold Attempt to parse URLs. Default is `false` + * @param alt_path Optional alternative path to directory contain Chromium data + * @returns `Chromium` instance class + */ + constructor(platform: PlatformType, unfold = false, browser = BrowserType.CHROMIUM, alt_path?: string) { + this.platform = platform; + this.unfold = unfold; + this.browser = browser; + + let browser_error = new ApplicationError("CHROMIUM", "").name; + switch (this.browser) { + case BrowserType.CHROME: { + browser_error = new ApplicationError("CHROME", "").name; + break; + } + case BrowserType.EDGE: { + browser_error = new ApplicationError("EDGE", "").name; + break; + } + case BrowserType.CHROMIUM: { + browser_error = new ApplicationError("CHROMIUM", "").name; + break; + } + case BrowserType.COMET: { + browser_error = new ApplicationError("COMET", "").name; + break; + } + case BrowserType.BRAVE: { + browser_error = new ApplicationError("BRAVE", "").name; + break; + } + default: { + break; + } + } + + if (alt_path === undefined) { + const results = this.profiles(platform, browser_error); + if (results instanceof ApplicationError) { + return; + } + this.paths = results; + return; + } + const browser_version = this.version(this.platform, alt_path, browser_error); + if (browser_version instanceof ApplicationError) { + return; + } + + this.paths = [{ + full_path: alt_path, + version: browser_version, + browser: this.browser, + }]; + } + + /** + * Extract browser history + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumHistory` + */ + public history(offset = 0, limit = 100): ChromiumHistory[] { + const query = `SELECT + urls.id AS id, + urls.url AS url, + title, + visit_count, + typed_count, + last_visit_time, + hidden, + visits.id AS visits_id, + from_visit, + transition, + segment_id, + visit_duration, + opener_visit + FROM + urls + JOIN visits ON urls.id = visits.url LIMIT ${limit} OFFSET ${offset}`; + return chromiumHistory(this.paths, this.platform, this.unfold, query); + } + + /** + * Extract Chromium browser downloads. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of browser history + */ + public downloads(offset = 0, limit = 100): ChromiumDownloads[] { + const query = `SELECT + downloads.id AS downloads_id, + guid, + current_path, + target_path, + start_time, + received_bytes, + total_bytes, + state, + danger_type, + interrupt_reason, + hash, + end_time, + opened, + last_access_time, + transient, + referrer, + site_url, + tab_url, + tab_referrer_url, + http_method, + by_ext_id, + by_ext_name, + etag, + last_modified, + mime_type, + original_mime_type, + downloads_url_chains.id AS downloads_url_chain_id, + chain_index, + url + FROM + downloads + JOIN downloads_url_chains ON downloads_url_chains.id = downloads.id LIMIT ${limit} OFFSET ${offset}`; + return chromiumDownloads(this.paths, this.platform, query); + } + + /** + * Extract Chromium browser cookies + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumCookies` + */ + public cookies(offset = 0, limit = 100): ChromiumCookies[] { + const query = `SELECT * FROM cookies LIMIT ${limit} OFFSET ${offset}`; + return chromiumCookies(this.paths, this.platform, query); + } + + /** + * Function to parse Chromium AutoFill information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumAutofill` + */ + public autofill(offset = 0, limit = 100): ChromiumAutofill[] { + const query = `SELECT name, value, date_created, date_last_used, count, value_lower from autofill LIMIT ${limit} OFFSET ${offset}`; + return chromiumAutofill(this.paths, this.platform, query); + } + + /** + * Function to parse Chromium Favicons information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumFavicons` + */ + public favicons(offset = 0, limit = 100): ChromiumFavicons[] { + const query = `SELECT url, last_updated, page_url FROM favicons JOIN favicon_bitmaps ON favicons.id = favicon_bitmaps.id JOIN icon_mapping ON icon_mapping.icon_id = favicons.id LIMIT ${limit} OFFSET ${offset}`; + return chromiumFavicons(this.paths, this.platform, query); + } + + /** + * Function to parse Chromium Shortcut information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumShortcuts` + */ + public shortcuts(offset = 0, limit = 100): ChromiumShortcuts[] { + const query = `SELECT id, text, fill_into_edit, url, contents, description, type, keyword, last_access_time FROM omni_box_shortcuts LIMIT ${limit} OFFSET ${offset}`; + return chromiumShortcuts(this.paths, this.platform, query); + } + + /** + * Function to parse Chromium Login information. + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumLogins` + */ + public logins(offset = 0, limit = 100): ChromiumLogins[] { + const query = `SELECT * from logins LIMIT ${limit} OFFSET ${offset}`; + return chromiumLogins(this.paths, this.platform, query); + } + + /** + * Function to parse Chromium Detect Incidental Party State (DIPS) information + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `ChromiumDips` + */ + public dips(offset = 0, limit = 100): ChromiumDips[] { + const query = `SELECT * from bounces LIMIT ${limit} OFFSET ${offset}`; + return chromiumDips(this.paths, this.platform, query); + } + + /** + * Get installed Chromium extensions + * @returns Array of parsed extensions + */ + public extensions(): Extension[] { + return chromiumExtensions(this.paths, this.platform); + } + + /** + * Get Chromium Preferences + * @returns Array of `Preferences` for each user + */ + public preferences(): Preferences[] { + return chromiumPreferences(this.paths, this.platform); + } + + /** + * Get Chromium Bookmarks + * @returns Array of `ChromiumBookmarks` for each user + */ + public bookmarks(): ChromiumBookmarks[] { + return chromiumBookmarks(this.paths, this.platform); + } + + /** + * Get Chromium Local Storage + * @returns Array of `ChromiumLocalStorage` for each user + */ + public localStorage(): ChromiumLocalStorage[] { + return chromiumLocalStorage(this.paths, this.platform); + } + + /** + * Get Chromium Sessions + * @returns Array of `ChromiumSession` for each user + */ + public sessions(): ChromiumSession[] { + return chromiumSessions(this.paths, this.platform); + } + + public cache(): ChromiumCache[] { + return chromiumCache(this.paths, this.platform); + } + + /** + * Function to timeline all Chromium artifacts. Similar to [Hindsight](https://github.com/obsidianforensics/hindsight) + * @param output `Output` structure object. Format type should be either `JSON` or `JSONL`. `JSONL` is recommended + */ + public retrospect(output: Output): void { + let offset = 0; + const limit = 100; + while (true) { + const entries = this.history(offset, limit); + if (entries.length === 0) { + break; + } + if (!this.unfold) { + entries.forEach(x => delete x["unfold"]); + } + const status = dumpData(entries, `retrospect_${this.browser}_history`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} history: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.cookies(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_cookies`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} cookies: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.downloads(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_downloads`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} downloads: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.autofill(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_autofill`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} autofill: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.logins(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_logins`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} logins: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.dips(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_dips`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} dips: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.favicons(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_favicons`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} favicons: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.shortcuts(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_${this.browser}_shortcuts`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} shortcuts: ${status}`); + } + offset += limit; + } + + const ext = this.extensions(); + let status = dumpData(ext, `retrospect_${this.browser}_extensions`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} extensions: ${status}`); + } + + const prefs = this.preferences(); + status = dumpData(prefs, `retrospect_${this.browser}_preferences`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} preferences: ${status}`); + } + + const books = this.bookmarks(); + status = dumpData(books, `retrospect_${this.browser}_bookmarks`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} bookmarks: ${status}`); + } + + const level = this.localStorage(); + status = dumpData(level, `retrospect_${this.browser}_localstorage`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} localstorage: ${status}`); + } + + const sess = this.sessions(); + status = dumpData(sess, `retrospect_${this.browser}_sessions`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} sessions: ${status}`); + } + + const cache = this.cache(); + status = dumpData(cache, `retrospect_${this.browser}_cache`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline ${this.browser} cache: ${status}`); + } + } + + /** + * Get base path for all browser data + * @param platform OS `PlatformType` + * @returns Array of `ChromiumProfiles` or `ApplicationError` + */ + private profiles(platform: PlatformType, browser: ErrorName): ChromiumProfiles[] | ApplicationError { + const browser_path = this.browser_path(platform, this.browser, browser); + if (browser_path instanceof ApplicationError) { + return browser_path; + } + const browser_paths = glob(browser_path); + if (browser_paths instanceof FileError) { + return new ApplicationError( + `${browser}`, + `failed to glob ${platform} paths: ${browser_paths}`, + ); + } + + const browser_profiles: ChromiumProfiles[] = []; + for (const entry of browser_paths) { + if (!entry.is_directory) { + continue; + } + const browser_version = this.version(this.platform, entry.full_path, browser); + if (browser_version instanceof ApplicationError) { + continue; + } + + const profile: ChromiumProfiles = { + full_path: entry.full_path, + version: browser_version, + browser: this.browser, + }; + + browser_profiles.push(profile); + } + return browser_profiles; + } + + /** + * Function to determine browser version + * @param platform OS `PlatformType` + * @param path Path to base browser user profile + * @returns Version number or `ApplicationError` + */ + private version(platform: PlatformType, path: string, browser: ErrorName): string | ApplicationError { + let version_path = `${path}/Last Version`; + if (platform === PlatformType.Windows) { + version_path = `${path}\\Last Version`; + } + // Version is just a single line + const text_data = readTextFile(version_path); + if (text_data instanceof FileError) { + return new ApplicationError(`${browser}`, `could not read ${version_path}: ${text_data}`); + } + + return text_data; + } + + /** + * Function to identify base paths to Chromium based browsers + * @param platform OS `PlatformType` + * @param browser_type Chromium based `BrowserType` + * @param browser_error `BrowserType` ErrorName + * @returns Glob to base directory for all users associated with the browser or `ApplicationError` + */ + private browser_path(platform: PlatformType, browser_type: BrowserType, browser_error: ErrorName): string | ApplicationError { + if (platform === PlatformType.Darwin) { + if (browser_type === BrowserType.CHROME) { + return "/Users/*/Library/Application Support/Google/Chrome"; + } else if (browser_type === BrowserType.EDGE) { + return "/Users/*/Library/Application Support/Microsoft Edge"; + } else if (browser_type === BrowserType.CHROMIUM) { + return "/Users/*/Library/Application Support/Chromium"; + } else if (browser_type === BrowserType.COMET) { + return "/Users/*/Library/Application Support/Perplexity/Comet"; + } else if (browser_type === BrowserType.BRAVE) { + return "/Users/*/Library/Application Support/BraveSoftware/Brave-Browser"; + } + + return new ApplicationError(`${browser_error}`, `Unsupported macOS browser! ${browser_type}`); + } + + if (platform === PlatformType.Linux) { + if (browser_type === BrowserType.CHROME) { + return "/home/*/.config/Google/Chrome"; + } else if (browser_type === BrowserType.EDGE) { + return "/home/*/.config/Microsoft Edge"; + } else if (browser_type === BrowserType.CHROMIUM) { + return "/home/*/.config/chromium/"; + } else if (browser_type === BrowserType.COMET) { + return "/home/*/.config/Perplexity/Comet"; + } else if (browser_type === BrowserType.BRAVE) { + return "/home/*/.config/BraveSoftware/Brave-Browser"; + } + + return new ApplicationError(`${browser_error}`, `Unsupported Linux browser! ${browser_type}`); + } + + if (platform === PlatformType.Windows) { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + if (browser_type === BrowserType.CHROME) { + return `${drive}\\Users\\*\\AppData\\Local\\Google\\Chrome\\User Data`; + } else if (browser_type === BrowserType.EDGE) { + return `${drive}\\Users\\*\\AppData\\Local\\Microsoft\\Edge\\User Data`; + } else if (browser_type === BrowserType.CHROMIUM) { + return `${drive}\\Users\\*\\AppData\\Local\\Chromium\\User Data`; + } else if (browser_type === BrowserType.COMET) { + return `${drive}\\Users\\*\\AppData\\Local\\Perplexity\\Comet\\User Data`; + } else if (browser_type === BrowserType.BRAVE) { + return `${drive}\\Users\\*\\AppData\\Local\\BraveSoftware\\Brave-Browser\\User Data`; + } + + return new ApplicationError(`${browser_error}`, `Unsupported Windows browser! ${browser_type}`); + } + + return new ApplicationError(`${browser_error}`, `Unsupported ${platform} browser! ${browser_type}`); + + } } \ No newline at end of file diff --git a/src/applications/chromium/json.ts b/src/applications/chromium/json.ts index 1c1f7b2d..0910c821 100644 --- a/src/applications/chromium/json.ts +++ b/src/applications/chromium/json.ts @@ -1,223 +1,223 @@ -import { BookmarkType, BrowserType, ChromiumBookmarks, ChromiumProfiles, Extension, } from "../../../types/applications/chromium"; -import { FileError } from "../../filesystem/errors"; -import { glob, readTextFile, stat } from "../../filesystem/files"; -import { PlatformType } from "../../system/systeminfo"; -import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; - -/** - * Get installed Chromium extensions - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @returns Array of parsed extensions - */ -export function chromiumExtensions(paths: ChromiumProfiles[], platform: PlatformType): Extension[] { - const hits: Extension[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/Extensions/*/*/manifest.json`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Extensions\\*\\*\\manifest.json`; - } - - const ext_paths = glob(full_path); - if (ext_paths instanceof FileError) { - continue; - } - - for (const ext_entry of ext_paths) { - const extension = readTextFile(ext_entry.full_path); - if (extension instanceof FileError) { - console.warn(`could not read file ${path}: ${extension}`); - continue; - } - - const data = JSON.parse(extension); - const ext_info: Extension = { - version: path.version, - message: `Extension: ${data["name"] ?? ""} | Version: ${data["version"] ?? ""}`, - datetime: "1970-01-01T00:00:00.000Z", - browser: path.browser, - timestamp_desc: "Extension Created", - artifact: "Browser Extension", - data_type: `applications:${path.browser.toLowerCase()}:extensions:entry`, - name: data["name"] ?? "", - author: "", - description: data["description"] ?? "", - manifest: ext_entry.full_path, - extension_version: data["version"] ?? "", - }; - const meta = stat(ext_entry.full_path); - if (!(meta instanceof FileError)) { - ext_info.datetime = meta.created; - } - - if (data["author"] === undefined) { - hits.push(ext_info); - continue; - } - - if (data["author"]["email"] !== undefined) { - ext_info.author = data["author"]["email"] ?? ""; - } - - hits.push(ext_info); - } - } - return hits; -} - - -/** - * Get Chromium Bookmarks - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @returns Array of `ChromiumBookmarks` - */ -export function chromiumBookmarks(paths: ChromiumProfiles[], platform: PlatformType): ChromiumBookmarks[] { - let hits: ChromiumBookmarks[] = []; - - for (const path of paths) { - let full_path = `${path.full_path}/*/Bookmarks`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Bookmarks`; - } - const book_paths = glob(full_path); - if (book_paths instanceof FileError) { - continue; - } - - for (const entry of book_paths) { - const results = readTextFile(entry.full_path); - if (results instanceof FileError) { - console.warn(`could not read file ${entry.full_path}: ${results}`); - continue; - } - - const book_json = JSON.parse(results); - const bar = book_json["roots"]["bookmark_bar"]["children"] as - | Record[] | undefined>[] - | undefined; - hits = hits.concat(getBookmarkChildren(bar, entry.full_path, path.version, BookmarkType.Bar, path.browser)); - - const other = book_json["roots"]["other"]["children"] as - | Record[] | undefined>[] - | undefined; - hits = hits.concat(getBookmarkChildren(other, entry.full_path, path.version, BookmarkType.Other, path.browser)); - - const synced = book_json["roots"]["other"]["synced"] as - | Record[] | undefined>[] - | undefined; - hits = hits.concat(getBookmarkChildren(synced, entry.full_path, path.version, BookmarkType.Sync, path.browser)); - } - } - return hits; -} - -/** - * Function to try to get children bookmark info - * @param book Parsed Bookmark children - * @returns Extract array of `ChromiumBookmarkChildren` - */ -function getBookmarkChildren( - book: - | Record[] | undefined>[] - | undefined, - path: string, - version: string, - bookmark_type: BookmarkType, - browser: BrowserType -): ChromiumBookmarks[] { - let books: ChromiumBookmarks[] = []; - if (typeof book === "undefined") { - return books; - } - const adjust_time = 1000000n; - for (const entry of book) { - if (typeof entry["children"] === "undefined") { - const book_entry: ChromiumBookmarks = { - date_added: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_added"] as string) / adjust_time) - )), - date_last_used: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_last_used"] as string) / adjust_time) - )), - guid: entry["guid"] as string, - id: Number(entry["id"] as string), - name: entry["name"] as string, - type: entry["type"] as string, - url: entry["url"] as string, - bookmark_type, - path, - version, - message: `Bookmark - ${entry["name"] as string}`, - datetime: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_added"] as string) / adjust_time) - )), - timestamp_desc: "Bookmark Added", - artifact: "Browser Bookmark", - data_type: `applications:${browser.toLowerCase()}:bookmark:entry`, - browser, - }; - books.push(book_entry); - continue; - } - - books = books.concat( - getBookmarkChildren( - entry["children"] as - | Record[] | undefined>[] - | undefined, - path, - version, - bookmark_type, - browser - ), - ); - } - - return books; -} - -/** - * Function to test the Chromium JSON file parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Chromium JSON parsing - */ -export function testChromiumJsonFiles(): void { - const path: ChromiumProfiles = { - full_path: "../../test_data/edge", - version: "141", - browser: BrowserType.EDGE - }; - const ext = chromiumExtensions([path], PlatformType.Darwin); - if (ext.length !== 3) { - throw `Got length ${ext.length} expected 3.......chromiumExtensions ❌`; - } - if (ext[0]?.name != "__MSG_extName__") { - throw `Got name ${ext[0]?.name} expected "__MSG_extName__".......chromiumExtensions ❌`; - } - console.info(` Function chromiumExtensions ✅`); - - const book = chromiumBookmarks([path], PlatformType.Darwin); - if (book.length !== 1) { - throw `Got length ${book.length} expected 1.......chromiumBookmarks ❌`; - } - - if (book[0]?.message != "Bookmark - Download the DuckDuckGo Browser for Mac") { - throw `Got message ${book[0]?.message} expected "Bookmark - Download the DuckDuckGo Browser for Mac".......chromiumBookmarks ❌`; - } - - if (book[0]?.date_added != "2025-11-02T22:47:41.000Z") { - throw `Got date ${book[0]?.date_added} expected "2025-11-02T22:47:41.000Z".......chromiumBookmarks ❌`; - } - - console.info(` Function chromiumBookmarks ✅`); - - const child = getBookmarkChildren(undefined, "", "", BookmarkType.Bar, BrowserType.EDGE); - if (child.length !== 0) { - throw `Got length ${child.length} expected 0.......getBookmarkChildren ❌`; - } - - console.info(` Function getBookmarkChildren ✅`); +import { BookmarkType, BrowserType, ChromiumBookmarks, ChromiumProfiles, Extension, } from "../../../types/applications/chromium"; +import { FileError } from "../../filesystem/errors"; +import { glob, readTextFile, stat } from "../../filesystem/files"; +import { PlatformType } from "../../system/systeminfo"; +import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; + +/** + * Get installed Chromium extensions + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @returns Array of parsed extensions + */ +export function chromiumExtensions(paths: ChromiumProfiles[], platform: PlatformType): Extension[] { + const hits: Extension[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/Extensions/*/*/manifest.json`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Extensions\\*\\*\\manifest.json`; + } + + const ext_paths = glob(full_path); + if (ext_paths instanceof FileError) { + continue; + } + + for (const ext_entry of ext_paths) { + const extension = readTextFile(ext_entry.full_path); + if (extension instanceof FileError) { + console.warn(`could not read file ${path}: ${extension}`); + continue; + } + + const data = JSON.parse(extension); + const ext_info: Extension = { + version: path.version, + message: `Extension: ${data["name"] ?? ""} | Version: ${data["version"] ?? ""}`, + datetime: "1970-01-01T00:00:00.000Z", + browser: path.browser, + timestamp_desc: "Extension Created", + artifact: "Browser Extension", + data_type: `applications:${path.browser.toLowerCase()}:extensions:entry`, + name: data["name"] ?? "", + author: "", + description: data["description"] ?? "", + evidence: ext_entry.full_path, + extension_version: data["version"] ?? "", + }; + const meta = stat(ext_entry.full_path); + if (!(meta instanceof FileError)) { + ext_info.datetime = meta.created; + } + + if (data["author"] === undefined) { + hits.push(ext_info); + continue; + } + + if (data["author"]["email"] !== undefined) { + ext_info.author = data["author"]["email"] ?? ""; + } + + hits.push(ext_info); + } + } + return hits; +} + + +/** + * Get Chromium Bookmarks + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @returns Array of `ChromiumBookmarks` + */ +export function chromiumBookmarks(paths: ChromiumProfiles[], platform: PlatformType): ChromiumBookmarks[] { + let hits: ChromiumBookmarks[] = []; + + for (const path of paths) { + let full_path = `${path.full_path}/*/Bookmarks`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Bookmarks`; + } + const book_paths = glob(full_path); + if (book_paths instanceof FileError) { + continue; + } + + for (const entry of book_paths) { + const results = readTextFile(entry.full_path); + if (results instanceof FileError) { + console.warn(`could not read file ${entry.full_path}: ${results}`); + continue; + } + + const book_json = JSON.parse(results); + const bar = book_json["roots"]["bookmark_bar"]["children"] as + | Record[] | undefined>[] + | undefined; + hits = hits.concat(getBookmarkChildren(bar, entry.full_path, path.version, BookmarkType.Bar, path.browser)); + + const other = book_json["roots"]["other"]["children"] as + | Record[] | undefined>[] + | undefined; + hits = hits.concat(getBookmarkChildren(other, entry.full_path, path.version, BookmarkType.Other, path.browser)); + + const synced = book_json["roots"]["other"]["synced"] as + | Record[] | undefined>[] + | undefined; + hits = hits.concat(getBookmarkChildren(synced, entry.full_path, path.version, BookmarkType.Sync, path.browser)); + } + } + return hits; +} + +/** + * Function to try to get children bookmark info + * @param book Parsed Bookmark children + * @returns Extract array of `ChromiumBookmarkChildren` + */ +function getBookmarkChildren( + book: + | Record[] | undefined>[] + | undefined, + path: string, + version: string, + bookmark_type: BookmarkType, + browser: BrowserType +): ChromiumBookmarks[] { + let books: ChromiumBookmarks[] = []; + if (typeof book === "undefined") { + return books; + } + const adjust_time = 1000000n; + for (const entry of book) { + if (typeof entry["children"] === "undefined") { + const book_entry: ChromiumBookmarks = { + date_added: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry["date_added"] as string) / adjust_time) + )), + date_last_used: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry["date_last_used"] as string) / adjust_time) + )), + guid: entry["guid"] as string, + id: Number(entry["id"] as string), + name: entry["name"] as string, + type: entry["type"] as string, + url: entry["url"] as string, + bookmark_type, + evidence: path, + version, + message: `Bookmark - ${entry["name"] as string}`, + datetime: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry["date_added"] as string) / adjust_time) + )), + timestamp_desc: "Bookmark Added", + artifact: "Browser Bookmark", + data_type: `applications:${browser.toLowerCase()}:bookmark:entry`, + browser, + }; + books.push(book_entry); + continue; + } + + books = books.concat( + getBookmarkChildren( + entry["children"] as + | Record[] | undefined>[] + | undefined, + path, + version, + bookmark_type, + browser + ), + ); + } + + return books; +} + +/** + * Function to test the Chromium JSON file parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Chromium JSON parsing + */ +export function testChromiumJsonFiles(): void { + const path: ChromiumProfiles = { + full_path: "../../test_data/edge", + version: "141", + browser: BrowserType.EDGE + }; + const ext = chromiumExtensions([path], PlatformType.Darwin); + if (ext.length !== 3) { + throw `Got length ${ext.length} expected 3.......chromiumExtensions ❌`; + } + if (ext[0]?.name != "__MSG_extName__") { + throw `Got name ${ext[0]?.name} expected "__MSG_extName__".......chromiumExtensions ❌`; + } + console.info(` Function chromiumExtensions ✅`); + + const book = chromiumBookmarks([path], PlatformType.Darwin); + if (book.length !== 1) { + throw `Got length ${book.length} expected 1.......chromiumBookmarks ❌`; + } + + if (book[0]?.message != "Bookmark - Download the DuckDuckGo Browser for Mac") { + throw `Got message ${book[0]?.message} expected "Bookmark - Download the DuckDuckGo Browser for Mac".......chromiumBookmarks ❌`; + } + + if (book[0]?.date_added != "2025-11-02T22:47:41.000Z") { + throw `Got date ${book[0]?.date_added} expected "2025-11-02T22:47:41.000Z".......chromiumBookmarks ❌`; + } + + console.info(` Function chromiumBookmarks ✅`); + + const child = getBookmarkChildren(undefined, "", "", BookmarkType.Bar, BrowserType.EDGE); + if (child.length !== 0) { + throw `Got length ${child.length} expected 0.......getBookmarkChildren ❌`; + } + + console.info(` Function getBookmarkChildren ✅`); } \ No newline at end of file diff --git a/src/applications/chromium/preferences.ts b/src/applications/chromium/preferences.ts index abe05e81..b642e97b 100644 --- a/src/applications/chromium/preferences.ts +++ b/src/applications/chromium/preferences.ts @@ -1,273 +1,273 @@ -import { glob, PlatformType, readTextFile } from "../../../mod"; -import { BrowserType, ChromiumProfiles, ExceptionCategory, Preferences } from "../../../types/applications/chromium"; -import { FileError } from "../../filesystem/errors"; -import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; - -/** - * Get Chromium Preferences - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @returns Array of Preferences - */ -export function chromiumPreferences(paths: ChromiumProfiles[], platform: PlatformType): Preferences[] { - let hits: Preferences[] = []; - - for (const path of paths) { - let full_path = `${path.full_path}/*/Preferences`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Preferences`; - } - const pref_paths = glob(full_path); - if (pref_paths instanceof FileError) { - continue; - } - - for (const entry of pref_paths) { - const pref = readTextFile(entry.full_path); - if (pref instanceof FileError) { - console.warn(`could not read file ${entry.full_path}: ${pref}`); - continue; - } - - const data = JSON.parse(pref); - const user_pref: Preferences = { - version: path.version, - message: "", - datetime: "", - browser: path.browser, - timestamp_desc: "Last Modified", - artifact: "User Preferences", - data_type: `applications:${path.browser.toLowerCase()}:preferences:entry`, - path: entry.full_path, - exception_category: ExceptionCategory.Zoom, - created_version: "", - profile_id: "", - preferences_created: "", - name: "", - url: "", - last_modified: "" - }; - - // Sites with zoomed preferences first - const zoom_values = zoomPrefs(data, user_pref); - const profile = data["profile"] as undefined | Profile; - if (profile === undefined) { - continue; - } - - const profile_info = profileInfo(profile, user_pref); - const sample = profile_info.at(0); - if (sample !== undefined) { - for (let i = 0; i < zoom_values.length; i++) { - const value = zoom_values[i]; - if (value === undefined) { - continue; - } - value.created_version = sample.created_version; - value.preferences_created = sample.preferences_created; - value.profile_id = sample.profile_id; - value.name = sample.name; - } - } - - hits = hits.concat(zoom_values); - hits = hits.concat(profile_info); - } - } - return hits; -} - -function zoomPrefs(data: Record>>>>, prefs: Preferences): Preferences[] { - const values: Preferences[] = []; - - const partitions = data["partition"]; - if (partitions === undefined) { - return values; - } - const levels = partitions["per_host_zoom_levels"]; - if (levels === undefined) { - return values; - } - - const urls = levels["x"]; - if (urls === undefined) { - return values; - } - - for (const key in urls) { - prefs.url = key; - const data = urls[key]; - if (data === undefined) { - continue; - } - prefs.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(data["last_modified"] ?? 0n) / 1000000n))); - prefs.datetime = prefs.last_modified; - prefs.message = key; - values.push(Object.assign({}, prefs)); - } - - return values; -} - -interface Profile { - "created_by_version": string; - "creation_time": string; - "edge_profile_id": string | undefined; - "enterprise_profile_guid": string | undefined; - name: string; - "content_settings": { - exceptions: { - "app_banner": Record, - "client_hints": Record, - "cookie_controls_metadata": Record, - "https_enforced": Record, - "media_engagement": Record, - "site_engagement": Record, - "ssl_cert_decisions": Record, - }; - } | undefined; -} - -function profileInfo(data: Profile, pref: Preferences): Preferences[] { - pref.created_version = data.created_by_version; - pref.name = data.name; - pref.preferences_created = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(data.creation_time) / 1000000n))); - pref.profile_id = data.edge_profile_id ?? data.enterprise_profile_guid ?? ""; - - const values: Preferences[] = []; - if (data.content_settings === undefined) { - return [Object.assign({}, pref)]; - } - const banner = data.content_settings.exceptions.app_banner; - for (const key in banner) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.AppBanner; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(banner[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - const hints = data.content_settings.exceptions.client_hints; - for (const key in hints) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.ClientHints; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(hints[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - const cookie = data.content_settings.exceptions.cookie_controls_metadata; - for (const key in cookie) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.CookieControls; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(cookie[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - const https = data.content_settings.exceptions.https_enforced; - for (const key in https) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.HttpsEnforced; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(https[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - const media = data.content_settings.exceptions.media_engagement; - for (const key in media) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.MediaEngagement; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(media[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - const site = data.content_settings.exceptions.site_engagement; - for (const key in site) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.SiteEngagement; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(site[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - const ssl = data.content_settings.exceptions.ssl_cert_decisions; - for (const key in ssl) { - pref.url = key; - pref.message = key; - pref.exception_category = ExceptionCategory.SslCert; - pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(ssl[key]?.last_modified ?? 0) / 1000000n))); - pref.datetime = pref.last_modified; - values.push(Object.assign({}, pref)); - } - - return values; -} - -/** - * Function to test the Chromium Preferences file parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Chromium Preferences parsing - */ -export function testChromiumPreferences(): void { - const path: ChromiumProfiles = { - full_path: "../../test_data/edge", - version: "141", - browser: BrowserType.EDGE - }; - - const pref = chromiumPreferences([path], PlatformType.Darwin); - if (pref.length !== 36 || pref[0] === undefined) { - throw `Got length ${pref.length} expected 36.......chromiumPreferences ❌`; - } - - if (pref[1]?.message !== "https://github.com:443,*") { - throw `Got message ${pref[1]?.message} expected "https://github.com:443,*".......chromiumPreferences ❌`; - } - - console.info(` Function chromiumPreferences ✅`); - - const zoom = zoomPrefs({}, pref[0]); - if (zoom.length !== 0) { - throw `Got length ${zoom.length} expected 0.......zoomPrefs ❌`; - } - - console.info(` Function zoomPrefs ✅`); - - const info = profileInfo({ - created_by_version: "", - creation_time: "", - edge_profile_id: undefined, - enterprise_profile_guid: undefined, - name: "", - content_settings: undefined - }, pref[0]); - - if (info.length !== 1) { - throw `Got length ${info.length} expected 1.......profileInfo ❌`; - } - - console.info(` Function profileInfo ✅`); +import { glob, PlatformType, readTextFile } from "../../../mod"; +import { BrowserType, ChromiumProfiles, ExceptionCategory, Preferences } from "../../../types/applications/chromium"; +import { FileError } from "../../filesystem/errors"; +import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; + +/** + * Get Chromium Preferences + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @returns Array of Preferences + */ +export function chromiumPreferences(paths: ChromiumProfiles[], platform: PlatformType): Preferences[] { + let hits: Preferences[] = []; + + for (const path of paths) { + let full_path = `${path.full_path}/*/Preferences`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Preferences`; + } + const pref_paths = glob(full_path); + if (pref_paths instanceof FileError) { + continue; + } + + for (const entry of pref_paths) { + const pref = readTextFile(entry.full_path); + if (pref instanceof FileError) { + console.warn(`could not read file ${entry.full_path}: ${pref}`); + continue; + } + + const data = JSON.parse(pref); + const user_pref: Preferences = { + version: path.version, + message: "", + datetime: "", + browser: path.browser, + timestamp_desc: "Last Modified", + artifact: "User Preferences", + data_type: `applications:${path.browser.toLowerCase()}:preferences:entry`, + evidence: entry.full_path, + exception_category: ExceptionCategory.Zoom, + created_version: "", + profile_id: "", + preferences_created: "", + name: "", + url: "", + last_modified: "" + }; + + // Sites with zoomed preferences first + const zoom_values = zoomPrefs(data, user_pref); + const profile = data["profile"] as undefined | Profile; + if (profile === undefined) { + continue; + } + + const profile_info = profileInfo(profile, user_pref); + const sample = profile_info.at(0); + if (sample !== undefined) { + for (let i = 0; i < zoom_values.length; i++) { + const value = zoom_values[i]; + if (value === undefined) { + continue; + } + value.created_version = sample.created_version; + value.preferences_created = sample.preferences_created; + value.profile_id = sample.profile_id; + value.name = sample.name; + } + } + + hits = hits.concat(zoom_values); + hits = hits.concat(profile_info); + } + } + return hits; +} + +function zoomPrefs(data: Record>>>>, prefs: Preferences): Preferences[] { + const values: Preferences[] = []; + + const partitions = data["partition"]; + if (partitions === undefined) { + return values; + } + const levels = partitions["per_host_zoom_levels"]; + if (levels === undefined) { + return values; + } + + const urls = levels["x"]; + if (urls === undefined) { + return values; + } + + for (const key in urls) { + prefs.url = key; + const data = urls[key]; + if (data === undefined) { + continue; + } + prefs.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(data["last_modified"] ?? 0n) / 1000000n))); + prefs.datetime = prefs.last_modified; + prefs.message = key; + values.push(Object.assign({}, prefs)); + } + + return values; +} + +interface Profile { + "created_by_version": string; + "creation_time": string; + "edge_profile_id": string | undefined; + "enterprise_profile_guid": string | undefined; + name: string; + "content_settings": { + exceptions: { + "app_banner": Record, + "client_hints": Record, + "cookie_controls_metadata": Record, + "https_enforced": Record, + "media_engagement": Record, + "site_engagement": Record, + "ssl_cert_decisions": Record, + }; + } | undefined; +} + +function profileInfo(data: Profile, pref: Preferences): Preferences[] { + pref.created_version = data.created_by_version; + pref.name = data.name; + pref.preferences_created = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(data.creation_time) / 1000000n))); + pref.profile_id = data.edge_profile_id ?? data.enterprise_profile_guid ?? ""; + + const values: Preferences[] = []; + if (data.content_settings === undefined) { + return [Object.assign({}, pref)]; + } + const banner = data.content_settings.exceptions.app_banner; + for (const key in banner) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.AppBanner; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(banner[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + const hints = data.content_settings.exceptions.client_hints; + for (const key in hints) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.ClientHints; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(hints[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + const cookie = data.content_settings.exceptions.cookie_controls_metadata; + for (const key in cookie) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.CookieControls; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(cookie[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + const https = data.content_settings.exceptions.https_enforced; + for (const key in https) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.HttpsEnforced; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(https[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + const media = data.content_settings.exceptions.media_engagement; + for (const key in media) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.MediaEngagement; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(media[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + const site = data.content_settings.exceptions.site_engagement; + for (const key in site) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.SiteEngagement; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(site[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + const ssl = data.content_settings.exceptions.ssl_cert_decisions; + for (const key in ssl) { + pref.url = key; + pref.message = key; + pref.exception_category = ExceptionCategory.SslCert; + pref.last_modified = unixEpochToISO(webkitToUnixEpoch(Number(BigInt(ssl[key]?.last_modified ?? 0) / 1000000n))); + pref.datetime = pref.last_modified; + values.push(Object.assign({}, pref)); + } + + return values; +} + +/** + * Function to test the Chromium Preferences file parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Chromium Preferences parsing + */ +export function testChromiumPreferences(): void { + const path: ChromiumProfiles = { + full_path: "../../test_data/edge", + version: "141", + browser: BrowserType.EDGE + }; + + const pref = chromiumPreferences([path], PlatformType.Darwin); + if (pref.length !== 36 || pref[0] === undefined) { + throw `Got length ${pref.length} expected 36.......chromiumPreferences ❌`; + } + + if (pref[1]?.message !== "https://github.com:443,*") { + throw `Got message ${pref[1]?.message} expected "https://github.com:443,*".......chromiumPreferences ❌`; + } + + console.info(` Function chromiumPreferences ✅`); + + const zoom = zoomPrefs({}, pref[0]); + if (zoom.length !== 0) { + throw `Got length ${zoom.length} expected 0.......zoomPrefs ❌`; + } + + console.info(` Function zoomPrefs ✅`); + + const info = profileInfo({ + created_by_version: "", + creation_time: "", + edge_profile_id: undefined, + enterprise_profile_guid: undefined, + name: "", + content_settings: undefined + }, pref[0]); + + if (info.length !== 1) { + throw `Got length ${info.length} expected 1.......profileInfo ❌`; + } + + console.info(` Function profileInfo ✅`); } \ No newline at end of file diff --git a/src/applications/chromium/sessions.ts b/src/applications/chromium/sessions.ts index 57905b49..798da36c 100644 --- a/src/applications/chromium/sessions.ts +++ b/src/applications/chromium/sessions.ts @@ -1,1398 +1,1415 @@ -import { BrowserType, ChromiumProfiles, ChromiumSession, SessionCommand, SessionTabCommand, SessionType } from "../../../types/applications/chromium"; -import { extractUtf16String, extractUtf8String } from "../../encoding/strings"; -import { FileError } from "../../filesystem/errors"; -import { glob, readFile } from "../../filesystem/files"; -import { NomError } from "../../nom/error"; -import { Endian, nomUnsignedEightBytes, nomUnsignedFourBytes, nomUnsignedOneBytes, nomUnsignedTwoBytes } from "../../nom/helpers"; -import { take } from "../../nom/parsers"; -import { PlatformType } from "../../system/systeminfo"; -import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; -import { ApplicationError } from "../errors"; - -/** - * Get Chromium sessions for all users - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @returns Array of `ChromiumSession` - */ -export function chromiumSessions(paths: ChromiumProfiles[], platform: PlatformType): ChromiumSession[] { - let values: ChromiumSession[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/Sessions/*`; - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Sessions\\*`; - } - - const sessions = glob(full_path); - if (sessions instanceof FileError) { - continue; - } - for (const entry of sessions) { - if (!entry.is_file) { - continue; - } - - let session_type = SessionType.Session; - if (entry.full_path.includes("Tabs_")) { - session_type = SessionType.Tab; - } - const status = parseSession(entry.full_path, session_type, path); - if (status instanceof ApplicationError) { - console.warn(`Failed to parse session for ${entry.full_path}: ${status}`); - continue; - } - values = values.concat(status); - } - } - return values; -} - -/** - * Function to parse a session file - * @param path Path to a session file - * @param session_type `SessionType` Can be `Session` or `Tab` - * @param profile `ChromiumProfiles` object - * @returns Array of `ChromiumSession` or `ApplicationError` - */ -function parseSession(path: string, session_type: SessionType, profile: ChromiumProfiles): ChromiumSession[] | ApplicationError { - const bytes = readFile(path); - if (bytes instanceof FileError) { - return new ApplicationError(`CHROMIUM`, `Failed to read session file ${path}: ${bytes}`); - } - - const header = getHeader(bytes); - if (header instanceof ApplicationError) { - return header; - } - - const min_size = 10; - const values: ChromiumSession[] = []; - const session_command_values: Record = {}; - const tab_command_values: Record = {}; - while (header.remaining.byteLength > min_size) { - const size = nomUnsignedTwoBytes(header.remaining, Endian.Le); - if (size instanceof NomError) { - break; - } - const payload = take(size.remaining, size.value); - if (payload instanceof NomError) { - break; - } - - header.remaining = payload.remaining as Uint8Array; - - const id = nomUnsignedOneBytes(payload.nommed as Uint8Array); - if (id instanceof NomError) { - break; - } - - if (session_type === SessionType.Session) { - const command = getSessionCommand(id.value); - parseSessionCommand(command, id.remaining, session_command_values); - } else { - const command = getTabCommand(id.value); - parseTabCommand(command, id.remaining, tab_command_values); - } - } - - for (const session_id in session_command_values) { - const ses: ChromiumSession = { - version: profile.version, - message: "", - datetime: session_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", - browser: profile.browser, - timestamp_desc: "Last Active", - artifact: "Browser Session", - data_type: `applications:${profile.browser.toLowerCase()}:session:entry`, - session_id, - last_active: session_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", - url: "", - title: "", - session_type: SessionType.Session, - path, - }; - for (const entry of session_command_values[session_id]?.commands ?? []) { - if (entry[SessionCommand.UpdateTabNavigation] === undefined) { - continue; - } - ses.url = (entry[SessionCommand.UpdateTabNavigation] as Record)["url"] ?? ":"; - ses.title = (entry[SessionCommand.UpdateTabNavigation] as Record)["title"] ?? ""; - ses.message = `Session: ${ses.url} | Page Title: ${ses.title}`; - values.push(Object.assign({}, ses)); - } - } - - for (const session_id in tab_command_values) { - const ses: ChromiumSession = { - version: profile.version, - message: "", - datetime: tab_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", - browser: profile.browser, - timestamp_desc: "Last Active", - artifact: "Browser Session", - data_type: `applications:${profile.browser.toLowerCase()}:tab:entry`, - session_id, - last_active: tab_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", - url: "", - title: "", - session_type: SessionType.Tab, - path, - }; - for (const entry of tab_command_values[session_id]?.commands ?? []) { - if (entry[SessionTabCommand.UpdateTabNavigation] === undefined) { - continue; - } - ses.url = (entry[SessionTabCommand.UpdateTabNavigation] as Record)["url"] ?? ":"; - ses.title = (entry[SessionTabCommand.UpdateTabNavigation] as Record)["title"] ?? ""; - ses.message = `Tab: ${ses.url} | Page Title: ${ses.title}`; - values.push(Object.assign({}, ses)); - } - } - - return values; -} - -interface Header { - signature: number; - version: number; - remaining: Uint8Array; -} - -/** - * Get initial structure of the Session file. Header is used to determine the version number - * @param bytes Session file bytes - * @returns `Header` information about the session file - */ -function getHeader(bytes: Uint8Array): Header | ApplicationError { - const sig = nomUnsignedFourBytes(bytes, Endian.Le); - if (sig instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get session sig: ${sig}`); - } - const version = nomUnsignedFourBytes(sig.remaining, Endian.Le); - if (version instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get session version: ${version}`); - } - - const head: Header = { - signature: sig.value, - version: version.value, - remaining: version.remaining, - }; - return head; -} - -/** - * Determine the command associated with Session. Each command stores different kinds of data. Some store URLs, timestamps, User agents, etc - * @param id Session ID number - * @returns Session Command value - */ -function getSessionCommand(id: number): SessionCommand { - switch (id) { - case 0: return SessionCommand.TabWindow; - case 1: return SessionCommand.WindowBounds; - case 2: return SessionCommand.TabIndexInWindow; - case 5: return SessionCommand.TabNavigationPathPrunedFromBack; - case 6: return SessionCommand.UpdateTabNavigation; - case 7: return SessionCommand.SelectedNavigationIndex; - case 8: return SessionCommand.SelectedTabInIndex; - case 9: return SessionCommand.WindowType; - case 10: return SessionCommand.WindowBounds2; - case 11: return SessionCommand.TabNavigationPathPrunedFromFront; - case 12: return SessionCommand.PinnedState; - case 13: return SessionCommand.ExtensionAppID; - case 14: return SessionCommand.WindowBounds3; - case 15: return SessionCommand.WindowAppName; - case 16: return SessionCommand.TabClosed; - case 17: return SessionCommand.WindowClosed; - case 18: return SessionCommand.TabUserAgentOverride; - case 19: return SessionCommand.SessionStorageAssociated; - case 20: return SessionCommand.ActiveWindow; - case 21: return SessionCommand.LastActiveTime; - case 22: return SessionCommand.WindowWorkspace; - case 23: return SessionCommand.WindowWorkspace2; - case 24: return SessionCommand.TabNavigationPathPruned; - case 25: return SessionCommand.TabGroup; - case 26: return SessionCommand.TabGroupMetadata; - case 27: return SessionCommand.TabGroupMetadata2; - case 28: return SessionCommand.TabGuid; - case 29: return SessionCommand.TabUserAgentOverride2; - case 30: return SessionCommand.TabData; - case 31: return SessionCommand.WindowUserTitle; - case 32: return SessionCommand.WindowVisibleOnAllWorkspaces; - case 33: return SessionCommand.AddTabExtraData; - case 34: return SessionCommand.AddWindowExtraData; - case 35: return SessionCommand.PlatformSessionId; - case 36: return SessionCommand.SplitTab; - case 37: return SessionCommand.SplitTabData; - // Chromium browsers can make there own custom commands - // https://github.com/cclgroupltd/ccl_chromium_reader/blob/552516720761397c4d482908b6b8b08130b313a1/ccl_chromium_reader/ccl_chromium_snss2.py#L95 - case 131: return SessionCommand.EdgeCommand; - case 132: return SessionCommand.EdgeCommand2; - case 44: return SessionCommand.EdgeCommand3; - case 50: return SessionCommand.EdgeCommand4; - case 255: return SessionCommand.CommandStorageBackend; - default: { - console.info(`Unknown session command ${id}`); - return SessionCommand.Unknown; - } - } -} - -/** - * Determine the command associated with Tab. Each command stores different kinds of data. Some store URLs, timestamps, User agents, etc - * @param id Tab ID number - * @returns Tab Command value - */ -function getTabCommand(id: number): SessionTabCommand { - switch (id) { - case 1: return SessionTabCommand.UpdateTabNavigation; - case 2: return SessionTabCommand.RestoredEntry; - case 3: return SessionTabCommand.WindowDeprecated; - case 4: return SessionTabCommand.SelectedNavigtionInTab; - case 5: return SessionTabCommand.PinnedState; - case 6: return SessionTabCommand.ExtensionAppID; - case 7: return SessionTabCommand.WindowAppName; - case 8: return SessionTabCommand.TabUserAgentOverride; - case 9: return SessionTabCommand.Window; - case 10: return SessionTabCommand.TabGroupData; - case 11: return SessionTabCommand.TabUserAgentOverride2; - case 12: return SessionTabCommand.WindowUserTitle; - case 13: return SessionTabCommand.CreateGroup; - case 14: return SessionTabCommand.AddTabExtraData; - case 255: return SessionTabCommand.CommandStorageBackend; - default: { - console.info(`Unknown tab command ${id}`); - return SessionTabCommand.Unknown; - } - } -} - -interface CommandValues { - commands: Record[]; - last_active: string; -} - -/** - * Function to parse the data associated with each SessionCommand. Many commands have similar strucutres - * @param command `SessionCommand` identifier - * @param bytes Bytes associated with the command - * @param command_values Values associated with the SessionCommand - */ -function parseSessionCommand(command: SessionCommand, bytes: Uint8Array, command_values: Record) { - switch (command) { - case SessionCommand.WindowType: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.WindowAppName: { - const window = parseWindowAppName(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.WindowUserTitle: { - const window = parseWindowAppName(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.WindowWorkspace2: { - const window = parseWindowAppName(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.WindowVisibleOnAllWorkspaces: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.PinnedState: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.TabIndexInWindow: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.TabGroup: { - const window = parseTabGroup(bytes); - if (!(window instanceof ApplicationError) && window.length !== 0 && window[0] !== undefined) { - if (command_values[window[0].session_id] === undefined) { - command_values[window[0].session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - - } else { - command_values[window[0].session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.TabWindow: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.SessionStorageAssociated: { - const window = parseSessionStorageAssociated(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.SelectedTabInIndex: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.LastActiveTime: { - const window = parseLastActive(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: window.last_active - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - const last = command_values[window.session_id]; - if (last !== undefined) { - last.last_active = window.last_active; - } - } - } - break; - } - case SessionCommand.SelectedNavigationIndex: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.UpdateTabNavigation: { - const window = parseUpdateTabNavigation(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.ActiveWindow: { - const window = parseSessionId(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window] === undefined) { - command_values[window] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.WindowBounds3: { - const window = parseWindowsBounds(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.CommandStorageBackend: { - // Contains nothing - break; - } case SessionCommand.AddWindowExtraData: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.TabUserAgentOverride2: { - const window = parseWindowAppName(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.TabClosed: { - const window = parseLastActive(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.EdgeCommand: { - const window = parseLastActive(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.EdgeCommand2: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.EdgeCommand3: { - const window = parseWindowAppName(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.EdgeCommand4: { - const window = parseWindowAppName(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - - break; - } - case SessionCommand.TabNavigationPathPruned: { - const window = parseWindowType(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionCommand.ExtensionAppID: { - const window = parseSessionStorageAssociated(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - default: { - console.info(`Unsupported session command: ${command}`); - } - } -} - -/** - * Function to parse the data associated with each TabCommand. Many commands have similar strucutres - * @param command `SessionTabCommand` identifier - * @param bytes Bytes associated with the command - * @param command_values Values associated with the SessionCommand - */ -function parseTabCommand(command: SessionTabCommand, bytes: Uint8Array, command_values: Record) { - switch (command) { - case SessionTabCommand.SelectedNavigtionInTab: { - const window = parseLastActive(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: window.last_active - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - const last = command_values[window.session_id]; - if (last !== undefined) { - last.last_active = window.last_active; - } - } - } - break; - } - case SessionTabCommand.UpdateTabNavigation: { - const window = parseUpdateTabNavigation(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionTabCommand.CommandStorageBackend: { - // Contains nothing - break; - } - case SessionTabCommand.Window: { - const window = parseWindow(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - case SessionTabCommand.TabUserAgentOverride2: { - // Contains array of user agents - break; - } - case SessionTabCommand.ExtensionAppID: { - const window = parseSessionStorageAssociated(bytes); - if (!(window instanceof ApplicationError)) { - if (command_values[window.session_id] === undefined) { - command_values[window.session_id] = { - commands: [({ - [command]: window, - })], - last_active: "" - }; - } else { - command_values[window.session_id]?.commands.push({ - [command]: window, - }); - } - } - break; - } - default: { - console.info(`Unsupported tab command: ${command}`); - } - } -} - -interface WindowType { - session_id: number; - index: number; -} - -/** - * Parse the WindowType info - * @param bytes Bytes associated with the WindowType. Should always be 8 bytes - * @returns `WindowType` object or `ApplicationError` - */ -function parseWindowType(bytes: Uint8Array): WindowType | ApplicationError { - const session = nomUnsignedFourBytes(bytes, Endian.Le); - if (session instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to window type session id: ${session}`); - } - - const index = nomUnsignedFourBytes(session.remaining, Endian.Le); - if (index instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to window type index: ${index}`); - } - - const window: WindowType = { - session_id: session.value, - index: index.value, - }; - - return window; -} - -/** - * Parse the WindowAppName info - * @param bytes Bytes associated with the WindowType. Should always be 12 bytes - * @returns `WindowType` object or `ApplicationError` - */ -function parseWindowAppName(bytes: Uint8Array): WindowType | ApplicationError { - const size = nomUnsignedFourBytes(bytes, Endian.Le); - if (size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to window app name size: ${size}`); - } - // Size is 8 bytes? - // Same values as WindowType? - - return parseWindowType(size.remaining); -} - -/** - * Parse the TabGroup info - * @param bytes Bytes associated with the TabGroup - * @returns Array of `WindowType` or `Application Error` - */ -function parseTabGroup(bytes: Uint8Array): WindowType[] | ApplicationError { - const limit = 4; - let count = 0; - const group: WindowType[] = []; - let remaining = bytes; - while (count < limit) { - const session = nomUnsignedFourBytes(remaining, Endian.Le); - if (session instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to window type session id: ${session}`); - } - - const index = nomUnsignedFourBytes(session.remaining, Endian.Le); - if (index instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to window type index: ${index}`); - } - remaining = index.remaining; - - const window: WindowType = { - session_id: session.value, - index: index.value, - }; - group.push(window); - count += 1; - } - - return group; -} - -interface SessionStorageAssociated { - session_id: number; - value: string; -} - -/** - * Parse the session storage associated info. Usually contains a string - * @param bytes Bytes assocaited with SessionStorageAssociated - * @returns `SessionStorageAssociated` object or `ApplicationError` object - */ -function parseSessionStorageAssociated(bytes: Uint8Array): SessionStorageAssociated | ApplicationError { - let size = nomUnsignedFourBytes(bytes, Endian.Le); - if (size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated size: ${size}`); - } - const session_id = nomUnsignedFourBytes(size.remaining, Endian.Le); - if (session_id instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated id: ${session_id}`); - } - - size = nomUnsignedFourBytes(session_id.remaining, Endian.Le); - if (size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated string size: ${size}`); - } - - const string_data = take(size.remaining, size.value); - if (string_data instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated string: ${string_data}`); - } - - const value = extractUtf8String(string_data.nommed as Uint8Array); - const info: SessionStorageAssociated = { - session_id: session_id.value, - value, - }; - return info; -} - -interface LastActive { - session_id: number; - index: number; - last_active: string; -} - -/** - * Parse the last active timestamp - * @param bytes Bytes assocaited with LastActive - * @returns `LastActive` object or `ApplicationError` object - */ -function parseLastActive(bytes: Uint8Array): LastActive | ApplicationError { - const session = nomUnsignedFourBytes(bytes, Endian.Le); - if (session instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to last active session id: ${session}`); - } - - const index = nomUnsignedFourBytes(session.remaining, Endian.Le); - if (index instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get last active index: ${index}`); - } - - const timestamp = nomUnsignedEightBytes(index.remaining, Endian.Le); - if (timestamp instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get last active timestamp: ${timestamp}`); - } - - const last: LastActive = { - session_id: session.value, - index: index.value, - last_active: unixEpochToISO(webkitToUnixEpoch(Number(timestamp.value / 1000000n))), - }; - - return last; -} - -/** - * Get the session ID value - * @param bytes Bytes associated with the session ID - * @returns Session ID or `ApplicationError - */ -function parseSessionId(bytes: Uint8Array): number | ApplicationError { - const session = nomUnsignedFourBytes(bytes, Endian.Le); - if (session instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to last active session id: ${session}`); - } - - return session.value; -} - -interface TabNavigation { - session_id: number; - unknown: number; - unknown2: number; - unknown3: number; - unknown4: number; - unknown5: number; -} - -/** - * Parse the windows bound info. - * @param bytes Bytes associted with TabNavigation - * @returns `TabNavigation` object or `ApplicationError` - */ -function parseWindowsBounds(bytes: Uint8Array): TabNavigation | ApplicationError { - const session = nomUnsignedFourBytes(bytes, Endian.Le); - if (session instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get windows bound session id: ${session}`); - } - - const unknown = nomUnsignedFourBytes(session.remaining, Endian.Le); - if (unknown instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown: ${unknown}`); - } - - const unknown2 = nomUnsignedFourBytes(unknown.remaining, Endian.Le); - if (unknown2 instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown2: ${unknown2}`); - } - - const unknown3 = nomUnsignedFourBytes(unknown2.remaining, Endian.Le); - if (unknown3 instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown3: ${unknown3}`); - } - - const unknown4 = nomUnsignedFourBytes(unknown3.remaining, Endian.Le); - if (unknown4 instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown4: ${unknown4}`); - } - - const unknown5 = nomUnsignedFourBytes(unknown4.remaining, Endian.Le); - if (unknown5 instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown5: ${unknown5}`); - } - - const tab: TabNavigation = { - session_id: session.value, - unknown: unknown.value, - unknown2: unknown2.value, - unknown3: unknown3.value, - unknown4: unknown4.value, - unknown5: unknown5.value - }; - - return tab; -} - -interface TabInfo { - session_id: number; - index: number; - url: string; - title: string; -} - -/** - * Parse the Tab Navigation info. Usually contains page info like URL and page title and alot more - * @param bytes Bytes associated with TabInfo - * @returns `TabInfo` object or `ApplicationError` - */ -function parseUpdateTabNavigation(bytes: Uint8Array): TabInfo | ApplicationError { - const size = nomUnsignedFourBytes(bytes, Endian.Le); - if (size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation size: ${size}`); - } - - const session_id = nomUnsignedFourBytes(size.remaining, Endian.Le); - if (session_id instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation session id: ${session_id}`); - } - const index = nomUnsignedFourBytes(session_id.remaining, Endian.Le); - if (index instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation index: ${index}`); - } - - // Size does NOT include end of string character - const url_size = nomUnsignedFourBytes(index.remaining, Endian.Le); - if (url_size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url size: ${url_size}`); - } - - // Should align to 4 bytes? - let adjust = url_size.value % 4; - if (adjust !== 0) { - adjust = 4 - adjust; - } - - const url_data = take(url_size.remaining, url_size.value + adjust); - if (url_data instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url: ${url_data}`); - } - - const url = extractUtf8String(url_data.nommed as Uint8Array); - - const title_size = nomUnsignedFourBytes(url_data.remaining as Uint8Array, Endian.Le); - if (title_size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url title size: ${title_size}`); - } - - const adjust_size = 2; - // Align 4 bytes - adjust = (title_size.value * adjust_size) % 4; - if (adjust !== 0) { - adjust = 4 - adjust; - } - - let title = ""; - let remaining = title_size.remaining; - if (title_size.value !== 0) { - // Title data is UTF16 o.O and does NOT include end of string character - const title_data = take(title_size.remaining, (title_size.value + adjust)); - if (title_data instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url title: ${title_data}`); - } - remaining = title_data.remaining as Uint8Array; - - const title_string = extractUtf16String(title_data.nommed as Uint8Array); - title = title_string; - } - - // A lot more left to parse! - // https://digitalinvestigation.wordpress.com/2012/09/03/chrome-session-and-tabs-files-and-the-puzzle-of-the-pickle/ - - const state_size = nomUnsignedFourBytes(remaining, Endian.Le); - if (state_size instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url state size: ${state_size}`); - } - // A very complex format. Currently not parsing. *Might* contain info like URL referrer, User agent, additional timestamps - // const state_data = take(state_size.remaining, state_size.value); - // if (state_data instanceof NomError) { - // return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url state data: ${state_data}`); - // } - - const info: TabInfo = { - session_id: session_id.value, - index: index.value, - url, - title, - }; - - return info; -} - -interface Window { - session_id: number; - index: number; - unknown: number; - window_timestamp: string; -} - -/** - * Parse the Window session command - * @param bytes Bytes associated with the Window command - * @returns `Window` object or `ApplicationError` - */ -function parseWindow(bytes: Uint8Array): Window | ApplicationError { - const session = nomUnsignedFourBytes(bytes, Endian.Le); - if (session instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get window session id: ${session}`); - } - - const index = nomUnsignedFourBytes(session.remaining, Endian.Le); - if (index instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get window index: ${index}`); - } - - const unknown = nomUnsignedFourBytes(index.remaining, Endian.Le); - if (unknown instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get window unknown: ${unknown}`); - } - - const timestamp = nomUnsignedEightBytes(unknown.remaining, Endian.Le); - if (timestamp instanceof NomError) { - return new ApplicationError(`CHROMIUM`, `Failed to get window timestamp: ${timestamp}`); - } - - // A lot more left to parse! - // https://digitalinvestigation.wordpress.com/2012/09/03/chrome-session-and-tabs-files-and-the-puzzle-of-the-pickle/ - - - const win: Window = { - session_id: session.value, - index: index.value, - unknown: unknown.value, - window_timestamp: unixEpochToISO(webkitToUnixEpoch(Number(timestamp.value / 1000000n))), - }; - - return win; -} - -/** - * Function to test the Chromium Sessions parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Chromium Sessions parsing - */ -export function testChromiumSessions(): void { - const path: ChromiumProfiles = { - full_path: "../../test_data/edge", - version: "141", - browser: BrowserType.EDGE - }; - - const sess = chromiumSessions([path], PlatformType.Darwin); - if (sess.length !== 115) { - throw `Got length ${sess.length} expected 115.......chromiumSessions ❌`; - } - - if (sess[12]?.message !== "Session: https://www.washingtonpost.com/politics/2025/11/02/nuclear-testing-trump-energy-secretary/ | Page Title: Trump energy secretary says no nuclear b") { - throw `Got message "${sess[12]?.message}" expected "Session: https://www.washingtonpost.com/politics/2025/11/02/nuclear-testing-trump-energy-secretary/ | Page Title: Trump energy secretary says no nuclear b".......chromiumSessions ❌`; - } - - - if (sess[0]?.message != "Session: edge://newtab/ | Page Title: New T") { - throw `Got message ${sess[0]?.message} expected "Session: edge://newtab/ | Page Title: New T".......chromiumSessions ❌`; - } - - console.info(` Function chromiumSessions ✅`); - - const sess_file = "../../test_data/edge/v141/Sessions/Session_13406596486318013"; - const results = parseSession(sess_file, SessionType.Session, path); - if (results instanceof ApplicationError) { - throw results.message; - } - - if (results.length !== 58) { - throw `Got length ${results.length} expected 58.......parseSession ❌`; - } - - if (results[7]?.message != "Session: https://www.washingtonpost.com/ | Page Title: The Washington Post - Breaking news and latest headlines") { - throw `Got message ${results[7]?.message} expected "Session: https://www.washingtonpost.com/ | Page Title: The Washington Post - Breaking news and latest headlines".......parseSession ❌`; - } - - console.info(` Function parseSession ✅`); - - const header = getHeader(new Uint8Array([0, 0, 0, 0, 1, 0, 0, 0])); - if (header instanceof ApplicationError) { - throw header.message; - } - - if (header.version !== 1) { - throw `Got length ${header.version} expected 1.......getHeader ❌`; - } - - console.info(` Function getHeader ✅`); - - const test = [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 131, 132, 44, 50, 255]; - for (const entry of test) { - const value = getSessionCommand(entry); - if (value === SessionCommand.Unknown) { - throw `Got Session command Unknown for ${entry}.......getSessionCommand ❌` - } - } - - console.info(` Function getSessionCommand ✅`); - - for (const entry of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255]) { - const value = getTabCommand(entry); - if (value === SessionTabCommand.Unknown) { - throw `Got Tab command Unknown for ${entry}.......getTabCommand ❌` - } - } - - console.info(` Function getTabCommand ✅`); - - const comm: Record = {}; - parseSessionCommand(SessionCommand.TabWindow, new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0]), comm); - if (!JSON.stringify(comm).includes("TabWindow")) { - throw `Got Session command ${JSON.stringify(comm)} wanted "TabWindow".......parseSessionCommand ❌` - } - - console.info(` Function parseSessionCommand ✅`); - - const tab: Record = {}; - parseTabCommand(SessionTabCommand.SelectedNavigtionInTab, new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0]), tab); - if (!JSON.stringify(tab).includes("SelectedNavigationInTab")) { - throw `Got Session command ${JSON.stringify(tab)} wanted "SelectedNavigationInTab".......parseTabCommand ❌` - } - - console.info(` Function parseTabCommand ✅`); - - let info = parseWindowType(new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0])); - if (info instanceof ApplicationError) { - throw info.message; - } - - if (info.index !== 2) { - throw `Got index ${info.index} expected 2.......parseWindowType ❌`; - } - - console.info(` Function parseWindowType ✅`); - - info = parseWindowAppName(new Uint8Array([8, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0])); - if (info instanceof ApplicationError) { - throw info.message; - } - - if (info.index !== 2) { - throw `Got index ${info.index} expected 2.......parseWindowAppName ❌`; - } - - console.info(` Function parseWindowAppName ✅`); - - const tab_group = parseTabGroup(new Uint8Array([8, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0])); - if (tab_group instanceof ApplicationError) { - throw tab_group.message; - } - - if (tab_group.length !== 4) { - throw `Got index ${tab_group.length} expected 4.......parseTabGroup ❌`; - } - - console.info(` Function parseTabGroup ✅`); - - const url_bytes = parseSessionStorageAssociated(new Uint8Array([44, 0, 0, 0, 208, 120, 213, 109, 36, 0, 0, 0, 97, 102, 100, 99, 50, 50, 54, 100, 95, 99, 55, 57, 51, 95, 52, 97, 51, 52, 95, 97, 49, 51, 50, 95, 50, 100, 102, 100, 99, 99, 50, 53, 56, 97, 57, 50])); - if (url_bytes instanceof ApplicationError) { - throw url_bytes.message; - } - - if (url_bytes.value !== "afdc226d_c793_4a34_a132_2dfdcc258a92") { - throw `Got value ${url_bytes.value} expected "afdc226d_c793_4a34_a132_2dfdcc258a92".......parseSessionStorageAssociated ❌`; - } - console.info(` Function parseSessionStorageAssociated ✅`); - - const last_active = parseLastActive(new Uint8Array([81, 121, 213, 109, 0, 0, 0, 0, 59, 250, 123, 137, 58, 161, 47, 0])); - if (last_active instanceof ApplicationError) { - throw last_active.message; - } - - if (last_active.last_active !== "2025-11-02T22:38:12.000Z") { - throw `Got time ${last_active.last_active} expected "2025-11-02T22:38:12.000Z".......parseLastActive ❌`; - } - console.info(` Function parseLastActive ✅`); - - const session_id = parseSessionId(new Uint8Array([1, 0, 0, 0])); - if (session_id instanceof ApplicationError) { - throw session_id.message; - } - - if (session_id !== 1) { - throw `Got ID ${session_id} expected "1".......parseSessionId ❌`; - } - console.info(` Function parseSessionId ✅`); - - const navigate = parseWindowsBounds(new Uint8Array([11, 121, 213, 109, 22, 0, 0, 0, 22, 0, 0, 0, 0, 15, 0, 0, 56, 4, 0, 0, 1, 0, 0, 0])); - if (navigate instanceof ApplicationError) { - throw navigate.message; - } - - if (navigate.session_id !== 1842706699) { - throw `Got ID ${navigate.session_id} expected "1842706699".......parseWindowsBounds ❌`; - } - console.info(` Function parseWindowsBounds ✅`); - - const bytes = readFile("../../test_data/edge/v141/raw/tab_url.raw"); - if (bytes instanceof FileError) { - throw bytes.message; - } - - const tab_url = parseUpdateTabNavigation(bytes); - if (tab_url instanceof ApplicationError) { - throw tab_url.message; - } - - if (tab_url.url != "edge://newtab/") { - throw `Got URL ${tab_url.url} expected "edge://newtab/".......parseUpdateTabNavigation ❌`; - } - console.info(` Function parseUpdateTabNavigation ✅`); - - const win = parseWindow(new Uint8Array([11, 121, 213, 109, 22, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); - if (win instanceof ApplicationError) { - throw win.message; - } - - if (win.session_id !== 1842706699) { - throw `Got ID ${win.session_id} expected "1842706699".......parseWindow ❌`; - } - console.info(` Function parseWindow ✅`); +import { BrowserType, ChromiumProfiles, ChromiumSession, SessionCommand, SessionTabCommand, SessionType } from "../../../types/applications/chromium"; +import { extractUtf16String, extractUtf8String } from "../../encoding/strings"; +import { FileError } from "../../filesystem/errors"; +import { glob, readFile } from "../../filesystem/files"; +import { NomError } from "../../nom/error"; +import { Endian, nomUnsignedEightBytes, nomUnsignedFourBytes, nomUnsignedOneBytes, nomUnsignedTwoBytes } from "../../nom/helpers"; +import { take } from "../../nom/parsers"; +import { PlatformType } from "../../system/systeminfo"; +import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; +import { WindowsError } from "../../windows/errors"; +import { readRawFile } from "../../windows/ntfs"; +import { ApplicationError } from "../errors"; + +/** + * Get Chromium sessions for all users + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @returns Array of `ChromiumSession` + */ +export function chromiumSessions(paths: ChromiumProfiles[], platform: PlatformType): ChromiumSession[] { + let values: ChromiumSession[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/Sessions/*`; + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Sessions\\*`; + } + + const sessions = glob(full_path); + if (sessions instanceof FileError) { + continue; + } + for (const entry of sessions) { + if (!entry.is_file) { + continue; + } + + let session_type = SessionType.Session; + if (entry.full_path.includes("Tabs_")) { + session_type = SessionType.Tab; + } + const status = parseSession(entry.full_path, session_type, path, platform); + if (status instanceof ApplicationError) { + console.warn(`Failed to parse session for ${entry.full_path}: ${status}`); + continue; + } + values = values.concat(status); + } + } + return values; +} + +/** + * Function to parse a session file + * @param path Path to a session file + * @param session_type `SessionType` Can be `Session` or `Tab` + * @param profile `ChromiumProfiles` object + * @returns Array of `ChromiumSession` or `ApplicationError` + */ +function parseSession(path: string, session_type: SessionType, profile: ChromiumProfiles, platform: PlatformType): ChromiumSession[] | ApplicationError { + let bytes; + + // On Windows if the browser is opened the Session files may be locked + // We will use raw disk access to open them + if (platform === PlatformType.Windows) { + bytes = readRawFile(path); + if (bytes instanceof WindowsError) { + return new ApplicationError(`CHROMIUM`, `Failed to read session file via raw disk ${path}: ${bytes}`); + } + } else { + bytes = readFile(path); + if (bytes instanceof FileError) { + return new ApplicationError(`CHROMIUM`, `Failed to read session file ${path}: ${bytes}`); + + } + } + + const header = getHeader(bytes); + if (header instanceof ApplicationError) { + return header; + } + + const min_size = 10; + const values: ChromiumSession[] = []; + const session_command_values: Record = {}; + const tab_command_values: Record = {}; + while (header.remaining.byteLength > min_size) { + const size = nomUnsignedTwoBytes(header.remaining, Endian.Le); + if (size instanceof NomError) { + break; + } + const payload = take(size.remaining, size.value); + if (payload instanceof NomError) { + break; + } + + header.remaining = payload.remaining as Uint8Array; + + const id = nomUnsignedOneBytes(payload.nommed as Uint8Array); + if (id instanceof NomError) { + break; + } + + if (session_type === SessionType.Session) { + const command = getSessionCommand(id.value); + parseSessionCommand(command, id.remaining, session_command_values); + } else { + const command = getTabCommand(id.value); + parseTabCommand(command, id.remaining, tab_command_values); + } + } + + for (const session_id in session_command_values) { + const ses: ChromiumSession = { + version: profile.version, + message: "", + datetime: session_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", + browser: profile.browser, + timestamp_desc: "Last Active", + artifact: "Browser Session", + data_type: `applications:${profile.browser.toLowerCase()}:session:entry`, + session_id, + last_active: session_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", + url: "", + title: "", + session_type: SessionType.Session, + evidence: path, + }; + for (const entry of session_command_values[session_id]?.commands ?? []) { + if (entry[SessionCommand.UpdateTabNavigation] === undefined) { + continue; + } + ses.url = (entry[SessionCommand.UpdateTabNavigation] as Record)["url"] ?? ":"; + ses.title = (entry[SessionCommand.UpdateTabNavigation] as Record)["title"] ?? ""; + ses.message = `Session: ${ses.url} | Page Title: ${ses.title}`; + values.push(Object.assign({}, ses)); + } + } + + for (const session_id in tab_command_values) { + const ses: ChromiumSession = { + version: profile.version, + message: "", + datetime: tab_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", + browser: profile.browser, + timestamp_desc: "Last Active", + artifact: "Browser Session", + data_type: `applications:${profile.browser.toLowerCase()}:tab:entry`, + session_id, + last_active: tab_command_values[session_id]?.last_active ?? "1970-01-01T00:00:00.000Z", + url: "", + title: "", + session_type: SessionType.Tab, + evidence: path, + }; + for (const entry of tab_command_values[session_id]?.commands ?? []) { + if (entry[SessionTabCommand.UpdateTabNavigation] === undefined) { + continue; + } + ses.url = (entry[SessionTabCommand.UpdateTabNavigation] as Record)["url"] ?? ":"; + ses.title = (entry[SessionTabCommand.UpdateTabNavigation] as Record)["title"] ?? ""; + ses.message = `Tab: ${ses.url} | Page Title: ${ses.title}`; + values.push(Object.assign({}, ses)); + } + } + + return values; +} + +interface Header { + signature: number; + version: number; + remaining: Uint8Array; +} + +/** + * Get initial structure of the Session file. Header is used to determine the version number + * @param bytes Session file bytes + * @returns `Header` information about the session file + */ +function getHeader(bytes: Uint8Array): Header | ApplicationError { + const sig = nomUnsignedFourBytes(bytes, Endian.Le); + if (sig instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get session sig: ${sig}`); + } + const version = nomUnsignedFourBytes(sig.remaining, Endian.Le); + if (version instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get session version: ${version}`); + } + + const head: Header = { + signature: sig.value, + version: version.value, + remaining: version.remaining, + }; + return head; +} + +/** + * Determine the command associated with Session. Each command stores different kinds of data. Some store URLs, timestamps, User agents, etc + * @param id Session ID number + * @returns Session Command value + */ +function getSessionCommand(id: number): SessionCommand { + switch (id) { + case 0: return SessionCommand.TabWindow; + case 1: return SessionCommand.WindowBounds; + case 2: return SessionCommand.TabIndexInWindow; + case 5: return SessionCommand.TabNavigationPathPrunedFromBack; + case 6: return SessionCommand.UpdateTabNavigation; + case 7: return SessionCommand.SelectedNavigationIndex; + case 8: return SessionCommand.SelectedTabInIndex; + case 9: return SessionCommand.WindowType; + case 10: return SessionCommand.WindowBounds2; + case 11: return SessionCommand.TabNavigationPathPrunedFromFront; + case 12: return SessionCommand.PinnedState; + case 13: return SessionCommand.ExtensionAppID; + case 14: return SessionCommand.WindowBounds3; + case 15: return SessionCommand.WindowAppName; + case 16: return SessionCommand.TabClosed; + case 17: return SessionCommand.WindowClosed; + case 18: return SessionCommand.TabUserAgentOverride; + case 19: return SessionCommand.SessionStorageAssociated; + case 20: return SessionCommand.ActiveWindow; + case 21: return SessionCommand.LastActiveTime; + case 22: return SessionCommand.WindowWorkspace; + case 23: return SessionCommand.WindowWorkspace2; + case 24: return SessionCommand.TabNavigationPathPruned; + case 25: return SessionCommand.TabGroup; + case 26: return SessionCommand.TabGroupMetadata; + case 27: return SessionCommand.TabGroupMetadata2; + case 28: return SessionCommand.TabGuid; + case 29: return SessionCommand.TabUserAgentOverride2; + case 30: return SessionCommand.TabData; + case 31: return SessionCommand.WindowUserTitle; + case 32: return SessionCommand.WindowVisibleOnAllWorkspaces; + case 33: return SessionCommand.AddTabExtraData; + case 34: return SessionCommand.AddWindowExtraData; + case 35: return SessionCommand.PlatformSessionId; + case 36: return SessionCommand.SplitTab; + case 37: return SessionCommand.SplitTabData; + // Chromium browsers can make there own custom commands + // https://github.com/cclgroupltd/ccl_chromium_reader/blob/552516720761397c4d482908b6b8b08130b313a1/ccl_chromium_reader/ccl_chromium_snss2.py#L95 + case 131: return SessionCommand.EdgeCommand; + case 132: return SessionCommand.EdgeCommand2; + case 44: return SessionCommand.EdgeCommand3; + case 50: return SessionCommand.EdgeCommand4; + case 255: return SessionCommand.CommandStorageBackend; + default: { + console.info(`Unknown session command ${id}`); + return SessionCommand.Unknown; + } + } +} + +/** + * Determine the command associated with Tab. Each command stores different kinds of data. Some store URLs, timestamps, User agents, etc + * @param id Tab ID number + * @returns Tab Command value + */ +function getTabCommand(id: number): SessionTabCommand { + switch (id) { + case 1: return SessionTabCommand.UpdateTabNavigation; + case 2: return SessionTabCommand.RestoredEntry; + case 3: return SessionTabCommand.WindowDeprecated; + case 4: return SessionTabCommand.SelectedNavigtionInTab; + case 5: return SessionTabCommand.PinnedState; + case 6: return SessionTabCommand.ExtensionAppID; + case 7: return SessionTabCommand.WindowAppName; + case 8: return SessionTabCommand.TabUserAgentOverride; + case 9: return SessionTabCommand.Window; + case 10: return SessionTabCommand.TabGroupData; + case 11: return SessionTabCommand.TabUserAgentOverride2; + case 12: return SessionTabCommand.WindowUserTitle; + case 13: return SessionTabCommand.CreateGroup; + case 14: return SessionTabCommand.AddTabExtraData; + case 255: return SessionTabCommand.CommandStorageBackend; + default: { + console.info(`Unknown tab command ${id}`); + return SessionTabCommand.Unknown; + } + } +} + +interface CommandValues { + commands: Record[]; + last_active: string; +} + +/** + * Function to parse the data associated with each SessionCommand. Many commands have similar strucutres + * @param command `SessionCommand` identifier + * @param bytes Bytes associated with the command + * @param command_values Values associated with the SessionCommand + */ +function parseSessionCommand(command: SessionCommand, bytes: Uint8Array, command_values: Record) { + switch (command) { + case SessionCommand.WindowType: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.WindowAppName: { + const window = parseWindowAppName(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.WindowUserTitle: { + const window = parseWindowAppName(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.WindowWorkspace2: { + const window = parseWindowAppName(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.WindowVisibleOnAllWorkspaces: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.PinnedState: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.TabIndexInWindow: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.TabGroup: { + const window = parseTabGroup(bytes); + if (!(window instanceof ApplicationError) && window.length !== 0 && window[0] !== undefined) { + if (command_values[window[0].session_id] === undefined) { + command_values[window[0].session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + + } else { + command_values[window[0].session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.TabWindow: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.SessionStorageAssociated: { + const window = parseSessionStorageAssociated(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.SelectedTabInIndex: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.LastActiveTime: { + const window = parseLastActive(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: window.last_active + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + const last = command_values[window.session_id]; + if (last !== undefined) { + last.last_active = window.last_active; + } + } + } + break; + } + case SessionCommand.SelectedNavigationIndex: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.UpdateTabNavigation: { + const window = parseUpdateTabNavigation(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.ActiveWindow: { + const window = parseSessionId(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window] === undefined) { + command_values[window] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.WindowBounds3: { + const window = parseWindowsBounds(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.CommandStorageBackend: { + // Contains nothing + break; + } case SessionCommand.AddWindowExtraData: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.TabUserAgentOverride2: { + const window = parseWindowAppName(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.TabClosed: { + const window = parseLastActive(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.EdgeCommand: { + const window = parseLastActive(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.EdgeCommand2: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.EdgeCommand3: { + const window = parseWindowAppName(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.EdgeCommand4: { + const window = parseWindowAppName(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + + break; + } + case SessionCommand.TabNavigationPathPruned: { + const window = parseWindowType(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionCommand.ExtensionAppID: { + const window = parseSessionStorageAssociated(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + default: { + console.info(`Unsupported session command: ${command}`); + } + } +} + +/** + * Function to parse the data associated with each TabCommand. Many commands have similar strucutres + * @param command `SessionTabCommand` identifier + * @param bytes Bytes associated with the command + * @param command_values Values associated with the SessionCommand + */ +function parseTabCommand(command: SessionTabCommand, bytes: Uint8Array, command_values: Record) { + switch (command) { + case SessionTabCommand.SelectedNavigtionInTab: { + const window = parseLastActive(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: window.last_active + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + const last = command_values[window.session_id]; + if (last !== undefined) { + last.last_active = window.last_active; + } + } + } + break; + } + case SessionTabCommand.UpdateTabNavigation: { + const window = parseUpdateTabNavigation(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionTabCommand.CommandStorageBackend: { + // Contains nothing + break; + } + case SessionTabCommand.Window: { + const window = parseWindow(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + case SessionTabCommand.TabUserAgentOverride2: { + // Contains array of user agents + break; + } + case SessionTabCommand.ExtensionAppID: { + const window = parseSessionStorageAssociated(bytes); + if (!(window instanceof ApplicationError)) { + if (command_values[window.session_id] === undefined) { + command_values[window.session_id] = { + commands: [({ + [command]: window, + })], + last_active: "" + }; + } else { + command_values[window.session_id]?.commands.push({ + [command]: window, + }); + } + } + break; + } + default: { + console.info(`Unsupported tab command: ${command}`); + } + } +} + +interface WindowType { + session_id: number; + index: number; +} + +/** + * Parse the WindowType info + * @param bytes Bytes associated with the WindowType. Should always be 8 bytes + * @returns `WindowType` object or `ApplicationError` + */ +function parseWindowType(bytes: Uint8Array): WindowType | ApplicationError { + const session = nomUnsignedFourBytes(bytes, Endian.Le); + if (session instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to window type session id: ${session}`); + } + + const index = nomUnsignedFourBytes(session.remaining, Endian.Le); + if (index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to window type index: ${index}`); + } + + const window: WindowType = { + session_id: session.value, + index: index.value, + }; + + return window; +} + +/** + * Parse the WindowAppName info + * @param bytes Bytes associated with the WindowType. Should always be 12 bytes + * @returns `WindowType` object or `ApplicationError` + */ +function parseWindowAppName(bytes: Uint8Array): WindowType | ApplicationError { + const size = nomUnsignedFourBytes(bytes, Endian.Le); + if (size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to window app name size: ${size}`); + } + // Size is 8 bytes? + // Same values as WindowType? + + return parseWindowType(size.remaining); +} + +/** + * Parse the TabGroup info + * @param bytes Bytes associated with the TabGroup + * @returns Array of `WindowType` or `Application Error` + */ +function parseTabGroup(bytes: Uint8Array): WindowType[] | ApplicationError { + const limit = 4; + let count = 0; + const group: WindowType[] = []; + let remaining = bytes; + while (count < limit) { + const session = nomUnsignedFourBytes(remaining, Endian.Le); + if (session instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to window type session id: ${session}`); + } + + const index = nomUnsignedFourBytes(session.remaining, Endian.Le); + if (index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to window type index: ${index}`); + } + remaining = index.remaining; + + const window: WindowType = { + session_id: session.value, + index: index.value, + }; + group.push(window); + count += 1; + } + + return group; +} + +interface SessionStorageAssociated { + session_id: number; + value: string; +} + +/** + * Parse the session storage associated info. Usually contains a string + * @param bytes Bytes assocaited with SessionStorageAssociated + * @returns `SessionStorageAssociated` object or `ApplicationError` object + */ +function parseSessionStorageAssociated(bytes: Uint8Array): SessionStorageAssociated | ApplicationError { + let size = nomUnsignedFourBytes(bytes, Endian.Le); + if (size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated size: ${size}`); + } + const session_id = nomUnsignedFourBytes(size.remaining, Endian.Le); + if (session_id instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated id: ${session_id}`); + } + + size = nomUnsignedFourBytes(session_id.remaining, Endian.Le); + if (size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated string size: ${size}`); + } + + const string_data = take(size.remaining, size.value); + if (string_data instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get session storage associated string: ${string_data}`); + } + + const value = extractUtf8String(string_data.nommed as Uint8Array); + const info: SessionStorageAssociated = { + session_id: session_id.value, + value, + }; + return info; +} + +interface LastActive { + session_id: number; + index: number; + last_active: string; +} + +/** + * Parse the last active timestamp + * @param bytes Bytes assocaited with LastActive + * @returns `LastActive` object or `ApplicationError` object + */ +function parseLastActive(bytes: Uint8Array): LastActive | ApplicationError { + const session = nomUnsignedFourBytes(bytes, Endian.Le); + if (session instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to last active session id: ${session}`); + } + + const index = nomUnsignedFourBytes(session.remaining, Endian.Le); + if (index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get last active index: ${index}`); + } + + const timestamp = nomUnsignedEightBytes(index.remaining, Endian.Le); + if (timestamp instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get last active timestamp: ${timestamp}`); + } + + const last: LastActive = { + session_id: session.value, + index: index.value, + last_active: unixEpochToISO(webkitToUnixEpoch(Number(timestamp.value / 1000000n))), + }; + + return last; +} + +/** + * Get the session ID value + * @param bytes Bytes associated with the session ID + * @returns Session ID or `ApplicationError + */ +function parseSessionId(bytes: Uint8Array): number | ApplicationError { + const session = nomUnsignedFourBytes(bytes, Endian.Le); + if (session instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to last active session id: ${session}`); + } + + return session.value; +} + +interface TabNavigation { + session_id: number; + unknown: number; + unknown2: number; + unknown3: number; + unknown4: number; + unknown5: number; +} + +/** + * Parse the windows bound info. + * @param bytes Bytes associted with TabNavigation + * @returns `TabNavigation` object or `ApplicationError` + */ +function parseWindowsBounds(bytes: Uint8Array): TabNavigation | ApplicationError { + const session = nomUnsignedFourBytes(bytes, Endian.Le); + if (session instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get windows bound session id: ${session}`); + } + + const unknown = nomUnsignedFourBytes(session.remaining, Endian.Le); + if (unknown instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown: ${unknown}`); + } + + const unknown2 = nomUnsignedFourBytes(unknown.remaining, Endian.Le); + if (unknown2 instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown2: ${unknown2}`); + } + + const unknown3 = nomUnsignedFourBytes(unknown2.remaining, Endian.Le); + if (unknown3 instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown3: ${unknown3}`); + } + + const unknown4 = nomUnsignedFourBytes(unknown3.remaining, Endian.Le); + if (unknown4 instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown4: ${unknown4}`); + } + + const unknown5 = nomUnsignedFourBytes(unknown4.remaining, Endian.Le); + if (unknown5 instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get windows bound unknown5: ${unknown5}`); + } + + const tab: TabNavigation = { + session_id: session.value, + unknown: unknown.value, + unknown2: unknown2.value, + unknown3: unknown3.value, + unknown4: unknown4.value, + unknown5: unknown5.value + }; + + return tab; +} + +interface TabInfo { + session_id: number; + index: number; + url: string; + title: string; +} + +/** + * Parse the Tab Navigation info. Usually contains page info like URL and page title and alot more + * @param bytes Bytes associated with TabInfo + * @returns `TabInfo` object or `ApplicationError` + */ +function parseUpdateTabNavigation(bytes: Uint8Array): TabInfo | ApplicationError { + const size = nomUnsignedFourBytes(bytes, Endian.Le); + if (size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation size: ${size}`); + } + + const session_id = nomUnsignedFourBytes(size.remaining, Endian.Le); + if (session_id instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation session id: ${session_id}`); + } + const index = nomUnsignedFourBytes(session_id.remaining, Endian.Le); + if (index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation index: ${index}`); + } + + // Size does NOT include end of string character + const url_size = nomUnsignedFourBytes(index.remaining, Endian.Le); + if (url_size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url size: ${url_size}`); + } + + // Should align to 4 bytes? + let adjust = url_size.value % 4; + if (adjust !== 0) { + adjust = 4 - adjust; + } + + const url_data = take(url_size.remaining, url_size.value + adjust); + if (url_data instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url: ${url_data}`); + } + + const url = extractUtf8String(url_data.nommed as Uint8Array); + + const title_size = nomUnsignedFourBytes(url_data.remaining as Uint8Array, Endian.Le); + if (title_size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url title size: ${title_size}`); + } + + const adjust_size = 2; + // Align 4 bytes + adjust = (title_size.value * adjust_size) % 4; + if (adjust !== 0) { + adjust = 4 - adjust; + } + + let title = ""; + let remaining = title_size.remaining; + if (title_size.value !== 0) { + // Title data is UTF16 o.O and does NOT include end of string character + const title_data = take(title_size.remaining, (title_size.value + adjust)); + if (title_data instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url title: ${title_data}`); + } + remaining = title_data.remaining as Uint8Array; + + const title_string = extractUtf16String(title_data.nommed as Uint8Array); + title = title_string; + } + + // A lot more left to parse! + // https://digitalinvestigation.wordpress.com/2012/09/03/chrome-session-and-tabs-files-and-the-puzzle-of-the-pickle/ + + const state_size = nomUnsignedFourBytes(remaining, Endian.Le); + if (state_size instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url state size: ${state_size}`); + } + // A very complex format. Currently not parsing. *Might* contain info like URL referrer, User agent, additional timestamps + // const state_data = take(state_size.remaining, state_size.value); + // if (state_data instanceof NomError) { + // return new ApplicationError(`CHROMIUM`, `Failed to get update tab navigation url state data: ${state_data}`); + // } + + const info: TabInfo = { + session_id: session_id.value, + index: index.value, + url, + title, + }; + + return info; +} + +interface Window { + session_id: number; + index: number; + unknown: number; + window_timestamp: string; +} + +/** + * Parse the Window session command + * @param bytes Bytes associated with the Window command + * @returns `Window` object or `ApplicationError` + */ +function parseWindow(bytes: Uint8Array): Window | ApplicationError { + const session = nomUnsignedFourBytes(bytes, Endian.Le); + if (session instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get window session id: ${session}`); + } + + const index = nomUnsignedFourBytes(session.remaining, Endian.Le); + if (index instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get window index: ${index}`); + } + + const unknown = nomUnsignedFourBytes(index.remaining, Endian.Le); + if (unknown instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get window unknown: ${unknown}`); + } + + const timestamp = nomUnsignedEightBytes(unknown.remaining, Endian.Le); + if (timestamp instanceof NomError) { + return new ApplicationError(`CHROMIUM`, `Failed to get window timestamp: ${timestamp}`); + } + + // A lot more left to parse! + // https://digitalinvestigation.wordpress.com/2012/09/03/chrome-session-and-tabs-files-and-the-puzzle-of-the-pickle/ + + + const win: Window = { + session_id: session.value, + index: index.value, + unknown: unknown.value, + window_timestamp: unixEpochToISO(webkitToUnixEpoch(Number(timestamp.value / 1000000n))), + }; + + return win; +} + +/** + * Function to test the Chromium Sessions parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Chromium Sessions parsing + */ +export function testChromiumSessions(): void { + const path: ChromiumProfiles = { + full_path: "../../test_data/edge", + version: "141", + browser: BrowserType.EDGE + }; + + const sess = chromiumSessions([path], PlatformType.Darwin); + if (sess.length !== 115) { + throw `Got length ${sess.length} expected 115.......chromiumSessions ❌`; + } + + if (!sess[67]?.evidence.includes("Session_13406596695873160")) { + throw `Got evidence "${sess[67]?.evidence}" expected "Session_13406596695873160".......chromiumSessions ❌`; + } + + if (sess[12]?.message !== "Session: https://www.washingtonpost.com/politics/2025/11/02/nuclear-testing-trump-energy-secretary/ | Page Title: Trump energy secretary says no nuclear b") { + throw `Got message "${sess[12]?.message}" expected "Session: https://www.washingtonpost.com/politics/2025/11/02/nuclear-testing-trump-energy-secretary/ | Page Title: Trump energy secretary says no nuclear b".......chromiumSessions ❌`; + } + + if (sess[0]?.message != "Session: edge://newtab/ | Page Title: New T") { + throw `Got message ${sess[0]?.message} expected "Session: edge://newtab/ | Page Title: New T".......chromiumSessions ❌`; + } + + console.info(` Function chromiumSessions ✅`); + + const sess_file = "../../test_data/edge/v141/Sessions/Session_13406596486318013"; + const results = parseSession(sess_file, SessionType.Session, path, PlatformType.Darwin); + if (results instanceof ApplicationError) { + throw results.message; + } + + if (results.length !== 58) { + throw `Got length ${results.length} expected 58.......parseSession ❌`; + } + + if (results[7]?.message != "Session: https://www.washingtonpost.com/ | Page Title: The Washington Post - Breaking news and latest headlines") { + throw `Got message ${results[7]?.message} expected "Session: https://www.washingtonpost.com/ | Page Title: The Washington Post - Breaking news and latest headlines".......parseSession ❌`; + } + + console.info(` Function parseSession ✅`); + + const header = getHeader(new Uint8Array([0, 0, 0, 0, 1, 0, 0, 0])); + if (header instanceof ApplicationError) { + throw header.message; + } + + if (header.version !== 1) { + throw `Got length ${header.version} expected 1.......getHeader ❌`; + } + + console.info(` Function getHeader ✅`); + + const test = [0, 1, 2, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 131, 132, 44, 50, 255]; + for (const entry of test) { + const value = getSessionCommand(entry); + if (value === SessionCommand.Unknown) { + throw `Got Session command Unknown for ${entry}.......getSessionCommand ❌` + } + } + + console.info(` Function getSessionCommand ✅`); + + for (const entry of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 255]) { + const value = getTabCommand(entry); + if (value === SessionTabCommand.Unknown) { + throw `Got Tab command Unknown for ${entry}.......getTabCommand ❌` + } + } + + console.info(` Function getTabCommand ✅`); + + const comm: Record = {}; + parseSessionCommand(SessionCommand.TabWindow, new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0]), comm); + if (!JSON.stringify(comm).includes("TabWindow")) { + throw `Got Session command ${JSON.stringify(comm)} wanted "TabWindow".......parseSessionCommand ❌` + } + + console.info(` Function parseSessionCommand ✅`); + + const tab: Record = {}; + parseTabCommand(SessionTabCommand.SelectedNavigtionInTab, new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0]), tab); + if (!JSON.stringify(tab).includes("SelectedNavigationInTab")) { + throw `Got Session command ${JSON.stringify(tab)} wanted "SelectedNavigationInTab".......parseTabCommand ❌` + } + + console.info(` Function parseTabCommand ✅`); + + let info = parseWindowType(new Uint8Array([1, 0, 0, 0, 2, 0, 0, 0])); + if (info instanceof ApplicationError) { + throw info.message; + } + + if (info.index !== 2) { + throw `Got index ${info.index} expected 2.......parseWindowType ❌`; + } + + console.info(` Function parseWindowType ✅`); + + info = parseWindowAppName(new Uint8Array([8, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0])); + if (info instanceof ApplicationError) { + throw info.message; + } + + if (info.index !== 2) { + throw `Got index ${info.index} expected 2.......parseWindowAppName ❌`; + } + + console.info(` Function parseWindowAppName ✅`); + + const tab_group = parseTabGroup(new Uint8Array([8, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0])); + if (tab_group instanceof ApplicationError) { + throw tab_group.message; + } + + if (tab_group.length !== 4) { + throw `Got index ${tab_group.length} expected 4.......parseTabGroup ❌`; + } + + console.info(` Function parseTabGroup ✅`); + + const url_bytes = parseSessionStorageAssociated(new Uint8Array([44, 0, 0, 0, 208, 120, 213, 109, 36, 0, 0, 0, 97, 102, 100, 99, 50, 50, 54, 100, 95, 99, 55, 57, 51, 95, 52, 97, 51, 52, 95, 97, 49, 51, 50, 95, 50, 100, 102, 100, 99, 99, 50, 53, 56, 97, 57, 50])); + if (url_bytes instanceof ApplicationError) { + throw url_bytes.message; + } + + if (url_bytes.value !== "afdc226d_c793_4a34_a132_2dfdcc258a92") { + throw `Got value ${url_bytes.value} expected "afdc226d_c793_4a34_a132_2dfdcc258a92".......parseSessionStorageAssociated ❌`; + } + console.info(` Function parseSessionStorageAssociated ✅`); + + const last_active = parseLastActive(new Uint8Array([81, 121, 213, 109, 0, 0, 0, 0, 59, 250, 123, 137, 58, 161, 47, 0])); + if (last_active instanceof ApplicationError) { + throw last_active.message; + } + + if (last_active.last_active !== "2025-11-02T22:38:12.000Z") { + throw `Got time ${last_active.last_active} expected "2025-11-02T22:38:12.000Z".......parseLastActive ❌`; + } + console.info(` Function parseLastActive ✅`); + + const session_id = parseSessionId(new Uint8Array([1, 0, 0, 0])); + if (session_id instanceof ApplicationError) { + throw session_id.message; + } + + if (session_id !== 1) { + throw `Got ID ${session_id} expected "1".......parseSessionId ❌`; + } + console.info(` Function parseSessionId ✅`); + + const navigate = parseWindowsBounds(new Uint8Array([11, 121, 213, 109, 22, 0, 0, 0, 22, 0, 0, 0, 0, 15, 0, 0, 56, 4, 0, 0, 1, 0, 0, 0])); + if (navigate instanceof ApplicationError) { + throw navigate.message; + } + + if (navigate.session_id !== 1842706699) { + throw `Got ID ${navigate.session_id} expected "1842706699".......parseWindowsBounds ❌`; + } + console.info(` Function parseWindowsBounds ✅`); + + const bytes = readFile("../../test_data/edge/v141/raw/tab_url.raw"); + if (bytes instanceof FileError) { + throw bytes.message; + } + + const tab_url = parseUpdateTabNavigation(bytes); + if (tab_url instanceof ApplicationError) { + throw tab_url.message; + } + + if (tab_url.url != "edge://newtab/") { + throw `Got URL ${tab_url.url} expected "edge://newtab/".......parseUpdateTabNavigation ❌`; + } + console.info(` Function parseUpdateTabNavigation ✅`); + + const win = parseWindow(new Uint8Array([11, 121, 213, 109, 22, 0, 0, 0, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])); + if (win instanceof ApplicationError) { + throw win.message; + } + + if (win.session_id !== 1842706699) { + throw `Got ID ${win.session_id} expected "1842706699".......parseWindow ❌`; + } + console.info(` Function parseWindow ✅`); } \ No newline at end of file diff --git a/src/applications/chromium/sqlite.ts b/src/applications/chromium/sqlite.ts index f1da6ae5..c8ceda4d 100644 --- a/src/applications/chromium/sqlite.ts +++ b/src/applications/chromium/sqlite.ts @@ -1,878 +1,878 @@ -import { ChromiumProfiles, ChromiumHistory, ChromiumDownloads, ChromiumCookies, ChromiumAutofill, ChromiumLogins, ChromiumDips, ChromiumCookieType, BrowserType, ChromiumFavicons, ChromiumShortcuts, ShortcutType } from "../../../types/applications/chromium"; -import { FileError } from "../../filesystem/errors"; -import { glob } from "../../filesystem/files"; -import { PlatformType } from "../../system/systeminfo"; -import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; -import { Unfold } from "../../unfold/client"; -import { UnfoldError } from "../../unfold/error"; -import { ApplicationError } from "../errors"; -import { querySqlite } from "../sqlite"; - -/** - * Get Chromium history for users - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param unfold Enable unfold parsing - * @param query SQL query to run - * @returns Array of `ChromiumHistory` - */ -export function chromiumHistory(paths: ChromiumProfiles[], platform: PlatformType, unfold: boolean, query: string): ChromiumHistory[] { - const hits: ChromiumHistory[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/History`; - - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\History`; - } - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - - for (const entry_path of glob_paths) { - const results = querySqlite(entry_path.full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${results}`); - continue; - } - let client: Unfold | undefined = undefined; - if (unfold) { - client = new Unfold(); - } - - // Loop through history rows - for (const entry of results) { - const adjust = 1000000; - const webkit = webkitToUnixEpoch( - (entry["last_visit_time"] as number ?? 0) / adjust, - ); - const history_row: ChromiumHistory = { - id: entry["id"] as number ?? 0, - url: entry["url"] as string ?? "", - title: entry["title"] as string ?? "", - visit_count: entry["visit_count"] as number ?? 0, - typed_count: entry["typed_count"] as number ?? 0, - last_visit_time: unixEpochToISO(webkit), - hidden: entry["hidden"] as number ?? 0, - visits_id: entry["visits_id"] as number ?? 0, - from_visit: entry["from_visit"] as number ?? 0, - transition: entry["transition"] as number ?? 0, - segment_id: entry["segment_id"] as number ?? 0, - visit_duration: entry["visit_duration"] as number ?? 0, - opener_visit: entry["opener_visit"] as number ?? 0, - unfold: undefined, - db_path: entry_path.full_path, - version: path.version, - message: entry["url"] as string ?? "", - datetime: unixEpochToISO(webkit), - timestamp_desc: "URL Visited", - artifact: "URL History", - data_type: `applications:${path.browser.toLowerCase()}:history:entry`, - browser: path.browser, - }; - - if (unfold && typeof client !== 'undefined') { - const result = client.parseUrl(history_row.url); - if (!(result instanceof UnfoldError)) { - history_row.unfold = result; - } - } - - hits.push(history_row); - } - } - - - } - - return hits; -} - -/** - * Get Chromium downloads for users - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @param browser Chromium based browser type - * @returns Array of `ChromiumDownloads` - */ -export function chromiumDownloads(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumDownloads[] { - const hits: ChromiumDownloads[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/History`; - - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\History`; - } - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - for (const entry_path of glob_paths) { - const results = querySqlite(entry_path.full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${results}`); - continue; - } - // Loop through downloads rows - for (const entry of results) { - const adjust = 1000000; - const start = webkitToUnixEpoch( - (entry["start_time"] as number ?? 0) / adjust, - ); - const end = webkitToUnixEpoch( - (entry["end_time"] as number ?? 0) / adjust, - ); - const access = webkitToUnixEpoch( - (entry["last_access_time"] as number ?? 0) / adjust, - ); - const download_row: ChromiumDownloads = { - id: entry["id"] as number ?? 0, - guid: entry["guid"] as string ?? "", - current_path: entry["current_path"] as string ?? "", - target_path: entry["target_path"] as string ?? "", - start_time: unixEpochToISO(start), - received_bytes: entry["received_bytes"] as number ?? 0, - total_bytes: entry["total_bytes"] as number ?? 0, - state: entry["state"] as number ?? 0, - danger_type: entry["danger_type"] as number ?? 0, - interrupt_reason: entry["interrupt_reason"] as number ?? 0, - hash: entry["hash"] as number[] ?? [], - end_time: unixEpochToISO(end), - opened: entry["opened"] as number ?? 0, - last_access_time: unixEpochToISO(access), - transient: entry["transient"] as number ?? 0, - referrer: entry["referrer"] as string ?? "", - site_url: entry["site_url"] as string ?? "", - tab_url: entry["tab_url"] as string ?? "", - tab_referrer_url: entry["tab_referrer_url"] as string ?? "", - http_method: entry["http_method"] as string ?? "", - by_ext_id: entry["by_ext_id"] as string ?? "", - by_ext_name: entry["by_ext_name"] as string ?? "", - etag: entry["etag"] as string ?? "", - last_modified: entry["last_modified"] as string ?? "", - mime_type: entry["mime_type"] as string ?? "", - original_mime_type: entry["original_mime_type"] as string ?? "", - downloads_url_chain_id: entry["downloads_url_chain_id"] as number ?? 0, - chain_index: entry["chain_index"] as number ?? 0, - url: entry["url"] as string ?? "", - db_path: entry_path.full_path, - version: path.version, - message: `${entry["url"] as string ?? ""} | ${entry["target_path"] as string ?? ""}`, - datetime: unixEpochToISO(start), - timestamp_desc: "File Download Start", - artifact: "File Download", - data_type: `applications:${path.browser.toLowerCase()}:downloads:entry`, - browser: path.browser, - }; - hits.push(download_row); - } - } - } - - return hits; -} - -/** - * Get Chromium downloads for users - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @returns Array of `ChromiumCookies` - */ -export function chromiumCookies(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumCookies[] { - const hits: ChromiumCookies[] = []; - for (const path of paths) { - let full_paths = [`${path.full_path}/*/Cookies`, `${path.full_path}/*/Network/Cookies`]; - - if (platform === PlatformType.Windows) { - full_paths = [`${path.full_path}\\*\\Network\\Cookies`, `${path.full_path}\\*\\Cookies`]; - } - - - for (const full_path of full_paths) { - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - for (const entry_path of glob_paths) { - const results = querySqlite(entry_path.full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${results}`); - continue; - } - // Loop through cookies rows - const adjust_time = 1000000n; - for (const entry of results) { - const cookie_entry: ChromiumCookies = { - creation: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["creation_utc"] as bigint) / adjust_time) - )), - host_key: entry["host_key"] as string, - top_frame_site_key: entry["top_frame_site_key"] as string | undefined ?? "", - name: entry["name"] as string | undefined ?? "", - value: entry["value"] as string | undefined ?? "", - encrypted_value: entry["encrypted_value"] as string, - path: entry["path"] as string | undefined ?? "", - expires: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["expires_utc"] as bigint) / adjust_time) - )), - is_secure: !!(entry["is_secure"] as number), - is_httponly: !!(entry["is_httponly"] as number), - last_access: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["last_access_utc"] as bigint) / adjust_time) - )), - has_expires: !!(entry["has_expires"] as number), - is_persistent: !!(entry["is_persistent"] as number), - priority: entry["priority"] as number, - samesite: entry["samesite"] as number, - source_scheme: entry["source_scheme"] as number, - source_port: entry["source_port"] as number, - is_same_party: entry["is_same_party"] as number | undefined ?? 0, - last_update: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["last_update_utc"] as bigint) / adjust_time) - )), - db_path: entry_path.full_path, - version: path.version, - message: `Cookie name: ${entry["name"] as string} | value: ${entry["value"] as string | undefined ?? ""}`, - datetime: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["expires_utc"] as bigint) / adjust_time) - )), - timestamp_desc: "Cookie Expires", - artifact: "Website Cookie", - data_type: `applications:${path.browser.toLowerCase()}:cookies:entry`, - browser: path.browser, - }; - hits.push(cookie_entry); - } - } - - } - } - return hits; -} - -/** - * Get Favicons for websites - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @returns Array of `ChromiumFavicons` - */ -export function chromiumFavicons(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumFavicons[] { - const hits: ChromiumFavicons[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/Favicons`; - - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Favicons`; - } - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - for (const entry_path of glob_paths) { - const results = querySqlite(entry_path.full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${results}`); - continue; - } - // Loop through favicon rows - for (const entry of results) { - const adjust = 1000000; - const last_update = webkitToUnixEpoch( - (entry["last_updated"] as number ?? 0) / adjust, - ); - let url = "Favicon URL Null"; - let page_url = "Favicon Page Null"; - let message = url; - if (typeof entry["url"] === 'string') { - url = entry["url"]; - message = url; - } - if (typeof entry["page_url"] === 'string') { - page_url = entry["page_url"]; - message = page_url; - } - - const download_row: ChromiumFavicons = { - db_path: entry_path.full_path, - version: path.version, - page_url, - message: `Favicon for ${message}`, - datetime: unixEpochToISO(last_update), - data_type: `applications:${path.browser.toLowerCase()}:favicons:entry`, - browser: path.browser, - last_update: unixEpochToISO(last_update), - url, - timestamp_desc: "Favicon Updated", - artifact: "Website Favicon" - }; - hits.push(download_row); - } - } - } - - return hits; -} - -/** - * Get Shortcuts for websites - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @returns Array of `ChromiumShortcuts` - */ -export function chromiumShortcuts(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumShortcuts[] { - const hits: ChromiumShortcuts[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/Shortcuts`; - - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Shortcuts`; - } - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - for (const entry_path of glob_paths) { - const results = querySqlite(entry_path.full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${results}`); - continue; - } - // Loop through favicon rows - for (const entry of results) { - const adjust = 1000000; - const last_update = webkitToUnixEpoch( - (entry["last_access_time"] as number ?? 0) / adjust, - ); - let short_type = -1; - if (typeof entry["type"] === 'number') { - short_type = entry["type"]; - } - let url = "Shortcut URL Null"; - if (typeof entry["url"] === 'string') { - url = entry["url"]; - } - const download_row: ChromiumShortcuts = { - db_path: entry_path.full_path, - version: path.version, - message: `Shortcut for ${url}`, - datetime: unixEpochToISO(last_update), - data_type: `applications:${path.browser.toLowerCase()}:shortcuts:entry`, - browser: path.browser, - last_update: unixEpochToISO(last_update), - url, - text: entry["text"] as string | undefined ?? "", - contents: entry["contents"] as string | undefined ?? "", - fill_into_edit: entry["fill_into_edit"] as string | undefined ?? "", - shortcut_id: entry["id"] as string | undefined ?? "", - keyword: entry["keyword"] as string | undefined ?? "", - shortcut_type: getShortcut(short_type), - description: entry["description"] as string | undefined ?? "", - timestamp_desc: "Shortcut Last Access", - artifact: "Website Shortcut" - }; - hits.push(download_row); - } - } - } - - return hits; -} - -/** - * Get ShortcutType for websites - * @param value enum value of the shortcut - */ -function getShortcut(value: number): ShortcutType { - switch (value) { - case 0: return ShortcutType.Typed; - case 1: return ShortcutType.History; - case 2: return ShortcutType.HistoryTitle; - case 3: return ShortcutType.HistoryBody; - case 4: return ShortcutType.HistoryKeyword; - case 5: return ShortcutType.Suggest; - case 6: return ShortcutType.SearchUrl; - case 7: return ShortcutType.SearchHistory; - case 8: return ShortcutType.SearchSuggest; - case 9: return ShortcutType.SearchSuggestEntity - case 10: return ShortcutType.SearchTail; - case 11: return ShortcutType.SearchPersonalized; - case 12: return ShortcutType.SearchProfile; - case 13: return ShortcutType.SearchEngine; - case 14: return ShortcutType.ExtensionApp; - case 15: return ShortcutType.UserContact; - case 16: return ShortcutType.Bookmark; - case 17: return ShortcutType.SuggestPersonalized; - case 18: return ShortcutType.Calculator; - case 19: return ShortcutType.Clipboard; - case 20: return ShortcutType.Voice; - case 21: return ShortcutType.Physical; - case 22: return ShortcutType.PhysicalOverflow; - case 23: return ShortcutType.Tab; - case 24: return ShortcutType.Document; - case 25: return ShortcutType.Pedal; - case 26: return ShortcutType.ClipboardText; - case 27: return ShortcutType.ClipboardImage; - case 28: return ShortcutType.TitleSuggest; - case 29: return ShortcutType.TitleNavSuggest; - case 30: return ShortcutType.OpenTab; - case 31: return ShortcutType.HistoryCluster; - case 32: return ShortcutType.Null; - case 33: return ShortcutType.Starter; - case 34: return ShortcutType.MostVisited; - case 35: return ShortcutType.Repeatable; - case 36: return ShortcutType.HistoryEmbed; - case 37: return ShortcutType.Enterprise; - case 38: return ShortcutType.HistoryEmbedAnswers; - case 39: return ShortcutType.TabGroup; - default: return ShortcutType.Unkonwn; - } -} - -/** - * Determine Cookie Type. From `https://chromium.googlesource.com/chromium/src/net/+/refs/heads/main/cookies/cookie_constants.h#378` - * @param source Cookie source_type column - * @returns `ChromiumCookieType` enum value - */ -export function getChromiumCookieType(source: number): ChromiumCookieType { - switch (source) { - case 0: return ChromiumCookieType.Unknown; - case 1: return ChromiumCookieType.Http; - case 2: return ChromiumCookieType.Script; - case 3: return ChromiumCookieType.Other; - default: return ChromiumCookieType.Unknown; - } -} - - -/** - * Function to extract Autofill information from database - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @returns Array of `ChromiumAutofill` - */ -export function chromiumAutofill(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumAutofill[] { - const hits: ChromiumAutofill[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/*/Web Data`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Web Data`; - } - - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - - for (const entry_path of glob_paths) { - const results = querySqlite(entry_path.full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${results}`); - continue; - } - - for (const entry of results) { - const fill_entry: ChromiumAutofill = { - db_path: entry_path.full_path, - date_created: unixEpochToISO(entry["date_created"] as number), - date_last_used: unixEpochToISO(entry["date_last_used"] as number), - count: entry["count"] as number, - name: entry["name"] as string ?? "", - value: entry["value"] as string ?? "", - value_lower: entry["value_lower"] as string ?? "", - version: path.version, - message: `Autofill name: ${entry["name"] as string ?? ""} | value: ${entry["value"] as string ?? ""}`, - datetime: unixEpochToISO(entry["date_created"] as number), - timestamp_desc: "Autofill Created", - artifact: "Website Autofill", - data_type: `applications:${path.browser.toLowerCase()}:autofill:entry`, - browser: path.browser, - }; - hits.push(fill_entry); - } - } - } - return hits; -} - -/** - * Function to extract Autofill information from database - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @returns Array of `ChromiumLogins` - */ -export function chromiumLogins(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumLogins[] { - const hits: ChromiumLogins[] = []; - const adjust_time = 1000000n; - - for (const path of paths) { - let full_path = `${path.full_path}/*/Login Data`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\Login Data`; - } - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - - for (const entry_path of glob_paths) { - const data = querySqlite(entry_path.full_path, query); - if (data instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${data}`); - continue; - } - // Loop through logins rows - for (const entry of data) { - const login_entry: ChromiumLogins = { - origin_url: entry["origin_url"] as string, - signon_realm: entry["signon_realm"] as string, - date_created: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_created"] as bigint) / adjust_time) - )), - blacklisted_by_user: entry["blacklisted_by_user"] as number, - scheme: entry["scheme"] as number, - id: entry["id"] as number, - date_last_used: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_last_used"] as bigint) / adjust_time) - )), - date_password_modified: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_password_modified"] as bigint) / adjust_time) - )), - sharing_notification_display: entry["sharing_notification_display"] as number, - db_path: entry_path.full_path, - action_url: entry["action_url"] as string | undefined, - username_element: entry["username_element"] as string | undefined, - username_value: entry["username_value"] as string | undefined, - times_used: entry["times_used"] as number | undefined, - icon_url: entry["icon_url"] as string | undefined, - possible_username_pairs: entry["possible_username_pairs"] as string | - undefined, - federation_url: entry["federation_url"] as string | undefined, - generation_upload_status: entry["generation_upload_status"] as number | - undefined, - sender_profile_image_url: entry["sender_profile_image_url"] as string | - undefined, - password_element: entry["password_element"] as string | undefined, - password_type: entry["password_type"] as number | undefined, - password_value: entry["password_value"] as string | undefined, - date_received: unixEpochToISO(webkitToUnixEpoch( - typeof entry["date_received"] === "undefined" - ? 0 - : Number(BigInt(entry["date_received"] as bigint) / adjust_time) - )), - sender_email: entry["sender_email"] as string | undefined, - sender_name: entry["sender_name"] as string | undefined, - skip_zero_click: entry["skip_zero_click"] as number | undefined, - submit_element: entry["submit_element"] as string | undefined, - display_name: entry["display_name"] as string | undefined, - form_data: entry["form_data"] as string | undefined, - moving_blocked_for: entry["moving_blocked_for"] as string | undefined, - keychain_identifier: entry["keychain_identifier"] as string | undefined, - version: path.version, - message: entry["origin_url"] as string, - datetime: unixEpochToISO(webkitToUnixEpoch( - Number(BigInt(entry["date_last_used"] as bigint) / adjust_time) - )), - timestamp_desc: "Last Login", - artifact: "Website Login", - data_type: `applications:${path.browser.toLowerCase()}:login:entry`, - browser: path.browser, - }; - hits.push(login_entry); - } - } - } - return hits; -} - -/** - * Function to extract Detect Incidental Party State (DIPS) information from database - * @param paths Array of `ChromiumProfiles` - * @param platform OS `PlatformType` - * @param query SQL query to run - * @returns Array of `ChromiumDips` - */ -export function chromiumDips(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumDips[] { - const hits: ChromiumDips[] = []; - const adjust_time = 1000000n; - - for (const path of paths) { - let full_path = `${path.full_path}/*/DIPS`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\*\\DIPS`; - } - - const glob_paths = glob(full_path); - if (glob_paths instanceof FileError) { - console.warn(`Failed to glob ${full_path}: ${glob_paths}`); - continue; - } - - for (const entry_path of glob_paths) { - const data = querySqlite(entry_path.full_path, query); - if (data instanceof ApplicationError) { - console.warn(`Failed to query ${entry_path.full_path}: ${data}`); - continue; - } - // Loop through logins rows - for (const entry of data) { - const dips_entry: ChromiumDips = { - site: entry["site"] as string, - path: entry_path.full_path, - first_bounce: unixEpochToISO(webkitToUnixEpoch( - typeof entry["first_bounce_time"] === "undefined" || - entry["first_bounce_time"] === null - ? 0 - : Number(BigInt(entry["first_bounce_time"] as bigint) / adjust_time) - )), - last_bounce: unixEpochToISO(webkitToUnixEpoch( - typeof entry["last_bounce_time"] === "undefined" || - entry["last_bounce_time"] === null - ? 0 - : Number(BigInt(entry["last_bounce_time"] as bigint) / adjust_time) - )), - first_site_storage: unixEpochToISO(webkitToUnixEpoch( - typeof entry["first_site_storage_time"] === "undefined" || - entry["first_site_storage_time"] === null - ? 0 - : Number( - BigInt(entry["first_site_storage_time"] as bigint) / adjust_time - ) - )), - first_stateful_bounce: unixEpochToISO(webkitToUnixEpoch( - typeof entry["first_stateful_bounce_time"] === "undefined" || - entry["first_stateful_bounce_time"] === null - ? 0 - : Number( - BigInt(entry["first_stateful_bounce_time"] as bigint) / adjust_time - ) - )), - first_user_interaction: unixEpochToISO(webkitToUnixEpoch( - typeof entry["first_user_interaction_time"] === "undefined" || - entry["first_user_interaction_time"] === null - ? 0 - : Number( - BigInt(entry["first_user_interaction_time"] as bigint) / - adjust_time - ) - )), - first_web_authn_assertion: unixEpochToISO(webkitToUnixEpoch( - entry["first_web_authn_assertion_time"] === null ? 0 : Number( - BigInt(entry["first_web_authn_assertion_time"] as bigint) / - adjust_time - ) - )), - last_site_storage: unixEpochToISO(webkitToUnixEpoch( - typeof entry["last_site_storage_time"] === "undefined" || - entry["last_site_storage_time"] === null - ? 0 - : Number( - BigInt(entry["last_site_storage_time"] as bigint) / adjust_time - ) - )), - last_stateful_bounce: unixEpochToISO(webkitToUnixEpoch( - typeof entry["last_stateful_bounce_time"] === "undefined" || - entry["last_stateful_bounce_time"] === null - ? 0 - : Number( - BigInt(entry["last_stateful_bounce_time"] as bigint) / adjust_time - ) - )), - last_user_interaction: unixEpochToISO(webkitToUnixEpoch( - typeof entry["last_user_interaction_time"] === "undefined" || - entry["last_user_interaction_time"] === null - ? 0 - : Number( - BigInt(entry["last_user_interaction_time"] as bigint) / adjust_time - ) - )), - last_web_authn_assertion: unixEpochToISO(webkitToUnixEpoch( - entry["last_web_authn_assertion_time"] === null ? 0 : Number( - BigInt(entry["last_web_authn_assertion_time"] as bigint) / - adjust_time - ) - )), - version: path.version, - message: entry["site"] as string, - datetime: unixEpochToISO(webkitToUnixEpoch( - typeof entry["first_site_storage_time"] === "undefined" || - entry["first_site_storage_time"] === null - ? 0 - : Number( - BigInt(entry["first_site_storage_time"] as bigint) / - adjust_time - ) - )), - timestamp_desc: "First Interaction", - artifact: "Browser DIPS", - data_type: `applications:${path.browser.toLowerCase()}:dips:entry`, - browser: path.browser, - }; - hits.push(dips_entry); - } - } - } - return hits; -} - -/** - * Function to test the Chromium sqlite file parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Chromium sqlite parsing - */ -export function testChromiumSqlite(): void { - const path: ChromiumProfiles = { - full_path: "../../test_data/edge", - version: "141", - browser: BrowserType.EDGE - }; - let query = `SELECT - urls.id AS id, - urls.url AS url, - title, - visit_count, - typed_count, - last_visit_time, - hidden, - visits.id AS visits_id, - from_visit, - transition, - segment_id, - visit_duration, - opener_visit - FROM - urls - JOIN visits ON urls.id = visits.url LIMIT 60 OFFSET 0`; - - const hist = chromiumHistory([path], PlatformType.Darwin, true, query); - if (hist.length !== 56) { - throw `Got length ${hist.length} expected 56.......chromiumHistory ❌`; - } - - if (hist[23]?.url !== "https://www.washingtonpost.com/latest-headlines/") { - throw `Got url ${hist[23]?.url} expected "https://www.washingtonpost.com/latest-headlines/".......chromiumHistory ❌`; - } - - console.info(` Function chromiumHistory ✅`); - - query = `SELECT - downloads.id AS downloads_id, - guid, - current_path, - target_path, - start_time, - received_bytes, - total_bytes, - state, - danger_type, - interrupt_reason, - hash, - end_time, - opened, - last_access_time, - transient, - referrer, - site_url, - tab_url, - tab_referrer_url, - http_method, - by_ext_id, - by_ext_name, - etag, - last_modified, - mime_type, - original_mime_type, - downloads_url_chains.id AS downloads_url_chain_id, - chain_index, - url - FROM - downloads - JOIN downloads_url_chains ON downloads_url_chains.id = downloads.id LIMIT 20 OFFSET 0`; - const downs = chromiumDownloads([path], PlatformType.Darwin, query); - if (downs.length !== 6) { - throw `Got length ${downs.length} expected 6.......chromiumDownloads ❌`; - } - - if (downs[4]?.url !== "https://github.com/Velocidex/velociraptor/releases/download/v0.75/velociraptor-v0.75.1-windows-amd64.exe") { - throw `Got url ${downs[4]?.url} expected "https://github.com/Velocidex/velociraptor/releases/download/v0.75/velociraptor-v0.75.1-windows-amd64.exe".......chromiumDownloads ❌`; - } - - console.info(` Function chromiumDownloads ✅`); - - query = `SELECT * FROM cookies`; - const cook = chromiumCookies([path], PlatformType.Darwin, query); - if (cook.length !== 251) { - throw `Got length ${cook.length} expected 251.......chromiumCookies ❌`; - } - - if (cook[4]?.message !== "Cookie name: __rubyUX | value: ") { - throw `Got url ${cook[4]?.message} expected "Cookie name: __rubyUX | value: ".......chromiumCookies ❌`; - } - - console.info(` Function chromiumCookies ✅`); - - query = `SSELECT name, value, date_created, date_last_used, count, value_lower from autofill`; - const fill = chromiumAutofill([path], PlatformType.Darwin, query); - if (fill.length !== 0) { - throw `Got length ${fill.length} expected 0.......chromiumAutofill ❌`; - } - - console.info(` Function chromiumAutofill ✅`); - - query = `SELECT * FROM logins`; - const log = chromiumLogins([path], PlatformType.Darwin, query); - if (log.length !== 0) { - throw `Got length ${log.length} expected 0.......chromiumLogins ❌`; - } - - console.info(` Function chromiumLogins ✅`); - - query = `SELECT * FROM bounces`; - const dip = chromiumDips([path], PlatformType.Darwin, query); - if (dip.length !== 33) { - throw `Got length ${dip.length} expected 33.......chromiumDips ❌`; - } - - console.info(` Function chromiumDips ✅`); - - query = `SELECT url, last_updated FROM favicons JOIN favicon_bitmaps ON favicons.id = favicon_bitmaps.id`; - const favi = chromiumFavicons([path], PlatformType.Darwin, query); - if (favi.length !== 60) { - throw `Got length ${favi.length} expected 60.......chromiumFavicons ❌`; - } - if (favi[0]?.message !== "Favicon for chrome-extension://lmijmgnfconjockjeepmlmkkibfgjmla/pages/shared/resources/images/icons/extension/favicon.ico") { - throw `Got url ${favi[0]?.message} expected wanted "Favicon for chrome-extension://lmijmgnfconjockjeepmlmkkibfgjmla/pages/shared/resources/images/icons/extension/favicon.ico".......chromiumFavicons ❌`; - } - - console.info(` Function chromiumFavicons ✅`); - - query = `SELECT id,text,fill_into_edit,url,contents,description,type,keyword,last_access_time FROM omni_box_shortcuts`; - const short = chromiumShortcuts([path], PlatformType.Darwin, query); - if (short.length !== 98) { - throw `Got length ${short.length} expected 60.......chromiumShortcuts ❌`; - } - if (short[17]?.message !== "Shortcut for https://www.bing.com/search?q=sharphound+github&FORM=ANAB01&PC=U531") { - throw `Got url ${short[17]?.message} expected wanted "Shortcut for https://www.bing.com/search?q=sharphound+github&FORM=ANAB01&PC=U531".......chromiumShortcuts ❌`; - } - - console.info(` Function chromiumShortcuts ✅`); +import { ChromiumProfiles, ChromiumHistory, ChromiumDownloads, ChromiumCookies, ChromiumAutofill, ChromiumLogins, ChromiumDips, ChromiumCookieType, BrowserType, ChromiumFavicons, ChromiumShortcuts, ShortcutType } from "../../../types/applications/chromium"; +import { FileError } from "../../filesystem/errors"; +import { glob } from "../../filesystem/files"; +import { PlatformType } from "../../system/systeminfo"; +import { unixEpochToISO, webkitToUnixEpoch } from "../../time/conversion"; +import { Unfold } from "../../unfold/client"; +import { UnfoldError } from "../../unfold/error"; +import { ApplicationError } from "../errors"; +import { querySqlite } from "../sqlite"; + +/** + * Get Chromium history for users + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param unfold Enable unfold parsing + * @param query SQL query to run + * @returns Array of `ChromiumHistory` + */ +export function chromiumHistory(paths: ChromiumProfiles[], platform: PlatformType, unfold: boolean, query: string): ChromiumHistory[] { + const hits: ChromiumHistory[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/History`; + + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\History`; + } + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + + for (const entry_path of glob_paths) { + const results = querySqlite(entry_path.full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${results}`); + continue; + } + let client: Unfold | undefined = undefined; + if (unfold) { + client = new Unfold(); + } + + // Loop through history rows + for (const entry of results) { + const adjust = 1000000; + const webkit = webkitToUnixEpoch( + (entry[ "last_visit_time" ] as number ?? 0) / adjust, + ); + const history_row: ChromiumHistory = { + id: entry[ "id" ] as number ?? 0, + url: entry[ "url" ] as string ?? "", + title: entry[ "title" ] as string ?? "", + visit_count: entry[ "visit_count" ] as number ?? 0, + typed_count: entry[ "typed_count" ] as number ?? 0, + last_visit_time: unixEpochToISO(webkit), + hidden: entry[ "hidden" ] as number ?? 0, + visits_id: entry[ "visits_id" ] as number ?? 0, + from_visit: entry[ "from_visit" ] as number ?? 0, + transition: entry[ "transition" ] as number ?? 0, + segment_id: entry[ "segment_id" ] as number ?? 0, + visit_duration: entry[ "visit_duration" ] as number ?? 0, + opener_visit: entry[ "opener_visit" ] as number ?? 0, + unfold: undefined, + evidence: entry_path.full_path, + version: path.version, + message: entry[ "url" ] as string ?? "", + datetime: unixEpochToISO(webkit), + timestamp_desc: "URL Visited", + artifact: "URL History", + data_type: `applications:${path.browser.toLowerCase()}:history:entry`, + browser: path.browser, + }; + + if (unfold && typeof client !== 'undefined') { + const result = client.parseUrl(history_row.url); + if (!(result instanceof UnfoldError)) { + history_row.unfold = result; + } + } + + hits.push(history_row); + } + } + + + } + + return hits; +} + +/** + * Get Chromium downloads for users + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @param browser Chromium based browser type + * @returns Array of `ChromiumDownloads` + */ +export function chromiumDownloads(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumDownloads[] { + const hits: ChromiumDownloads[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/History`; + + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\History`; + } + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + for (const entry_path of glob_paths) { + const results = querySqlite(entry_path.full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${results}`); + continue; + } + // Loop through downloads rows + for (const entry of results) { + const adjust = 1000000; + const start = webkitToUnixEpoch( + (entry[ "start_time" ] as number ?? 0) / adjust, + ); + const end = webkitToUnixEpoch( + (entry[ "end_time" ] as number ?? 0) / adjust, + ); + const access = webkitToUnixEpoch( + (entry[ "last_access_time" ] as number ?? 0) / adjust, + ); + const download_row: ChromiumDownloads = { + id: entry[ "id" ] as number ?? 0, + guid: entry[ "guid" ] as string ?? "", + current_path: entry[ "current_path" ] as string ?? "", + target_path: entry[ "target_path" ] as string ?? "", + start_time: unixEpochToISO(start), + received_bytes: entry[ "received_bytes" ] as number ?? 0, + total_bytes: entry[ "total_bytes" ] as number ?? 0, + state: entry[ "state" ] as number ?? 0, + danger_type: entry[ "danger_type" ] as number ?? 0, + interrupt_reason: entry[ "interrupt_reason" ] as number ?? 0, + hash: entry[ "hash" ] as number[] ?? [], + end_time: unixEpochToISO(end), + opened: entry[ "opened" ] as number ?? 0, + last_access_time: unixEpochToISO(access), + transient: entry[ "transient" ] as number ?? 0, + referrer: entry[ "referrer" ] as string ?? "", + site_url: entry[ "site_url" ] as string ?? "", + tab_url: entry[ "tab_url" ] as string ?? "", + tab_referrer_url: entry[ "tab_referrer_url" ] as string ?? "", + http_method: entry[ "http_method" ] as string ?? "", + by_ext_id: entry[ "by_ext_id" ] as string ?? "", + by_ext_name: entry[ "by_ext_name" ] as string ?? "", + etag: entry[ "etag" ] as string ?? "", + last_modified: entry[ "last_modified" ] as string ?? "", + mime_type: entry[ "mime_type" ] as string ?? "", + original_mime_type: entry[ "original_mime_type" ] as string ?? "", + downloads_url_chain_id: entry[ "downloads_url_chain_id" ] as number ?? 0, + chain_index: entry[ "chain_index" ] as number ?? 0, + url: entry[ "url" ] as string ?? "", + evidence: entry_path.full_path, + version: path.version, + message: `${entry[ "url" ] as string ?? ""} | ${entry[ "target_path" ] as string ?? ""}`, + datetime: unixEpochToISO(start), + timestamp_desc: "File Download Start", + artifact: "File Download", + data_type: `applications:${path.browser.toLowerCase()}:downloads:entry`, + browser: path.browser, + }; + hits.push(download_row); + } + } + } + + return hits; +} + +/** + * Get Chromium downloads for users + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @returns Array of `ChromiumCookies` + */ +export function chromiumCookies(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumCookies[] { + const hits: ChromiumCookies[] = []; + for (const path of paths) { + let full_paths = [ `${path.full_path}/*/Cookies`, `${path.full_path}/*/Network/Cookies` ]; + + if (platform === PlatformType.Windows) { + full_paths = [ `${path.full_path}\\*\\Network\\Cookies`, `${path.full_path}\\*\\Cookies` ]; + } + + + for (const full_path of full_paths) { + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + for (const entry_path of glob_paths) { + const results = querySqlite(entry_path.full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${results}`); + continue; + } + // Loop through cookies rows + const adjust_time = 1000000n; + for (const entry of results) { + const cookie_entry: ChromiumCookies = { + creation: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "creation_utc" ] as bigint) / adjust_time) + )), + host_key: entry[ "host_key" ] as string, + top_frame_site_key: entry[ "top_frame_site_key" ] as string | undefined ?? "", + name: entry[ "name" ] as string | undefined ?? "", + value: entry[ "value" ] as string | undefined ?? "", + encrypted_value: entry[ "encrypted_value" ] as string, + path: entry[ "path" ] as string | undefined ?? "", + expires: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "expires_utc" ] as bigint) / adjust_time) + )), + is_secure: !!(entry[ "is_secure" ] as number), + is_httponly: !!(entry[ "is_httponly" ] as number), + last_access: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "last_access_utc" ] as bigint) / adjust_time) + )), + has_expires: !!(entry[ "has_expires" ] as number), + is_persistent: !!(entry[ "is_persistent" ] as number), + priority: entry[ "priority" ] as number, + samesite: entry[ "samesite" ] as number, + source_scheme: entry[ "source_scheme" ] as number, + source_port: entry[ "source_port" ] as number, + is_same_party: entry[ "is_same_party" ] as number | undefined ?? 0, + last_update: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "last_update_utc" ] as bigint) / adjust_time) + )), + evidence: entry_path.full_path, + version: path.version, + message: `Cookie name: ${entry[ "name" ] as string} | value: ${entry[ "value" ] as string | undefined ?? ""}`, + datetime: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "expires_utc" ] as bigint) / adjust_time) + )), + timestamp_desc: "Cookie Expires", + artifact: "Website Cookie", + data_type: `applications:${path.browser.toLowerCase()}:cookies:entry`, + browser: path.browser, + }; + hits.push(cookie_entry); + } + } + + } + } + return hits; +} + +/** + * Get Favicons for websites + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @returns Array of `ChromiumFavicons` + */ +export function chromiumFavicons(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumFavicons[] { + const hits: ChromiumFavicons[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/Favicons`; + + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Favicons`; + } + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + for (const entry_path of glob_paths) { + const results = querySqlite(entry_path.full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${results}`); + continue; + } + // Loop through favicon rows + for (const entry of results) { + const adjust = 1000000; + const last_update = webkitToUnixEpoch( + (entry[ "last_updated" ] as number ?? 0) / adjust, + ); + let url = "Favicon URL Null"; + let page_url = "Favicon Page Null"; + let message = url; + if (typeof entry[ "url" ] === 'string') { + url = entry[ "url" ]; + message = url; + } + if (typeof entry[ "page_url" ] === 'string') { + page_url = entry[ "page_url" ]; + message = page_url; + } + + const download_row: ChromiumFavicons = { + evidence: entry_path.full_path, + version: path.version, + page_url, + message: `Favicon for ${message}`, + datetime: unixEpochToISO(last_update), + data_type: `applications:${path.browser.toLowerCase()}:favicons:entry`, + browser: path.browser, + last_update: unixEpochToISO(last_update), + url, + timestamp_desc: "Favicon Updated", + artifact: "Website Favicon" + }; + hits.push(download_row); + } + } + } + + return hits; +} + +/** + * Get Shortcuts for websites + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @returns Array of `ChromiumShortcuts` + */ +export function chromiumShortcuts(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumShortcuts[] { + const hits: ChromiumShortcuts[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/Shortcuts`; + + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Shortcuts`; + } + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + for (const entry_path of glob_paths) { + const results = querySqlite(entry_path.full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${results}`); + continue; + } + // Loop through favicon rows + for (const entry of results) { + const adjust = 1000000; + const last_update = webkitToUnixEpoch( + (entry[ "last_access_time" ] as number ?? 0) / adjust, + ); + let short_type = -1; + if (typeof entry[ "type" ] === 'number') { + short_type = entry[ "type" ]; + } + let url = "Shortcut URL Null"; + if (typeof entry[ "url" ] === 'string') { + url = entry[ "url" ]; + } + const download_row: ChromiumShortcuts = { + evidence: entry_path.full_path, + version: path.version, + message: `Shortcut for ${url}`, + datetime: unixEpochToISO(last_update), + data_type: `applications:${path.browser.toLowerCase()}:shortcuts:entry`, + browser: path.browser, + last_update: unixEpochToISO(last_update), + url, + text: entry[ "text" ] as string | undefined ?? "", + contents: entry[ "contents" ] as string | undefined ?? "", + fill_into_edit: entry[ "fill_into_edit" ] as string | undefined ?? "", + shortcut_id: entry[ "id" ] as string | undefined ?? "", + keyword: entry[ "keyword" ] as string | undefined ?? "", + shortcut_type: getShortcut(short_type), + description: entry[ "description" ] as string | undefined ?? "", + timestamp_desc: "Shortcut Last Access", + artifact: "Website Shortcut" + }; + hits.push(download_row); + } + } + } + + return hits; +} + +/** + * Get ShortcutType for websites + * @param value enum value of the shortcut + */ +function getShortcut(value: number): ShortcutType { + switch (value) { + case 0: return ShortcutType.Typed; + case 1: return ShortcutType.History; + case 2: return ShortcutType.HistoryTitle; + case 3: return ShortcutType.HistoryBody; + case 4: return ShortcutType.HistoryKeyword; + case 5: return ShortcutType.Suggest; + case 6: return ShortcutType.SearchUrl; + case 7: return ShortcutType.SearchHistory; + case 8: return ShortcutType.SearchSuggest; + case 9: return ShortcutType.SearchSuggestEntity; + case 10: return ShortcutType.SearchTail; + case 11: return ShortcutType.SearchPersonalized; + case 12: return ShortcutType.SearchProfile; + case 13: return ShortcutType.SearchEngine; + case 14: return ShortcutType.ExtensionApp; + case 15: return ShortcutType.UserContact; + case 16: return ShortcutType.Bookmark; + case 17: return ShortcutType.SuggestPersonalized; + case 18: return ShortcutType.Calculator; + case 19: return ShortcutType.Clipboard; + case 20: return ShortcutType.Voice; + case 21: return ShortcutType.Physical; + case 22: return ShortcutType.PhysicalOverflow; + case 23: return ShortcutType.Tab; + case 24: return ShortcutType.Document; + case 25: return ShortcutType.Pedal; + case 26: return ShortcutType.ClipboardText; + case 27: return ShortcutType.ClipboardImage; + case 28: return ShortcutType.TitleSuggest; + case 29: return ShortcutType.TitleNavSuggest; + case 30: return ShortcutType.OpenTab; + case 31: return ShortcutType.HistoryCluster; + case 32: return ShortcutType.Null; + case 33: return ShortcutType.Starter; + case 34: return ShortcutType.MostVisited; + case 35: return ShortcutType.Repeatable; + case 36: return ShortcutType.HistoryEmbed; + case 37: return ShortcutType.Enterprise; + case 38: return ShortcutType.HistoryEmbedAnswers; + case 39: return ShortcutType.TabGroup; + default: return ShortcutType.Unknown; + } +} + +/** + * Determine Cookie Type. From `https://chromium.googlesource.com/chromium/src/net/+/refs/heads/main/cookies/cookie_constants.h#378` + * @param source Cookie source_type column + * @returns `ChromiumCookieType` enum value + */ +export function getChromiumCookieType(source: number): ChromiumCookieType { + switch (source) { + case 0: return ChromiumCookieType.Unknown; + case 1: return ChromiumCookieType.Http; + case 2: return ChromiumCookieType.Script; + case 3: return ChromiumCookieType.Other; + default: return ChromiumCookieType.Unknown; + } +} + + +/** + * Function to extract Autofill information from database + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @returns Array of `ChromiumAutofill` + */ +export function chromiumAutofill(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumAutofill[] { + const hits: ChromiumAutofill[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/*/Web Data`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Web Data`; + } + + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + + for (const entry_path of glob_paths) { + const results = querySqlite(entry_path.full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${results}`); + continue; + } + + for (const entry of results) { + const fill_entry: ChromiumAutofill = { + evidence: entry_path.full_path, + date_created: unixEpochToISO(entry[ "date_created" ] as number), + date_last_used: unixEpochToISO(entry[ "date_last_used" ] as number), + count: entry[ "count" ] as number, + name: entry[ "name" ] as string ?? "", + value: entry[ "value" ] as string ?? "", + value_lower: entry[ "value_lower" ] as string ?? "", + version: path.version, + message: `Autofill name: ${entry[ "name" ] as string ?? ""} | value: ${entry[ "value" ] as string ?? ""}`, + datetime: unixEpochToISO(entry[ "date_created" ] as number), + timestamp_desc: "Autofill Created", + artifact: "Website Autofill", + data_type: `applications:${path.browser.toLowerCase()}:autofill:entry`, + browser: path.browser, + }; + hits.push(fill_entry); + } + } + } + return hits; +} + +/** + * Function to extract Autofill information from database + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @returns Array of `ChromiumLogins` + */ +export function chromiumLogins(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumLogins[] { + const hits: ChromiumLogins[] = []; + const adjust_time = 1000000n; + + for (const path of paths) { + let full_path = `${path.full_path}/*/Login Data`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\Login Data`; + } + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + + for (const entry_path of glob_paths) { + const data = querySqlite(entry_path.full_path, query); + if (data instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${data}`); + continue; + } + // Loop through logins rows + for (const entry of data) { + const login_entry: ChromiumLogins = { + origin_url: entry[ "origin_url" ] as string, + signon_realm: entry[ "signon_realm" ] as string, + date_created: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "date_created" ] as bigint) / adjust_time) + )), + blacklisted_by_user: entry[ "blacklisted_by_user" ] as number, + scheme: entry[ "scheme" ] as number, + id: entry[ "id" ] as number, + date_last_used: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "date_last_used" ] as bigint) / adjust_time) + )), + date_password_modified: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "date_password_modified" ] as bigint) / adjust_time) + )), + sharing_notification_display: entry[ "sharing_notification_display" ] as number, + evidence: entry_path.full_path, + action_url: entry[ "action_url" ] as string | undefined, + username_element: entry[ "username_element" ] as string | undefined, + username_value: entry[ "username_value" ] as string | undefined, + times_used: entry[ "times_used" ] as number | undefined, + icon_url: entry[ "icon_url" ] as string | undefined, + possible_username_pairs: entry[ "possible_username_pairs" ] as string | + undefined, + federation_url: entry[ "federation_url" ] as string | undefined, + generation_upload_status: entry[ "generation_upload_status" ] as number | + undefined, + sender_profile_image_url: entry[ "sender_profile_image_url" ] as string | + undefined, + password_element: entry[ "password_element" ] as string | undefined, + password_type: entry[ "password_type" ] as number | undefined, + password_value: entry[ "password_value" ] as string | undefined, + date_received: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "date_received" ] === "undefined" || entry[ "date_received" ] === null + ? 0 + : Number(BigInt(entry[ "date_received" ] as bigint) / adjust_time) + )), + sender_email: entry[ "sender_email" ] as string | undefined, + sender_name: entry[ "sender_name" ] as string | undefined, + skip_zero_click: entry[ "skip_zero_click" ] as number | undefined, + submit_element: entry[ "submit_element" ] as string | undefined, + display_name: entry[ "display_name" ] as string | undefined, + form_data: entry[ "form_data" ] as string | undefined, + moving_blocked_for: entry[ "moving_blocked_for" ] as string | undefined, + keychain_identifier: entry[ "keychain_identifier" ] as string | undefined, + version: path.version, + message: entry[ "origin_url" ] as string, + datetime: unixEpochToISO(webkitToUnixEpoch( + Number(BigInt(entry[ "date_last_used" ] as bigint) / adjust_time) + )), + timestamp_desc: "Last Login", + artifact: "Website Login", + data_type: `applications:${path.browser.toLowerCase()}:login:entry`, + browser: path.browser, + }; + hits.push(login_entry); + } + } + } + return hits; +} + +/** + * Function to extract Detect Incidental Party State (DIPS) information from database + * @param paths Array of `ChromiumProfiles` + * @param platform OS `PlatformType` + * @param query SQL query to run + * @returns Array of `ChromiumDips` + */ +export function chromiumDips(paths: ChromiumProfiles[], platform: PlatformType, query: string): ChromiumDips[] { + const hits: ChromiumDips[] = []; + const adjust_time = 1000000n; + + for (const path of paths) { + let full_path = `${path.full_path}/*/DIPS`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\*\\DIPS`; + } + + const glob_paths = glob(full_path); + if (glob_paths instanceof FileError) { + console.warn(`Failed to glob ${full_path}: ${glob_paths}`); + continue; + } + + for (const entry_path of glob_paths) { + const data = querySqlite(entry_path.full_path, query); + if (data instanceof ApplicationError) { + console.warn(`Failed to query ${entry_path.full_path}: ${data}`); + continue; + } + // Loop through logins rows + for (const entry of data) { + const dips_entry: ChromiumDips = { + site: entry[ "site" ] as string, + evidence: entry_path.full_path, + first_bounce: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "first_bounce_time" ] === "undefined" || + entry[ "first_bounce_time" ] === null + ? 0 + : Number(BigInt(entry[ "first_bounce_time" ] as bigint) / adjust_time) + )), + last_bounce: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "last_bounce_time" ] === "undefined" || + entry[ "last_bounce_time" ] === null + ? 0 + : Number(BigInt(entry[ "last_bounce_time" ] as bigint) / adjust_time) + )), + first_site_storage: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "first_site_storage_time" ] === "undefined" || + entry[ "first_site_storage_time" ] === null + ? 0 + : Number( + BigInt(entry[ "first_site_storage_time" ] as bigint) / adjust_time + ) + )), + first_stateful_bounce: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "first_stateful_bounce_time" ] === "undefined" || + entry[ "first_stateful_bounce_time" ] === null + ? 0 + : Number( + BigInt(entry[ "first_stateful_bounce_time" ] as bigint) / adjust_time + ) + )), + first_user_interaction: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "first_user_interaction_time" ] === "undefined" || + entry[ "first_user_interaction_time" ] === null + ? 0 + : Number( + BigInt(entry[ "first_user_interaction_time" ] as bigint) / + adjust_time + ) + )), + first_web_authn_assertion: unixEpochToISO(webkitToUnixEpoch( + entry[ "first_web_authn_assertion_time" ] === null ? 0 : Number( + BigInt(entry[ "first_web_authn_assertion_time" ] as bigint) / + adjust_time + ) + )), + last_site_storage: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "last_site_storage_time" ] === "undefined" || + entry[ "last_site_storage_time" ] === null + ? 0 + : Number( + BigInt(entry[ "last_site_storage_time" ] as bigint) / adjust_time + ) + )), + last_stateful_bounce: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "last_stateful_bounce_time" ] === "undefined" || + entry[ "last_stateful_bounce_time" ] === null + ? 0 + : Number( + BigInt(entry[ "last_stateful_bounce_time" ] as bigint) / adjust_time + ) + )), + last_user_interaction: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "last_user_interaction_time" ] === "undefined" || + entry[ "last_user_interaction_time" ] === null + ? 0 + : Number( + BigInt(entry[ "last_user_interaction_time" ] as bigint) / adjust_time + ) + )), + last_web_authn_assertion: unixEpochToISO(webkitToUnixEpoch( + entry[ "last_web_authn_assertion_time" ] === null ? 0 : Number( + BigInt(entry[ "last_web_authn_assertion_time" ] as bigint) / + adjust_time + ) + )), + version: path.version, + message: entry[ "site" ] as string, + datetime: unixEpochToISO(webkitToUnixEpoch( + typeof entry[ "first_site_storage_time" ] === "undefined" || + entry[ "first_site_storage_time" ] === null + ? 0 + : Number( + BigInt(entry[ "first_site_storage_time" ] as bigint) / + adjust_time + ) + )), + timestamp_desc: "First Interaction", + artifact: "Browser DIPS", + data_type: `applications:${path.browser.toLowerCase()}:dips:entry`, + browser: path.browser, + }; + hits.push(dips_entry); + } + } + } + return hits; +} + +/** + * Function to test the Chromium sqlite file parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Chromium sqlite parsing + */ +export function testChromiumSqlite(): void { + const path: ChromiumProfiles = { + full_path: "../../test_data/edge", + version: "141", + browser: BrowserType.EDGE + }; + let query = `SELECT + urls.id AS id, + urls.url AS url, + title, + visit_count, + typed_count, + last_visit_time, + hidden, + visits.id AS visits_id, + from_visit, + transition, + segment_id, + visit_duration, + opener_visit + FROM + urls + JOIN visits ON urls.id = visits.url LIMIT 60 OFFSET 0`; + + const hist = chromiumHistory([ path ], PlatformType.Darwin, true, query); + if (hist.length !== 56) { + throw `Got length ${hist.length} expected 56.......chromiumHistory ❌`; + } + + if (hist[ 23 ]?.url !== "https://www.washingtonpost.com/latest-headlines/") { + throw `Got url ${hist[ 23 ]?.url} expected "https://www.washingtonpost.com/latest-headlines/".......chromiumHistory ❌`; + } + + console.info(` Function chromiumHistory ✅`); + + query = `SELECT + downloads.id AS downloads_id, + guid, + current_path, + target_path, + start_time, + received_bytes, + total_bytes, + state, + danger_type, + interrupt_reason, + hash, + end_time, + opened, + last_access_time, + transient, + referrer, + site_url, + tab_url, + tab_referrer_url, + http_method, + by_ext_id, + by_ext_name, + etag, + last_modified, + mime_type, + original_mime_type, + downloads_url_chains.id AS downloads_url_chain_id, + chain_index, + url + FROM + downloads + JOIN downloads_url_chains ON downloads_url_chains.id = downloads.id LIMIT 20 OFFSET 0`; + const downs = chromiumDownloads([ path ], PlatformType.Darwin, query); + if (downs.length !== 6) { + throw `Got length ${downs.length} expected 6.......chromiumDownloads ❌`; + } + + if (downs[ 4 ]?.url !== "https://github.com/Velocidex/velociraptor/releases/download/v0.75/velociraptor-v0.75.1-windows-amd64.exe") { + throw `Got url ${downs[ 4 ]?.url} expected "https://github.com/Velocidex/velociraptor/releases/download/v0.75/velociraptor-v0.75.1-windows-amd64.exe".......chromiumDownloads ❌`; + } + + console.info(` Function chromiumDownloads ✅`); + + query = `SELECT * FROM cookies`; + const cook = chromiumCookies([ path ], PlatformType.Darwin, query); + if (cook.length !== 251) { + throw `Got length ${cook.length} expected 251.......chromiumCookies ❌`; + } + + if (cook[ 4 ]?.message !== "Cookie name: __rubyUX | value: ") { + throw `Got url ${cook[ 4 ]?.message} expected "Cookie name: __rubyUX | value: ".......chromiumCookies ❌`; + } + + console.info(` Function chromiumCookies ✅`); + + query = `SSELECT name, value, date_created, date_last_used, count, value_lower from autofill`; + const fill = chromiumAutofill([ path ], PlatformType.Darwin, query); + if (fill.length !== 0) { + throw `Got length ${fill.length} expected 0.......chromiumAutofill ❌`; + } + + console.info(` Function chromiumAutofill ✅`); + + query = `SELECT * FROM logins`; + const log = chromiumLogins([ path ], PlatformType.Darwin, query); + if (log.length !== 0) { + throw `Got length ${log.length} expected 0.......chromiumLogins ❌`; + } + + console.info(` Function chromiumLogins ✅`); + + query = `SELECT * FROM bounces`; + const dip = chromiumDips([ path ], PlatformType.Darwin, query); + if (dip.length !== 33) { + throw `Got length ${dip.length} expected 33.......chromiumDips ❌`; + } + + console.info(` Function chromiumDips ✅`); + + query = `SELECT url, last_updated FROM favicons JOIN favicon_bitmaps ON favicons.id = favicon_bitmaps.id`; + const favi = chromiumFavicons([ path ], PlatformType.Darwin, query); + if (favi.length !== 60) { + throw `Got length ${favi.length} expected 60.......chromiumFavicons ❌`; + } + if (favi[ 0 ]?.message !== "Favicon for chrome-extension://lmijmgnfconjockjeepmlmkkibfgjmla/pages/shared/resources/images/icons/extension/favicon.ico") { + throw `Got url ${favi[ 0 ]?.message} expected wanted "Favicon for chrome-extension://lmijmgnfconjockjeepmlmkkibfgjmla/pages/shared/resources/images/icons/extension/favicon.ico".......chromiumFavicons ❌`; + } + + console.info(` Function chromiumFavicons ✅`); + + query = `SELECT id,text,fill_into_edit,url,contents,description,type,keyword,last_access_time FROM omni_box_shortcuts`; + const short = chromiumShortcuts([ path ], PlatformType.Darwin, query); + if (short.length !== 98) { + throw `Got length ${short.length} expected 60.......chromiumShortcuts ❌`; + } + if (short[ 17 ]?.message !== "Shortcut for https://www.bing.com/search?q=sharphound+github&FORM=ANAB01&PC=U531") { + throw `Got url ${short[ 17 ]?.message} expected wanted "Shortcut for https://www.bing.com/search?q=sharphound+github&FORM=ANAB01&PC=U531".......chromiumShortcuts ❌`; + } + + console.info(` Function chromiumShortcuts ✅`); } \ No newline at end of file diff --git a/src/applications/errors.ts b/src/applications/errors.ts index 761f8f88..e7b3d7c9 100644 --- a/src/applications/errors.ts +++ b/src/applications/errors.ts @@ -17,6 +17,7 @@ export type ErrorName = | "ANYDESK" | "COMET" | "BRAVE" - | "SYNCTHING"; + | "SYNCTHING" + | "RUSTDESK"; export class ApplicationError extends ErrorBase { } diff --git a/src/applications/firefox/addons.ts b/src/applications/firefox/addons.ts deleted file mode 100644 index 9b97464d..00000000 --- a/src/applications/firefox/addons.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { FirefoxAddons, FirefoxProfiles } from "../../../types/applications/firefox"; -import { FileError } from "../../filesystem/errors"; -import { readTextFile } from "../../filesystem/files"; -import { PlatformType } from "../../system/systeminfo"; -import { unixEpochToISO } from "../../time/conversion"; - -/** - * Get installed Firefox addons - * @param paths Array of `FirefoxProfiles` - * @param platform Platform to parse Firefox addons - * @returns Array of `FirefoxAddons` or `ApplicationError` - */ -export function firefoxAddons( - paths: FirefoxProfiles[], - platform: PlatformType, -): FirefoxAddons[] { - - const extensions: FirefoxAddons[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/extensions.json`; - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\extensions.json`; - } - - const extension = readTextFile(full_path); - if (extension instanceof FileError) { - console.warn(`failed to read file ${full_path}: ${extension}`); - continue; - } - - const data = JSON.parse(extension)["addons"]; - for (const entry of data) { - const value: FirefoxAddons = { - installed: unixEpochToISO(entry["installDate"] ?? 0), - updated: unixEpochToISO(entry["updateDate"] ?? 0), - active: entry["active"] ?? false, - visible: entry["visible"] ?? false, - author: entry["id"] ?? "", - addon_version: entry["version"] ?? "", - path: entry["path"] ?? "", - db_path: full_path, - message: `Addon ${entry["defaultLocale"]["name"] ?? ""} installed`, - datetime: unixEpochToISO(entry["installDate"] ?? 0), - name: entry["defaultLocale"]["name"] ?? "", - description: entry["defaultLocale"]["description"] ?? "", - creator: entry["defaultLocale"]["creator"] ?? "", - timestamp_desc: "Extension Installed", - artifact: "Browser Extension", - data_type: "application:firefox:extension:entry", - version: path.version, - }; - extensions.push(value); - } - } - - return extensions; -} \ No newline at end of file diff --git a/src/applications/firefox/fox.ts b/src/applications/firefox/fox.ts index a06729b6..93510d98 100644 --- a/src/applications/firefox/fox.ts +++ b/src/applications/firefox/fox.ts @@ -1,310 +1,338 @@ -import { FirefoxAddons, FirefoxCookies, FirefoxDownloads, FirefoxFavicons, FirefoxFormHistory, FirefoxHistory, FirefoxProfiles, FirefoxStorage } from "../../../types/applications/firefox"; -import { GlobInfo } from "../../../types/filesystem/globs"; -import { getEnvValue } from "../../environment/env"; -import { FileError } from "../../filesystem/errors"; -import { glob, readTextFile } from "../../filesystem/files"; -import { SystemError } from "../../system/error"; -import { dumpData, Output } from "../../system/output"; -import { PlatformType } from "../../system/systeminfo"; -import { ApplicationError } from "../errors"; -import { firefoxAddons } from "./addons"; -import { firefoxCookies, firefoxDownloads, firefoxFavicons, firefoxFormhistory, firefoxHistory, firefoxStorage } from "./sqlite"; - -/** - * Class to extract Firefox information - */ -export class FireFox { - private paths: FirefoxProfiles[] = []; - private platform: PlatformType; - private unfold: boolean; - - /** - * Construct a `FireFox` object that can be used to parse browser data - * @param platform OS `PlatformType` - * @param unfold Attempt to parse URLs. Default is `false` - * @param alt_path Optional alternative path to directory contain FireFox data - * @returns `FireFox` instance class - */ - constructor(platform: PlatformType, unfold = false, alt_path?: string) { - this.platform = platform; - this.unfold = unfold; - if (alt_path === undefined) { - const results = this.profiles(platform); - if (results instanceof ApplicationError) { - return; - } - this.paths = results; - return; - } - const fox_version = this.version(this.platform, alt_path); - if (fox_version instanceof ApplicationError) { - return; - } - - this.paths = [{ - full_path: alt_path, - version: fox_version - }]; - } - - /** - * Extract FireFox history - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `FirefoxHistory` - */ - public history(offset = 0, limit = 100): FirefoxHistory[] { - return firefoxHistory(this.paths, this.platform, this.unfold, offset, limit); - } - - /** - * Extract FireFox cookies - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `FirefoxCookies` - */ - public cookies(offset = 0, limit = 100): FirefoxCookies[] { - return firefoxCookies(this.paths, this.platform, offset, limit); - } - - /** - * Extract FireFox downloads - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `FirefoxDownloads` - */ - public downloads(offset = 0, limit = 100): FirefoxDownloads[] { - return firefoxDownloads(this.paths, this.platform, offset, limit); - } - - /** - * Extract FireFox addons - * @returns Array of `JSON` objects - */ - public addons(): FirefoxAddons[] { - return firefoxAddons(this.paths, this.platform); - } - - /** - * Function to extract entries from `storage.sqlite` - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `FirefoxStorage` - */ - public storage(offset = 0, limit = 100): FirefoxStorage[] { - return firefoxStorage(this.paths, this.platform, offset, limit); - } - - /** - * Function to extract favicon entries - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `FirefoxFavicons` - */ - public favicons(offset = 0, limit = 100): FirefoxFavicons[] { - return firefoxFavicons(this.paths, this.platform, offset, limit); - } - - /** - * Function to extract form history entries - * @param [offset=0] Starting db offset. Default is zero - * @param [limit=100] How many records to return. Default is 100 - * @returns Array of `FirefoxFormHistory` - */ - public formHistory(offset = 0, limit = 100): FirefoxFormHistory[] { - return firefoxFormhistory(this.paths, this.platform, offset, limit); - } - - /** - * Function to timeline all Firefox artifacts. Similar to [Hindsight](https://github.com/obsidianforensics/hindsight) - * @param output `Output` structure object. Format type should be either `JSON` or `JSONL`. `JSONL` is recommended - */ - public retrospect(output: Output): void { - let offset = 0; - const limit = 100; - - while (true) { - const entries = this.history(offset, limit); - if (entries.length === 0) { - break; - } - if (!this.unfold) { - entries.forEach(x => delete x["unfold"]); - } - const status = dumpData(entries, `retrospect_firefox_history`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline firefox history: ${status}`); - } - offset += limit; - } - - offset = 0; - - while (true) { - const entries = this.cookies(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_firefox_cookies`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline firefox cookies: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.favicons(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_firefox_favicons`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline firefox favicons: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.storage(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_firefox_storage`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline firefox storage: ${status}`); - } - offset += limit; - } - - offset = 0; - while (true) { - const entries = this.formHistory(offset, limit); - if (entries.length === 0) { - break; - } - const status = dumpData(entries, `retrospect_firefox_formhistory`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline firefox form history: ${status}`); - } - offset += limit; - } - - const ext = this.addons(); - const status = dumpData(ext, `retrospect_firefox_extensions`, output); - if (status instanceof SystemError) { - console.error(`Failed timeline firefox extensions: ${status}`); - } - } - - /** - * Get base path for all FireFox data - * @param platform OS `PlatformType` - * @returns Array of `FirefoxProfiles` or `ApplicationError` - */ - private profiles(platform: PlatformType): FirefoxProfiles[] | ApplicationError { - let paths: GlobInfo[] = []; - switch (platform) { - case PlatformType.Darwin: { - const mac_paths = glob( - `/Users/*/Library/Application Support/Firefox/Profiles/*/`, - ); - if (mac_paths instanceof FileError) { - return new ApplicationError( - "FIREFOX", - `failed to glob macos paths: ${mac_paths}`, - ); - } - paths = mac_paths; - break; - } - case PlatformType.Windows: { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - const win_paths = glob( - `${drive}\\Users\\*\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\*\\`, - ); - if (win_paths instanceof FileError) { - return new ApplicationError( - "FIREFOX", - `failed to glob windows paths: ${win_paths}`, - ); - } - paths = win_paths; - break; - } - case PlatformType.Linux: { - // FireFox can now exist in two possible locations. Newer versions are under .config - const config_paths = [`/home/*/.mozilla/firefox/*/`, `/home/*/.config/mozilla/firefox/*/`]; - for (const entry of config_paths) { - const linux_paths = glob(entry); - if (linux_paths instanceof FileError) { - continue; - } - paths = paths.concat(linux_paths); - } - break; - } - default: { - return []; - } - } - - const firefox_profiles: FirefoxProfiles[] = []; - for (const entry of paths) { - if (!entry.is_directory) { - continue; - } - const fox_version = this.version(this.platform, entry.full_path); - if (fox_version instanceof ApplicationError) { - continue; - } - - const profile: FirefoxProfiles = { - full_path: entry.full_path, - version: fox_version, - }; - - firefox_profiles.push(profile); - } - return firefox_profiles; - } - - /** - * Function to determine FireFox version - * @param platform OS `PlatformType` - * @param path Path to base FireFox user profile - * @returns Version number or `ApplicationError` - */ - private version(platform: PlatformType, path: string): string | ApplicationError { - let version_path = `${path}/prefs.js`; - if (platform === PlatformType.Windows) { - version_path = `${path}\\prefs.js`; - } - const text_data = readTextFile(version_path); - if (text_data instanceof FileError) { - return new ApplicationError(`FIREFOX`, `could not read ${version_path}: ${text_data}`); - } - - const version_regex = /user_pref\("extensions.lastAppVersion".*/; - const match = text_data.match(version_regex); - if (match === null) { - return new ApplicationError(`FIREFOX`, `could not match version regex`); - } - const version_line = match?.at(0); - if (version_line === undefined) { - return new ApplicationError(`FIREFOX`, `could not find version line got undefined`); - } - - const version_string = version_line.split(",").at(1); - if (version_string === undefined) { - return new ApplicationError(`FIREFOX`, `could not find version got undefined`); - } - - const version_value = version_string.replace(" ", "").replaceAll('"', "").replace(")", "").replace(";", ""); - return version_value; - } +import { FirefoxAddons, FirefoxBookmark, FirefoxCookies, FirefoxDownloads, FirefoxFavicons, FirefoxFormHistory, FirefoxHistory, FirefoxProfiles, FirefoxSession, FirefoxStorage } from "../../../types/applications/firefox"; +import { GlobInfo } from "../../../types/filesystem/globs"; +import { getEnvValue } from "../../environment/env"; +import { FileError } from "../../filesystem/errors"; +import { glob, readTextFile } from "../../filesystem/files"; +import { SystemError } from "../../system/error"; +import { dumpData, Output } from "../../system/output"; +import { PlatformType } from "../../system/systeminfo"; +import { ApplicationError } from "../errors"; +import { firefoxAddons, firefoxBookmark, firefoxSessions } from "./json"; +import { firefoxCookies, firefoxDownloads, firefoxFavicons, firefoxFormhistory, firefoxHistory, firefoxStorage } from "./sqlite"; + +/** + * Class to extract Firefox information + */ +export class FireFox { + private paths: FirefoxProfiles[] = []; + private platform: PlatformType; + private unfold: boolean; + + /** + * Construct a `FireFox` object that can be used to parse browser data + * @param platform OS `PlatformType` + * @param unfold Attempt to parse URLs. Default is `false` + * @param alt_path Optional alternative path to directory contain FireFox data + * @returns `FireFox` instance class + */ + constructor (platform: PlatformType, unfold = false, alt_path?: string) { + this.platform = platform; + this.unfold = unfold; + if (alt_path === undefined) { + const results = this.profiles(platform); + if (results instanceof ApplicationError) { + return; + } + this.paths = results; + return; + } + const fox_version = this.version(this.platform, alt_path); + if (fox_version instanceof ApplicationError) { + return; + } + + this.paths = [ { + full_path: alt_path, + version: fox_version + } ]; + } + + /** + * Extract FireFox history + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `FirefoxHistory` + */ + public history(offset = 0, limit = 100): FirefoxHistory[] { + return firefoxHistory(this.paths, this.platform, this.unfold, offset, limit); + } + + /** + * Extract FireFox cookies + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `FirefoxCookies` + */ + public cookies(offset = 0, limit = 100): FirefoxCookies[] { + return firefoxCookies(this.paths, this.platform, offset, limit); + } + + /** + * Extract FireFox downloads + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `FirefoxDownloads` + */ + public downloads(offset = 0, limit = 100): FirefoxDownloads[] { + return firefoxDownloads(this.paths, this.platform, offset, limit); + } + + /** + * Extract FireFox addons + * @returns Array of `JSON` objects + */ + public addons(): FirefoxAddons[] { + return firefoxAddons(this.paths, this.platform); + } + + /** + * Function to extract entries from `storage.sqlite` + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `FirefoxStorage` + */ + public storage(offset = 0, limit = 100): FirefoxStorage[] { + return firefoxStorage(this.paths, this.platform, offset, limit); + } + + /** + * Function to extract bookmarks + * @returns Array of `FirefoxBookmark` + */ + public bookmarks(): FirefoxBookmark[] { + return firefoxBookmark(this.paths, this.platform); + } + + /** + * Function to extract sessions + * @returns Array of `FirefoxBookmark` + */ + public sessions(): FirefoxSession[] { + return firefoxSessions(this.paths, this.platform); + } + + /** + * Function to extract favicon entries + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `FirefoxFavicons` + */ + public favicons(offset = 0, limit = 100): FirefoxFavicons[] { + return firefoxFavicons(this.paths, this.platform, offset, limit); + } + + /** + * Function to extract form history entries + * @param [offset=0] Starting db offset. Default is zero + * @param [limit=100] How many records to return. Default is 100 + * @returns Array of `FirefoxFormHistory` + */ + public formHistory(offset = 0, limit = 100): FirefoxFormHistory[] { + return firefoxFormhistory(this.paths, this.platform, offset, limit); + } + + /** + * Function to timeline all Firefox artifacts. Similar to [Hindsight](https://github.com/obsidianforensics/hindsight) + * @param output `Output` structure object. Format type should be either `JSON` or `JSONL`. `JSONL` is recommended + */ + public retrospect(output: Output): void { + let offset = 0; + const limit = 100; + + while (true) { + const entries = this.history(offset, limit); + if (entries.length === 0) { + break; + } + if (!this.unfold) { + entries.forEach(x => delete x[ "unfold" ]); + } + const status = dumpData(entries, `retrospect_firefox_history`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox history: ${status}`); + } + offset += limit; + } + + offset = 0; + + while (true) { + const entries = this.cookies(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_firefox_cookies`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox cookies: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.favicons(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_firefox_favicons`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox favicons: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.storage(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_firefox_storage`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox storage: ${status}`); + } + offset += limit; + } + + offset = 0; + while (true) { + const entries = this.formHistory(offset, limit); + if (entries.length === 0) { + break; + } + const status = dumpData(entries, `retrospect_firefox_formhistory`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox form history: ${status}`); + } + offset += limit; + } + + const ext = this.addons(); + let status = dumpData(ext, `retrospect_firefox_extensions`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox extensions: ${status}`); + } + + const books = this.bookmarks(); + status = dumpData(books, `retrospect_firefox_bookmarks`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox bookmarks: ${status}`); + } + + const sess = this.sessions(); + status = dumpData(sess, `retrospect_firefox_sessions`, output); + if (status instanceof SystemError) { + console.error(`Failed timeline firefox sessions: ${status}`); + } + } + + /** + * Get base path for all FireFox data + * @param platform OS `PlatformType` + * @returns Array of `FirefoxProfiles` or `ApplicationError` + */ + private profiles(platform: PlatformType): FirefoxProfiles[] | ApplicationError { + let paths: GlobInfo[] = []; + switch (platform) { + case PlatformType.Darwin: { + const mac_paths = glob( + `/Users/*/Library/Application Support/Firefox/Profiles/*/`, + ); + if (mac_paths instanceof FileError) { + return new ApplicationError( + "FIREFOX", + `failed to glob macos paths: ${mac_paths}`, + ); + } + paths = mac_paths; + break; + } + case PlatformType.Windows: { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + const win_paths = glob( + `${drive}\\Users\\*\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\*\\`, + ); + if (win_paths instanceof FileError) { + return new ApplicationError( + "FIREFOX", + `failed to glob windows paths: ${win_paths}`, + ); + } + paths = win_paths; + break; + } + case PlatformType.Linux: { + // FireFox can now exist in two possible locations. Newer versions are under .config + const config_paths = [ `/home/*/.mozilla/firefox/*/`, `/home/*/.config/mozilla/firefox/*/` ]; + for (const entry of config_paths) { + const linux_paths = glob(entry); + if (linux_paths instanceof FileError) { + continue; + } + paths = paths.concat(linux_paths); + } + break; + } + default: { + return []; + } + } + + const firefox_profiles: FirefoxProfiles[] = []; + for (const entry of paths) { + if (!entry.is_directory) { + continue; + } + const fox_version = this.version(this.platform, entry.full_path); + if (fox_version instanceof ApplicationError) { + continue; + } + + const profile: FirefoxProfiles = { + full_path: entry.full_path, + version: fox_version, + }; + + firefox_profiles.push(profile); + } + return firefox_profiles; + } + + /** + * Function to determine FireFox version + * @param platform OS `PlatformType` + * @param path Path to base FireFox user profile + * @returns Version number or `ApplicationError` + */ + private version(platform: PlatformType, path: string): string | ApplicationError { + let version_path = `${path}/prefs.js`; + if (platform === PlatformType.Windows) { + version_path = `${path}\\prefs.js`; + } + const text_data = readTextFile(version_path); + if (text_data instanceof FileError) { + return new ApplicationError(`FIREFOX`, `could not read ${version_path}: ${text_data}`); + } + + const version_regex = /user_pref\("extensions.lastAppVersion".*/; + const match = text_data.match(version_regex); + if (match === null) { + return new ApplicationError(`FIREFOX`, `could not match version regex`); + } + const version_line = match?.at(0); + if (version_line === undefined) { + return new ApplicationError(`FIREFOX`, `could not find version line got undefined`); + } + + const version_string = version_line.split(",").at(1); + if (version_string === undefined) { + return new ApplicationError(`FIREFOX`, `could not find version got undefined`); + } + + const version_value = version_string.replace(" ", "").replaceAll('"', "").replace(")", "").replace(";", ""); + return version_value; + } } \ No newline at end of file diff --git a/src/applications/firefox/json.ts b/src/applications/firefox/json.ts new file mode 100644 index 00000000..9f42072f --- /dev/null +++ b/src/applications/firefox/json.ts @@ -0,0 +1,334 @@ +import { PlatformType } from "../../system/systeminfo"; +import { FirefoxBookmark, FirefoxBookmarkRaw, FirefoxProfiles, FirefoxAddons, FirefoxSession, FirefoxSessionRaw } from "../../../types/applications/firefox"; +import { glob, readFile, readTextFile } from "../../filesystem/files"; +import { FileError } from "../../filesystem/errors"; +import { Endian, nomUnsignedEightBytes, nomUnsignedFourBytes } from "../../nom/helpers"; +import { NomError } from "../../nom/error"; +import { decompress_lz4 } from "../../compression/decompress"; +import { CompressionError } from "../../compression/errors"; +import { extractUtf8String } from "../../encoding/strings"; +import { unixEpochToISO } from "../../time/conversion"; + +/** + * Get Firefox bookmarks + * @param paths Array of `FirefoxProfiles` + * @param platform Platform to parse Firefox bookmarks + * @returns Array of `FirefoxBookmark` + */ +export function firefoxBookmark(paths: FirefoxProfiles[], platform: PlatformType): FirefoxBookmark[] { + let bookmark: FirefoxBookmark[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/bookmarkbackups/bookmarks*`; + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\bookmarkbackups\\bookmarks*`; + } + + const book_files = glob(full_path); + if (book_files instanceof FileError) { + continue; + } + + for (const entry of book_files) { + if (!entry.is_file) { + continue; + } + const bytes = readFile(entry.full_path); + if (bytes instanceof FileError) { + continue; + } + const decom_bytes = parseCompression(bytes); + if (decom_bytes instanceof NomError || decom_bytes instanceof CompressionError) { + continue; + } + const text = extractUtf8String(decom_bytes); + const values = extractBookmark(text, path.version, path.full_path, entry.full_path); + bookmark = bookmark.concat(values); + } + } + return bookmark; +} + +/** + * Function to decompress lz4 compressed data + * @param bytes Bytes associated with compressed data + * @returns Decompressed bytes or `NomError` or `CompressionError` + */ +function parseCompression(bytes: Uint8Array): Uint8Array | NomError | CompressionError { + const remaining = nomUnsignedEightBytes(bytes, Endian.Le); + if (remaining instanceof NomError) { + return remaining; + } + + const decom_size = nomUnsignedFourBytes(remaining.remaining, Endian.Le); + if (decom_size instanceof NomError) { + return decom_size; + } + + const decom_bytes = decompress_lz4(decom_size.remaining, decom_size.value, new Uint8Array()); + if (decom_bytes instanceof CompressionError) { + return decom_bytes; + } + + return decom_bytes; +} + +/** + * Function to extract bookmark info + * @param data Bookmark JSON text + * @param version Firefox version + * @param path Path to Firefox profile + * @param evidence Path to bookmark file + * @returns Array of `FirefoxBookmark` + */ +function extractBookmark(data: string, version: string, path: string, evidence: string): FirefoxBookmark[] { + const book = JSON.parse(data) as FirefoxBookmarkRaw; + let values: FirefoxBookmark[] = []; + if (Array.isArray(book.children)) { + values = extractChildren(book.children, version, path, evidence); + } + + return values; +} + +/** + * Function to extract bookmark children info + * @param data Bookmark JSON text + * @param version Firefox version + * @param path Path to Firefox profile + * @param evidence Path to bookmark file + * @returns Array of `FirefoxBookmark` + */ +function extractChildren(data: FirefoxBookmarkRaw[], version: string, path: string, evidence: string): FirefoxBookmark[] { + let values: FirefoxBookmark[] = []; + for (const child of data) { + if (child.typeCode === 1) { + const value: FirefoxBookmark = { + timestamp_desc: "Bookmark Created", + artifact: "Browser Bookmark", + data_type: "application:firefox:bookmark:entry", + datetime: unixEpochToISO(child.dateAdded ?? 0), + message: `Bookmark for '${child.uri ?? "Unknown"}'`, + version, + path, + evidence, + added: unixEpochToISO(child.dateAdded ?? 0), + last_modified: unixEpochToISO(child.lastModified ?? 0), + title: child.title, + id: child.id, + guid: child.guid, + icon: child.iconUri ?? "Unknown", + uri: child.uri ?? "Unknown" + }; + values.push(value); + } + + + if (Array.isArray(child.children)) { + const childs = extractChildren(child.children, version, path, evidence); + values = values.concat(childs); + } + } + + return values; +} + + +/** + * Get installed Firefox addons + * @param paths Array of `FirefoxProfiles` + * @param platform Platform to parse Firefox addons + * @returns Array of `FirefoxAddons` + */ +export function firefoxAddons( + paths: FirefoxProfiles[], + platform: PlatformType, +): FirefoxAddons[] { + const extensions: FirefoxAddons[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/extensions.json`; + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\extensions.json`; + } + + const extension = readTextFile(full_path); + if (extension instanceof FileError) { + console.warn(`failed to read file ${full_path}: ${extension}`); + continue; + } + + const data = JSON.parse(extension)[ "addons" ]; + for (const entry of data) { + const value: FirefoxAddons = { + installed: unixEpochToISO(entry[ "installDate" ] ?? 0), + updated: unixEpochToISO(entry[ "updateDate" ] ?? 0), + active: entry[ "active" ] ?? false, + visible: entry[ "visible" ] ?? false, + author: entry[ "id" ] ?? "", + addon_version: entry[ "version" ] ?? "", + path: entry[ "path" ] ?? "", + evidence: full_path, + message: `Addon ${entry[ "defaultLocale" ][ "name" ] ?? ""} installed`, + datetime: unixEpochToISO(entry[ "installDate" ] ?? 0), + name: entry[ "defaultLocale" ][ "name" ] ?? "", + description: entry[ "defaultLocale" ][ "description" ] ?? "", + creator: entry[ "defaultLocale" ][ "creator" ] ?? "", + timestamp_desc: "Extension Installed", + artifact: "Browser Extension", + data_type: "application:firefox:extension:entry", + version: path.version, + }; + extensions.push(value); + } + } + + return extensions; +} + +/** + * Get Firefox sessions + * @param paths Array of `FirefoxProfiles` + * @param platform Platform to parse Firefox sessions + * @returns Array of `FirefoxSession` + */ +export function firefoxSessions( + paths: FirefoxProfiles[], + platform: PlatformType, +): FirefoxSession[] { + let values: FirefoxSession[] = []; + for (const path of paths) { + let full_path = [ `${path.full_path}/sessionstore-backups/*`, `${path.full_path}/sessionstore.jsonlz4` ]; + if (platform === PlatformType.Windows) { + full_path = [ `${path.full_path}\\sessionstore-backups\\*`, `${path.full_path}\\sessionstore.jsonlz4` ]; + } + + for (const sess_path of full_path) { + const session_files = glob(sess_path); + if (session_files instanceof FileError) { + continue; + } + + for (const entry of session_files) { + if (!entry.is_file) { + continue; + } + const bytes = readFile(entry.full_path); + if (bytes instanceof FileError) { + continue; + } + const decom_bytes = parseCompression(bytes); + if (decom_bytes instanceof NomError || decom_bytes instanceof CompressionError) { + continue; + } + const text = extractUtf8String(decom_bytes); + const result = extractSession(text, path.version, path.full_path, entry.full_path); + values = values.concat(result); + } + } + } + + return values; +} + +/** + * Extract some of the data from the complex JSON object + * @param data Complex JSON string + * @param version Firefox version + * @param path Path to Firefox profile + * @param evidence Path to bookmark file + * @returns Array of `FirefoxSession` + */ +function extractSession(data: string, version: string, path: string, evidence: string): FirefoxSession[] { + const sess: FirefoxSessionRaw = JSON.parse(data); + const values: FirefoxSession[] = []; + const started = sess.session.startTime; + for (const win of sess.windows) { + for (const tab of win.tabs) { + for (const entry of tab.entries) { + const value: FirefoxSession = { + timestamp_desc: "Session Started", + artifact: "Browser Session", + data_type: "application:firefox:session:entry", + datetime: unixEpochToISO(started), + message: `Session URL '${entry.url}'`, + version, + path, + evidence, + last_accessed: unixEpochToISO(tab.lastAccessed), + url: entry.url, + title: entry.title, + id: entry.ID, + tab_closed: "1970-01-01T00:00:00.000Z", + window_closed: unixEpochToISO(win.closedAt ?? 0), + session_start: unixEpochToISO(started) + }; + values.push(value); + } + } + for (const tab of win._closedTabs) { + for (const entry of tab.state.entries) { + const value: FirefoxSession = { + timestamp_desc: "Session Started", + artifact: "Browser Session", + data_type: "application:firefox:session:entry", + datetime: unixEpochToISO(started), + message: `Session URL '${entry.url}'`, + version, + path, + evidence, + last_accessed: unixEpochToISO(tab.state.lastAccessed), + url: entry.url, + title: entry.title, + id: entry.ID, + tab_closed: unixEpochToISO(tab.closedAt), + window_closed: unixEpochToISO(win.closedAt ?? 0), + session_start: unixEpochToISO(started) + }; + values.push(value); + } + } + } + + return values; +} + +/** + * Function to test the Firefox JSON file parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Firefox JSON parsing + */ +export function testFirefoxJsonFiles(): void { + const path: FirefoxProfiles = { + full_path: "../../test_data/firefox/v148.0.2", + version: "148.0.2", + }; + + const ext = firefoxAddons([ path ], PlatformType.Darwin); + if (ext.length !== 13) { + throw `Got length ${ext.length} expected 13.......firefoxAddons ❌`; + } + + if (ext[ 0 ]?.name != "Data Leak Blocker") { + throw `Got name ${ext[ 0 ]?.name} expected "Data Leak Blocker".......firefoxAddons ❌`; + } + console.info(` Function firefoxAddons ✅`); + + const book = firefoxBookmark([ path ], PlatformType.Darwin); + if (book.length !== 7) { + throw `Got length ${book.length} expected 7.......firefoxBookmark ❌`; + } + + if (book[ 0 ]?.uri != "https://support.mozilla.org/products/firefox") { + throw `Got name ${book[ 0 ]?.uri} expected "https://support.mozilla.org/products/firefox".......firefoxBookmark ❌`; + } + console.info(` Function firefoxBookmark ✅`); + + const sess = firefoxSessions([ path ], PlatformType.Darwin); + if (sess.length !== 133) { + throw `Got length ${sess.length} expected 133.......firefoxSessions ❌`; + } + + if (sess[ 0 ]?.url != "about:home") { + throw `Got name ${sess[ 0 ]?.url} expected "about:home".......firefoxSessions ❌`; + } + console.info(` Function firefoxSessions ✅`); +} \ No newline at end of file diff --git a/src/applications/firefox/sqlite.ts b/src/applications/firefox/sqlite.ts index 68a1bce3..cbc6fb04 100644 --- a/src/applications/firefox/sqlite.ts +++ b/src/applications/firefox/sqlite.ts @@ -1,438 +1,438 @@ -import { FirefoxCookies, FirefoxDownloads, FirefoxFavicons, FirefoxFormHistory, FirefoxHistory, FirefoxProfiles, FirefoxStorage, Respository } from "../../../types/applications/firefox"; -import { PlatformType } from "../../system/systeminfo"; -import { unixEpochToISO } from "../../time/conversion"; -import { Unfold } from "../../unfold/client"; -import { UnfoldError } from "../../unfold/error"; -import { ApplicationError } from "../errors"; -import { querySqlite } from "../sqlite"; - -/** - * Get FireFox history for users - * @param paths Array of `FirefoxProfiles` - * @param platform OS `PlatformType` - * @param unfold Enable unfold parsing - * @param offset Starting DB offset - * @param limit Number of records to return - * @returns Array of `FirefoxHistory` - */ -export function firefoxHistory(paths: FirefoxProfiles[], platform: PlatformType, unfold: boolean, offset: number, limit: number): FirefoxHistory[] { - const query = `SELECT - moz_places.id AS moz_places_id, - url, - title, - rev_host, - visit_count, - hidden, - typed, - last_visit_date, - guid, - foreign_count, - url_hash, - description, - preview_image_url, - origin_id, - prefix, - host, - moz_origins.frecency AS frequency - FROM - moz_places - JOIN moz_origins ON moz_places.origin_id = moz_origins.id LIMIT ${limit} OFFSET ${offset}`; - const hits: FirefoxHistory[] = []; - let client: Unfold | undefined = undefined; - if (unfold) { - client = new Unfold(); - } - - for (const path of paths) { - let full_path = `${path.full_path}/places.sqlite`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\places.sqlite`; - } - - const results = querySqlite(full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${full_path}: ${results}`); - continue; - } - - // Loop through history rows - for (const entry of results) { - const history_row: FirefoxHistory = { - moz_places_id: entry["moz_places_id"] as number ?? 0, - url: entry["url"] as string ?? "", - title: entry["title"] as string ?? "", - rev_host: entry["rev_host"] as string ?? "", - visit_count: entry["visit_count"] as number ?? 0, - hidden: entry["hidden"] as number ?? 0, - typed: entry["typed"] as number ?? 0, - frequency: entry["frequency"] as number ?? 0, - last_visit_date: unixEpochToISO( - entry["last_visit_date"] as bigint ?? 0 - ), - guid: entry["guid"] as string ?? "", - foreign_count: entry["foreign_count"] as number ?? 0, - url_hash: entry["url_hash"] as number ?? 0, - description: entry["description"] as string ?? "", - preview_image_url: entry["preview_image_url"] as string ?? "", - prefix: entry["prefix"] as string ?? "", - host: entry["host"] as string ?? "", - unfold: undefined, - db_path: full_path, - message: entry["url"] as string ?? "", - datetime: unixEpochToISO( - entry["last_visit_date"] as bigint ?? 0 - ), - timestamp_desc: "URL Visited", - artifact: "URL History", - data_type: "application:firefox:history:entry", - version: path.version, - }; - - if (unfold && typeof client !== 'undefined') { - const result = client.parseUrl(history_row.url); - if (!(result instanceof UnfoldError)) { - history_row.unfold = result; - } - } - hits.push(history_row); - } - } - - return hits; -} - -/** - * Get FireFox downloads for users - * @param paths Array of `FirefoxProfiles` - * @param platform OS `PlatformType` - * @param offset Starting DB offset - * @param limit Number of records to return - * @returns Array of `FirefoxDownloads` or `ApplicationError` - */ -export function firefoxDownloads(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxDownloads[] { - const query = `SELECT - moz_annos.id AS downloads_id, - place_id, - anno_attribute_id, - content, - flags, - expiration, - type, - dateAdded, - lastModified, - moz_places.id AS moz_places_id, - url, - title, - rev_host, - visit_count, - hidden, - typed, - last_visit_date, - guid, - foreign_count, - url_hash, - description, - preview_image_url, - name - FROM - moz_annos - JOIN moz_places ON moz_annos.place_id = moz_places.id - JOIN moz_anno_attributes ON anno_attribute_id = moz_anno_attributes.id LIMIT ${limit} OFFSET ${offset}`; - const hits: FirefoxDownloads[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/places.sqlite`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\places.sqlite`; - } - - const results = querySqlite(full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${full_path}: ${results}`); - continue; - } - // Loop through downloads rows - for (const entry of results) { - const download_row: FirefoxDownloads = { - id: entry["id"] as number ?? 0, - place_id: entry["place_id"] as number ?? 0, - anno_attribute_id: entry["anno_attribute_id"] as number ?? 0, - content: entry["content"] as string ?? "", - flags: entry["flags"] as number ?? 0, - expiration: entry["expiration"] as number ?? 0, - download_type: entry["download_type"] as number ?? 0, - date_added: unixEpochToISO( - entry["date_added"] as bigint ?? 0 - ), - last_modified: unixEpochToISO( - entry["last_modified"] as bigint ?? 0 - ), - name: entry["name"] as string ?? "", - db_path: full_path, - message: "", - datetime: unixEpochToISO( - entry["date_added"] as bigint ?? 0 - ), - timestamp_desc: "File Download Start", - artifact: "File Download", - data_type: "application:firefox:downloads:entry", - moz_places_id: entry["moz_places_id"] as number ?? 0, - url: entry["url"] as string ?? "", - title: entry["title"] as string ?? "", - rev_host: entry["rev_host"] as string ?? "", - visit_count: entry["visit_count"] as number ?? 0, - hidden: entry["hidden"] as number ?? 0, - typed: entry["typed"] as number ?? 0, - frequency: entry["frequency"] as number ?? 0, - last_visit_date: unixEpochToISO( - entry["last_visit_date"] as bigint ?? 0 - ), - guid: entry["guid"] as string ?? "", - foreign_count: entry["foreign_count"] as number ?? 0, - url_hash: entry["url_hash"] as number ?? 0, - description: entry["description"] as string ?? "", - preview_image_url: entry["preview_image_url"] as string ?? "", - version: path.version, - }; - download_row.message = `File download from: ${download_row.url} | File: ${download_row.name}` - - hits.push(download_row); - } - } - - return hits; -} - -/** - * Get FireFox cookies for users - * @param paths Array of `FirefoxProfiles` - * @param platform OS `PlatformType` - * @param offset Starting DB offset - * @param limit Number of records to return - * @returns Array of `FirefoxCookies` or `ApplicationError` - */ -export function firefoxCookies(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxCookies[] { - const cookies: FirefoxCookies[] = []; - const query = `select * from moz_cookies LIMIT ${limit} OFFSET ${offset}`; - - for (const path of paths) { - let full_path = `${path.full_path}/cookies.sqlite`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\cookies.sqlite`; - } - - const results = querySqlite(full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${full_path}: ${results.message}`); - continue; - } - - for (const entry of results) { - const cookie_entry: FirefoxCookies = { - id: entry["id"] as number, - origin_attributes: entry["originAttributes"] as string, - in_browser_element: !!(entry["inBrowserElement"] as number), - same_site: !!(entry["sameSite"] as number), - scheme_map: entry["schemeMap"] as number, - name: entry["name"] as string ?? "", - value: entry["value"] as string ?? "", - path: entry["path"] as string ?? "", - expiry: unixEpochToISO( - (entry["expiry"] as bigint ?? 0) - ), - is_secure: !!(entry["isSecure"] as number ?? 0), - is_http_only: !!(entry["isHttpOnly"] as number ?? 0), - host: entry["host"] as string ?? "", - db_path: full_path, - message: "", - datetime: unixEpochToISO( - entry["expiry"] as bigint ?? 0 - ), - timestamp_desc: "Cookie Expires", - artifact: "Website Cookie", - data_type: "application:firefox:cookies:entry", - last_accessed: unixEpochToISO( - (entry["lastAccessed"] as bigint ?? 0) - ), - creation_time: unixEpochToISO( - (entry["creationTime"] as bigint ?? 0) - ), - version: path.version, - }; - - cookie_entry.message = `Cookie from ${cookie_entry.host} | Value: ${cookie_entry.value}` - cookies.push(cookie_entry); - } - - } - return cookies; - -} - -/** - * Get FireFox favicons for urls - * @param paths Array of `FirefoxProfiles` - * @param platform OS `PlatformType` - * @param offset Starting DB offset - * @param limit Number of records to return - * @returns Array of `FirefoxFavicons` or `ApplicationError` - */ -export function firefoxFavicons(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxFavicons[] { - const favicons: FirefoxFavicons[] = []; - const query = `SELECT icon_url, expire_ms FROM moz_icons LIMIT ${limit} OFFSET ${offset}`; - - for (const path of paths) { - let full_path = `${path.full_path}/favicons.sqlite`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\favicons.sqlite`; - } - - const results = querySqlite(full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${full_path}: ${results}`); - continue; - } - - for (const entry of results) { - const fav_entry: FirefoxFavicons = { - icon_url: entry["icon_url"] as string, - expires: unixEpochToISO(entry["expire_ms"] as number), - db_path: full_path, - datetime: unixEpochToISO(entry["expire_ms"] as number), - timestamp_desc: "Favicon Expires", - artifact: "URL Favicon", - data_type: "application:firefox:favicons:entry", - message: `Favicon: ${entry["icon_url"]}`, - version: path.version, - }; - - favicons.push(fav_entry); - } - - } - return favicons; -} - -/** - * Get FireFox storage info - * @param paths Array of `FirefoxProfiles` - * @param platform OS `PlatformType` - * @param offset Starting DB offset - * @param limit Number of records to return - * @returns Array of `FirefoxFavicons` or `ApplicationError` - */ -export function firefoxStorage(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxStorage[] { - const query = `SELECT repository_id, suffix, group_, origin, client_usages, usage, last_access_time, accessed, persisted FROM origin LIMIT ${limit} OFFSET ${offset}`; - - const storage: FirefoxStorage[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/storage.sqlite`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\storage.sqlite`; - } - - const results = querySqlite(full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${full_path}: ${results}`); - continue; - } - - for (const entry of results) { - const fav_entry: FirefoxStorage = { - db_path: full_path, - repository: getRepo(entry["repository_id"] as number), - group: entry["group_"] as string, - origin: entry["origin"] as string, - client_usages: entry["client_usages"] as string, - last_access: unixEpochToISO(entry["last_access_time"] as number), - accessed: entry["accessed"] as number, - persisted: entry["persisted"] as number, - suffix: entry["suffix"] as string ?? undefined, - datetime: unixEpochToISO(entry["last_access_time"] as number), - timestamp_desc: "Website Storage Last Accessed", - artifact: "Website Storage", - data_type: "application:firefox:storage:entry", - message: `Storage for ${entry["origin"]}`, - version: path.version, - }; - - storage.push(fav_entry); - } - - } - return storage; -} - -/** - * Get FireFox form history info - * @param paths Array of `FirefoxProfiles` - * @param platform OS `PlatformType` - * @param offset Starting DB offset - * @param limit Number of records to return - * @returns Array of `FirefoxFormHistory` or `ApplicationError` - */ -export function firefoxFormhistory(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxFormHistory[] { - const query = `SELECT - fieldname, - value, - timesUsed, - firstUsed, - lastUsed, - guid, - source - FROM - moz_formhistory - LEFT JOIN moz_history_to_sources ON moz_history_to_sources.history_id = moz_formhistory.id - LEFT JOIN moz_sources ON moz_history_to_sources.source_id = moz_sources.id - LIMIT ${limit} OFFSET ${offset}`; - - const values: FirefoxFormHistory[] = []; - for (const path of paths) { - let full_path = `${path.full_path}/formhistory.sqlite`; - - if (platform === PlatformType.Windows) { - full_path = `${path.full_path}\\formhistory.sqlite`; - } - const results = querySqlite(full_path, query); - if (results instanceof ApplicationError) { - console.warn(`Failed to query ${full_path}: ${results}`); - continue; - } - for(const entry of results) { - const form: FirefoxFormHistory = { - timestamp_desc: "Last Searched", - artifact: "Form History", - data_type: "application:firefox:formhistory:entry", - datetime: unixEpochToISO(entry["lastUsed"] as bigint ?? 0), - message: `Form search for '${entry["value"] ?? ""}'`, - version: path.version, - path: path.full_path, - db_path: full_path, - search_term: entry["value"] as string ?? "", - last_used: unixEpochToISO(entry["lastUsed"] as bigint ?? 0), - first_used: unixEpochToISO(entry["firstUsed"] as bigint ?? 0), - fieldname: entry["fieldname"] as string ?? "", - guid: entry["guid"] as string ?? "", - times_used: entry["timesUsed"] as number ?? "", - source: entry["source"] as string ?? "" - }; - values.push(form); - } - } - - return values; -} - -function getRepo(id: number): Respository { - switch (id) { - case 1: return Respository.Temporary; - case 0: return Respository.Persistent; - case 2: return Respository.Default; - case 3: return Respository.Private; - default: return Respository.Unknown; - } +import { FirefoxCookies, FirefoxDownloads, FirefoxFavicons, FirefoxFormHistory, FirefoxHistory, FirefoxProfiles, FirefoxStorage, Respository } from "../../../types/applications/firefox"; +import { PlatformType } from "../../system/systeminfo"; +import { unixEpochToISO } from "../../time/conversion"; +import { Unfold } from "../../unfold/client"; +import { UnfoldError } from "../../unfold/error"; +import { ApplicationError } from "../errors"; +import { querySqlite } from "../sqlite"; + +/** + * Get FireFox history for users + * @param paths Array of `FirefoxProfiles` + * @param platform OS `PlatformType` + * @param unfold Enable unfold parsing + * @param offset Starting DB offset + * @param limit Number of records to return + * @returns Array of `FirefoxHistory` + */ +export function firefoxHistory(paths: FirefoxProfiles[], platform: PlatformType, unfold: boolean, offset: number, limit: number): FirefoxHistory[] { + const query = `SELECT + moz_places.id AS moz_places_id, + url, + title, + rev_host, + visit_count, + hidden, + typed, + last_visit_date, + guid, + foreign_count, + url_hash, + description, + preview_image_url, + origin_id, + prefix, + host, + moz_origins.frecency AS frequency + FROM + moz_places + JOIN moz_origins ON moz_places.origin_id = moz_origins.id LIMIT ${limit} OFFSET ${offset}`; + const hits: FirefoxHistory[] = []; + let client: Unfold | undefined = undefined; + if (unfold) { + client = new Unfold(); + } + + for (const path of paths) { + let full_path = `${path.full_path}/places.sqlite`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\places.sqlite`; + } + + const results = querySqlite(full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${full_path}: ${results}`); + continue; + } + + // Loop through history rows + for (const entry of results) { + const history_row: FirefoxHistory = { + moz_places_id: entry[ "moz_places_id" ] as number ?? 0, + url: entry[ "url" ] as string ?? "", + title: entry[ "title" ] as string ?? "", + rev_host: entry[ "rev_host" ] as string ?? "", + visit_count: entry[ "visit_count" ] as number ?? 0, + hidden: entry[ "hidden" ] as number ?? 0, + typed: entry[ "typed" ] as number ?? 0, + frequency: entry[ "frequency" ] as number ?? 0, + last_visit_date: unixEpochToISO( + entry[ "last_visit_date" ] as bigint ?? 0 + ), + guid: entry[ "guid" ] as string ?? "", + foreign_count: entry[ "foreign_count" ] as number ?? 0, + url_hash: entry[ "url_hash" ] as number ?? 0, + description: entry[ "description" ] as string ?? "", + preview_image_url: entry[ "preview_image_url" ] as string ?? "", + prefix: entry[ "prefix" ] as string ?? "", + host: entry[ "host" ] as string ?? "", + unfold: undefined, + evidence: full_path, + message: entry[ "url" ] as string ?? "", + datetime: unixEpochToISO( + entry[ "last_visit_date" ] as bigint ?? 0 + ), + timestamp_desc: "URL Visited", + artifact: "URL History", + data_type: "application:firefox:history:entry", + version: path.version, + }; + + if (unfold && typeof client !== 'undefined') { + const result = client.parseUrl(history_row.url); + if (!(result instanceof UnfoldError)) { + history_row.unfold = result; + } + } + hits.push(history_row); + } + } + + return hits; +} + +/** + * Get FireFox downloads for users + * @param paths Array of `FirefoxProfiles` + * @param platform OS `PlatformType` + * @param offset Starting DB offset + * @param limit Number of records to return + * @returns Array of `FirefoxDownloads` or `ApplicationError` + */ +export function firefoxDownloads(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxDownloads[] { + const query = `SELECT + moz_annos.id AS downloads_id, + place_id, + anno_attribute_id, + content, + flags, + expiration, + type, + dateAdded, + lastModified, + moz_places.id AS moz_places_id, + url, + title, + rev_host, + visit_count, + hidden, + typed, + last_visit_date, + guid, + foreign_count, + url_hash, + description, + preview_image_url, + name + FROM + moz_annos + JOIN moz_places ON moz_annos.place_id = moz_places.id + JOIN moz_anno_attributes ON anno_attribute_id = moz_anno_attributes.id LIMIT ${limit} OFFSET ${offset}`; + const hits: FirefoxDownloads[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/places.sqlite`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\places.sqlite`; + } + + const results = querySqlite(full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${full_path}: ${results}`); + continue; + } + // Loop through downloads rows + for (const entry of results) { + const download_row: FirefoxDownloads = { + id: entry[ "id" ] as number ?? 0, + place_id: entry[ "place_id" ] as number ?? 0, + anno_attribute_id: entry[ "anno_attribute_id" ] as number ?? 0, + content: entry[ "content" ] as string ?? "", + flags: entry[ "flags" ] as number ?? 0, + expiration: entry[ "expiration" ] as number ?? 0, + download_type: entry[ "download_type" ] as number ?? 0, + date_added: unixEpochToISO( + entry[ "date_added" ] as bigint ?? 0 + ), + last_modified: unixEpochToISO( + entry[ "last_modified" ] as bigint ?? 0 + ), + name: entry[ "name" ] as string ?? "", + evidence: full_path, + message: "", + datetime: unixEpochToISO( + entry[ "date_added" ] as bigint ?? 0 + ), + timestamp_desc: "File Download Start", + artifact: "File Download", + data_type: "application:firefox:downloads:entry", + moz_places_id: entry[ "moz_places_id" ] as number ?? 0, + url: entry[ "url" ] as string ?? "", + title: entry[ "title" ] as string ?? "", + rev_host: entry[ "rev_host" ] as string ?? "", + visit_count: entry[ "visit_count" ] as number ?? 0, + hidden: entry[ "hidden" ] as number ?? 0, + typed: entry[ "typed" ] as number ?? 0, + frequency: entry[ "frequency" ] as number ?? 0, + last_visit_date: unixEpochToISO( + entry[ "last_visit_date" ] as bigint ?? 0 + ), + guid: entry[ "guid" ] as string ?? "", + foreign_count: entry[ "foreign_count" ] as number ?? 0, + url_hash: entry[ "url_hash" ] as number ?? 0, + description: entry[ "description" ] as string ?? "", + preview_image_url: entry[ "preview_image_url" ] as string ?? "", + version: path.version, + }; + download_row.message = `File download from: ${download_row.url} | File: ${download_row.name}`; + + hits.push(download_row); + } + } + + return hits; +} + +/** + * Get FireFox cookies for users + * @param paths Array of `FirefoxProfiles` + * @param platform OS `PlatformType` + * @param offset Starting DB offset + * @param limit Number of records to return + * @returns Array of `FirefoxCookies` or `ApplicationError` + */ +export function firefoxCookies(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxCookies[] { + const cookies: FirefoxCookies[] = []; + const query = `select * from moz_cookies LIMIT ${limit} OFFSET ${offset}`; + + for (const path of paths) { + let full_path = `${path.full_path}/cookies.sqlite`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\cookies.sqlite`; + } + + const results = querySqlite(full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${full_path}: ${results.message}`); + continue; + } + + for (const entry of results) { + const cookie_entry: FirefoxCookies = { + id: entry[ "id" ] as number, + origin_attributes: entry[ "originAttributes" ] as string, + in_browser_element: !!(entry[ "inBrowserElement" ] as number), + same_site: !!(entry[ "sameSite" ] as number), + scheme_map: entry[ "schemeMap" ] as number, + name: entry[ "name" ] as string ?? "", + value: entry[ "value" ] as string ?? "", + path: entry[ "path" ] as string ?? "", + expiry: unixEpochToISO( + (entry[ "expiry" ] as bigint ?? 0) + ), + is_secure: !!(entry[ "isSecure" ] as number ?? 0), + is_http_only: !!(entry[ "isHttpOnly" ] as number ?? 0), + host: entry[ "host" ] as string ?? "", + evidence: full_path, + message: "", + datetime: unixEpochToISO( + entry[ "expiry" ] as bigint ?? 0 + ), + timestamp_desc: "Cookie Expires", + artifact: "Website Cookie", + data_type: "application:firefox:cookies:entry", + last_accessed: unixEpochToISO( + (entry[ "lastAccessed" ] as bigint ?? 0) + ), + creation_time: unixEpochToISO( + (entry[ "creationTime" ] as bigint ?? 0) + ), + version: path.version, + }; + + cookie_entry.message = `Cookie from ${cookie_entry.host} | Value: ${cookie_entry.value}`; + cookies.push(cookie_entry); + } + + } + return cookies; + +} + +/** + * Get FireFox favicons for urls + * @param paths Array of `FirefoxProfiles` + * @param platform OS `PlatformType` + * @param offset Starting DB offset + * @param limit Number of records to return + * @returns Array of `FirefoxFavicons` or `ApplicationError` + */ +export function firefoxFavicons(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxFavicons[] { + const favicons: FirefoxFavicons[] = []; + const query = `SELECT icon_url, expire_ms FROM moz_icons LIMIT ${limit} OFFSET ${offset}`; + + for (const path of paths) { + let full_path = `${path.full_path}/favicons.sqlite`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\favicons.sqlite`; + } + + const results = querySqlite(full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${full_path}: ${results}`); + continue; + } + + for (const entry of results) { + const fav_entry: FirefoxFavicons = { + icon_url: entry[ "icon_url" ] as string, + expires: unixEpochToISO(entry[ "expire_ms" ] as number), + evidence: full_path, + datetime: unixEpochToISO(entry[ "expire_ms" ] as number), + timestamp_desc: "Favicon Expires", + artifact: "URL Favicon", + data_type: "application:firefox:favicons:entry", + message: `Favicon: ${entry[ "icon_url" ]}`, + version: path.version, + }; + + favicons.push(fav_entry); + } + + } + return favicons; +} + +/** + * Get FireFox storage info + * @param paths Array of `FirefoxProfiles` + * @param platform OS `PlatformType` + * @param offset Starting DB offset + * @param limit Number of records to return + * @returns Array of `FirefoxFavicons` or `ApplicationError` + */ +export function firefoxStorage(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxStorage[] { + const query = `SELECT repository_id, suffix, group_, origin, client_usages, usage, last_access_time, accessed, persisted FROM origin LIMIT ${limit} OFFSET ${offset}`; + + const storage: FirefoxStorage[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/storage.sqlite`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\storage.sqlite`; + } + + const results = querySqlite(full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${full_path}: ${results}`); + continue; + } + + for (const entry of results) { + const fav_entry: FirefoxStorage = { + evidence: full_path, + repository: getRepo(entry[ "repository_id" ] as number), + group: entry[ "group_" ] as string, + origin: entry[ "origin" ] as string, + client_usages: entry[ "client_usages" ] as string, + last_access: unixEpochToISO(entry[ "last_access_time" ] as number), + accessed: entry[ "accessed" ] as number, + persisted: entry[ "persisted" ] as number, + suffix: entry[ "suffix" ] as string ?? undefined, + datetime: unixEpochToISO(entry[ "last_access_time" ] as number), + timestamp_desc: "Website Storage Last Accessed", + artifact: "Website Storage", + data_type: "application:firefox:storage:entry", + message: `Storage for ${entry[ "origin" ]}`, + version: path.version, + }; + + storage.push(fav_entry); + } + + } + return storage; +} + +/** + * Get FireFox form history info + * @param paths Array of `FirefoxProfiles` + * @param platform OS `PlatformType` + * @param offset Starting DB offset + * @param limit Number of records to return + * @returns Array of `FirefoxFormHistory` or `ApplicationError` + */ +export function firefoxFormhistory(paths: FirefoxProfiles[], platform: PlatformType, offset: number, limit: number): FirefoxFormHistory[] { + const query = `SELECT + fieldname, + value, + timesUsed, + firstUsed, + lastUsed, + guid, + source + FROM + moz_formhistory + LEFT JOIN moz_history_to_sources ON moz_history_to_sources.history_id = moz_formhistory.id + LEFT JOIN moz_sources ON moz_history_to_sources.source_id = moz_sources.id + LIMIT ${limit} OFFSET ${offset}`; + + const values: FirefoxFormHistory[] = []; + for (const path of paths) { + let full_path = `${path.full_path}/formhistory.sqlite`; + + if (platform === PlatformType.Windows) { + full_path = `${path.full_path}\\formhistory.sqlite`; + } + const results = querySqlite(full_path, query); + if (results instanceof ApplicationError) { + console.warn(`Failed to query ${full_path}: ${results}`); + continue; + } + for (const entry of results) { + const form: FirefoxFormHistory = { + timestamp_desc: "Last Searched", + artifact: "Form History", + data_type: "application:firefox:formhistory:entry", + datetime: unixEpochToISO(entry[ "lastUsed" ] as bigint ?? 0), + message: `Form search for '${entry[ "value" ] ?? ""}'`, + version: path.version, + path: path.full_path, + evidence: full_path, + search_term: entry[ "value" ] as string ?? "", + last_used: unixEpochToISO(entry[ "lastUsed" ] as bigint ?? 0), + first_used: unixEpochToISO(entry[ "firstUsed" ] as bigint ?? 0), + fieldname: entry[ "fieldname" ] as string ?? "", + guid: entry[ "guid" ] as string ?? "", + times_used: entry[ "timesUsed" ] as number ?? "", + source: entry[ "source" ] as string ?? "" + }; + values.push(form); + } + } + + return values; +} + +function getRepo(id: number): Respository { + switch (id) { + case 1: return Respository.Temporary; + case 0: return Respository.Persistent; + case 2: return Respository.Default; + case 3: return Respository.Private; + default: return Respository.Unknown; + } } \ No newline at end of file diff --git a/src/applications/leveldb/table.ts b/src/applications/leveldb/table.ts index b27543c9..227d9cfd 100644 --- a/src/applications/leveldb/table.ts +++ b/src/applications/leveldb/table.ts @@ -254,7 +254,7 @@ function parseKey(shared_key: string, data: Uint8Array): KeyValueData | Applicat return new ApplicationError(`LEVELDB`, `could not get key state data: ${key_state}`); } - let key_string = ""; + let key_string; // Sometimes key is composed of 2 strings const prefix = 95; // If key starts has prefix '_' then it has two parts @@ -333,7 +333,7 @@ function parseBlock(data: Uint8Array, offset: number, size: number, path: string return new ApplicationError(`LEVELDB`, `could not determine compression type: ${is_compressed}`); } - let compression = CompressionType.None; + let compression; switch (is_compressed.value) { case 0: { compression = CompressionType.None; @@ -386,7 +386,7 @@ function parseBlock(data: Uint8Array, offset: number, size: number, path: string shared_key: first_key_value.shared_key, origin: first_key_value.key, key: first_key_value.entry_key, - path, + evidence: path, state: first_key_value.value_type !== ValueType.Unknown ? LogType.Value : LogType.Deletion }; values.push(entry); @@ -411,7 +411,7 @@ function parseBlock(data: Uint8Array, offset: number, size: number, path: string shared_key: key_value.shared_key, origin: key_value.key.split(" ").at(0) ?? "", key: key_value.key.split(" ").at(2) ?? "", - path, + evidence: path, state: key_value.value_type !== ValueType.Unknown ? LogType.Value : LogType.Deletion }; @@ -495,7 +495,7 @@ function parseBlockData(shared_key: string, data: Uint8Array): BlockValue | Appl if (non_shared_key.value as number >= key_metadata_min_size) { const non_shared_key = (non_shared_data.nommed as Uint8Array); const key_data = new Uint8Array(non_shared_key.buffer.slice(0, non_shared_key.buffer.byteLength - key_metadata_min_size)); - let key_string = ""; + let key_string; let entry_key = ""; // First key string has end of string character? const first_part = takeUntil(key_data, new Uint8Array([ 0 ])); diff --git a/src/applications/leveldb/wal.ts b/src/applications/leveldb/wal.ts index a4c9ffa0..02524468 100644 --- a/src/applications/leveldb/wal.ts +++ b/src/applications/leveldb/wal.ts @@ -172,7 +172,6 @@ function parseWalValues(data: Uint8Array, path: string): LevelDbEntry[] | Applic } let log_type = LogType.Unknown; - let key = new Uint8Array([]); let value: null | Uint8Array = null; // Key size is varint value @@ -185,7 +184,7 @@ function parseWalValues(data: Uint8Array, path: string): LevelDbEntry[] | Applic if (key_data instanceof NomError) { return new ApplicationError(`LEVELDB`, `could not get wal key data: ${key_data}`); } - key = key_data.nommed as Uint8Array; + const key = key_data.nommed as Uint8Array; remaining = key_data.remaining as Uint8Array; if (value_type.value === 1) { @@ -209,7 +208,6 @@ function parseWalValues(data: Uint8Array, path: string): LevelDbEntry[] | Applic remaining = value_data.remaining as Uint8Array; } else if (value_type.value === 0) { // It seems only keys can be recovered from deleted entries - log_type = LogType.Deletion; const key_string = parseKey(key); const entry: LevelDbEntry = { @@ -220,7 +218,7 @@ function parseWalValues(data: Uint8Array, path: string): LevelDbEntry[] | Applic shared_key: "", origin: key_string.split(" ").at(0) ?? key_string, key: key_string.split(" ").at(1) ?? key_string, - path, + evidence: path, state: LogType.Deletion }; values.push(entry); @@ -242,7 +240,7 @@ function parseWalValues(data: Uint8Array, path: string): LevelDbEntry[] | Applic shared_key: "", origin: key_string.split(" ").at(0) ?? key_string, key: key_string.split(" ").at(1) ?? key_string, - path, + evidence: path, state: LogType.Value }; @@ -538,7 +536,6 @@ export function parseValue(data: Uint8Array, value_type: ValueType): string | nu return encode(data); } console.warn(`unknown value type: ${value_type}`); - //console.log(JSON.stringify(Array.from(data))); return "Unknown value"; } diff --git a/src/applications/libreoffice.ts b/src/applications/libreoffice.ts index 6a752cbb..a88a99d4 100644 --- a/src/applications/libreoffice.ts +++ b/src/applications/libreoffice.ts @@ -115,7 +115,7 @@ export function recentFiles( password: "", readonly: false, thumbnail: "", - source: path.full_path, + evidence: path.full_path, message: path_data[ "oor:name" ], timestamp_desc: "N/A", artifact: "LibreOffice Recent Files", diff --git a/src/applications/nextcloud/cloud.ts b/src/applications/nextcloud/cloud.ts index 93196eec..700a95e5 100644 --- a/src/applications/nextcloud/cloud.ts +++ b/src/applications/nextcloud/cloud.ts @@ -1,5 +1,4 @@ import { NextcloudClientActivityLog, NextcloudClientConfig, NextcloudClientSyncLog, NextcloudClientUsers } from "../../../types/applications/nextcloud"; -import { GlobInfo } from "../../../types/filesystem/globs"; import { decompress_gzip } from "../../compression/decompress"; import { CompressionError } from "../../compression/errors"; import { extractUtf8String } from "../../encoding/strings"; @@ -49,7 +48,7 @@ export class NextcloudClient { * @returns Array of `NextcloudClientUsers` or `ApplicationError` */ private profiles(): NextcloudClientUsers[] | ApplicationError { - let paths: GlobInfo[] = []; + let paths; switch (this.platform) { case PlatformType.Linux: { const linux_paths = glob("/home/*/.config/Nextcloud"); diff --git a/src/applications/office.ts b/src/applications/office.ts index 816aa942..c8ce2738 100644 --- a/src/applications/office.ts +++ b/src/applications/office.ts @@ -1,221 +1,222 @@ -import { getRegistry, PlatformType } from "../../mod"; -import { FileError } from "../filesystem/errors"; -import { glob } from "../filesystem/files"; -import { parseBookmark } from "../macos/bookmark"; -import { BookmarkData as OfficeRecentFilesMacos } from "../../types/macos/bookmark"; -import { MacosError } from "../macos/errors"; -import { getPlist } from "../macos/plist"; -import { ApplicationError } from "./errors"; -import { getEnvValue } from "../environment/mod"; -import { WindowsError } from "../windows/errors"; -import { Registry } from "../../types/windows/registry"; -import { filetimeToUnixEpoch, unixEpochToISO } from "../time/conversion"; -import { OfficeRecentFilesWindows } from "../../types/applications/office"; -import { OfficeApp } from "../../types/applications/office"; - -/** - * Function to extract Microsoft Office MRU files - * @param platform OS `PlatformType`. Only Windows or Darwin is supported - * @param alt_file Optional alternative path to NTUSER.DAT or plist file - * @returns Array of `OfficeRecentFilesMacos` or `OfficeRecentFilesWindows` or `ApplicationError` - */ -export function officeMruFiles( - platform: PlatformType.Darwin | PlatformType.Windows, - alt_file?: string, -): OfficeRecentFilesMacos[] | OfficeRecentFilesWindows[] | ApplicationError { - switch (platform) { - case PlatformType.Darwin: - return officeBookmarks(alt_file); - case PlatformType.Windows: - return officeMru(alt_file); - } -} - -/** - * Function to parse NTUSER.DAT Registry file to extract Office MRU entries - * @param alt_file Optional alternative path to NTUSER.DAT - * @returns Array of `OfficeRecentFilesWindows` or `ApplicationError` - */ -function officeMru( - alt_file?: string, -): OfficeRecentFilesWindows[] | ApplicationError { - const paths: string[] = []; - if (alt_file !== undefined) { - paths.push(alt_file); - } else { - const volume = getEnvValue("SystemDrive"); - if (volume === "") { - return new ApplicationError(`OFFICE`, `no SystemDrive found`); - } - const glob_office = `${volume}\\Users\\*\\NTUSER.DAT`; - const glob_paths = glob(glob_office); - if (glob_paths instanceof FileError) { - return new ApplicationError( - `OFFICE`, - `failed to glob office paths for macOS: ${glob_paths}`, - ); - } - for (const entry of glob_paths) { - if (!entry.is_file || entry.full_path.includes("Default User")) { - continue; - } - paths.push(entry.full_path); - } - } - - let all_values: OfficeRecentFilesWindows[] = []; - for (const reg of paths) { - const reg_data = getRegistry(reg); - if (reg_data instanceof WindowsError) { - console.warn(`failed to parse Windows Registry ${reg}: ${reg_data}`); - continue; - } - - const values = extractMruRegistry(reg_data, reg); - all_values = all_values.concat(values); - } - return all_values; -} - -/** - * Extract MRU info - * @param data Array of `Registry` entries - * @param registry_file Path to the Registry file - * @returns Array of `OfficeRecentFilesWindows` - */ -function extractMruRegistry( - data: Registry[], - registry_file: string, -): OfficeRecentFilesWindows[] { - const mrus: Registry[] = []; - const filter = [ "\\Office\\", "\\File MRU" ]; - for (const entries of data) { - if ( - !filter.every((item) => entries.path.includes(item)) || - entries.values.length === 0 || - entries.path.includes("\\File MRU\\") - ) { - continue; - } - mrus.push(entries); - } - - const office_mru: OfficeRecentFilesWindows[] = []; - for (const mru_path of mrus) { - for (const value of mru_path.values) { - if (!value.value.includes("Item")) { - continue; - } - - const mru_data = value.data.split("*"); - const path = mru_data.at(1) ?? ""; - const timestamp = mru_data.at(0); - if (timestamp === undefined) { - console.warn(`could not split MRU path properly: ${value.data}`); - continue; - } - - const match = /T0.*]\[/; - let time_data = timestamp.match(match)?.[ 0 ]; - if (time_data === undefined) { - console.warn(`could not match MRU path properly: ${timestamp}`); - continue; - } - - time_data = time_data.replace("T", "").replace("]", "").replace("[", ""); - const filetime = BigInt(`0x${time_data}`); - const last_opened = unixEpochToISO(filetimeToUnixEpoch(filetime)); - - const mru_entry: OfficeRecentFilesWindows = { - path, - last_opened, - application: officeType(mru_path.path), - registry_file, - key_path: mru_path.path, - }; - - office_mru.push(mru_entry); - } - } - - return office_mru; -} - -/** - * Determine Office application associated with MRU entry - * @param path Registry key path - * @returns `OfficeApp` enum - */ -function officeType(path: string): OfficeApp { - if (path.includes(OfficeApp.WORD)) return OfficeApp.WORD; - if (path.includes(OfficeApp.ACCESS)) return OfficeApp.ACCESS; - if (path.includes(OfficeApp.EXCEL)) return OfficeApp.EXCEL; - if (path.includes(OfficeApp.ONENOTE)) return OfficeApp.ONENOTE; - if (path.includes(OfficeApp.POWERPOINT)) return OfficeApp.POWERPOINT; - - return OfficeApp.UNKNOWN; -} - -/** - * Function to parse Office bookmark data on macOS - * @param alt_file Optional alternative path to Office plist file - * @returns Array of `OfficeRecentFilesMacos` or `OfficeRecentFilesWindows` or `ApplicationError` - */ -function officeBookmarks( - alt_file?: string, -): OfficeRecentFilesMacos[] | ApplicationError { - const paths: string[] = []; - if (alt_file !== undefined) { - paths.push(alt_file); - } else { - const glob_office = - "/Users/*/Library/Containers/com.microsoft*/Data/Library/Preferences/com.microsoft.*.securebookmarks.plist"; - const glob_paths = glob(glob_office); - if (glob_paths instanceof FileError) { - return new ApplicationError( - `OFFICE`, - `failed to glob office paths for macOS: ${glob_paths}`, - ); - } - for (const entry of glob_paths) { - paths.push(entry.full_path); - } - } - - const office_files: OfficeRecentFilesMacos[] = []; - for (const entry of paths) { - const plist_data = getPlist(entry); - if (plist_data instanceof MacosError) { - console.warn(`failed to parse ${entry}: ${plist_data}`); - continue; - } - - if (plist_data instanceof Uint8Array || Array.isArray(plist_data)) { - continue; - } - - for (const value in plist_data) { - const bookmark_values = plist_data[ value ] as Record< - string, - number[] | string - >; - const data = bookmark_values[ "kBookmarkDataKey" ]; - if (typeof data === "string") { - console.warn(`got string for kBookmarkDataKey? It should be bytes`); - continue; - } else if (data === undefined) { - console.warn(`got undefined for kBookmarkDataKey? It should be bytes`); - continue; - } - const book_data = Uint8Array.from(data); - - const book = parseBookmark(book_data); - if (book instanceof MacosError) { - console.warn(`failed to parse MRU bookmark data: ${book}`); - continue; - } - office_files.push(book); - } - } - return office_files; -} +import { getRegistry, PlatformType } from "../../mod"; +import { FileError } from "../filesystem/errors"; +import { glob } from "../filesystem/files"; +import { parseBookmark } from "../macos/bookmark"; +import { BookmarkData as OfficeRecentFilesMacos } from "../../types/macos/bookmark"; +import { MacosError } from "../macos/errors"; +import { getPlist } from "../macos/plist"; +import { ApplicationError } from "./errors"; +import { getEnvValue } from "../environment/mod"; +import { WindowsError } from "../windows/errors"; +import { Registry } from "../../types/windows/registry"; +import { filetimeToUnixEpoch, unixEpochToISO } from "../time/conversion"; +import { OfficeRecentFilesWindows } from "../../types/applications/office"; +import { OfficeApp } from "../../types/applications/office"; + +/** + * Function to extract Microsoft Office MRU files + * @param platform OS `PlatformType`. Only Windows or Darwin is supported + * @param alt_file Optional alternative path to NTUSER.DAT or plist file + * @returns Array of `OfficeRecentFilesMacos` or `OfficeRecentFilesWindows` or `ApplicationError` + */ +export function officeMruFiles( + platform: PlatformType.Darwin | PlatformType.Windows, + alt_file?: string, +): OfficeRecentFilesMacos[] | OfficeRecentFilesWindows[] | ApplicationError { + switch (platform) { + case PlatformType.Darwin: + return officeBookmarks(alt_file); + case PlatformType.Windows: + return officeMru(alt_file); + } +} + +/** + * Function to parse NTUSER.DAT Registry file to extract Office MRU entries + * @param alt_file Optional alternative path to NTUSER.DAT + * @returns Array of `OfficeRecentFilesWindows` or `ApplicationError` + */ +function officeMru( + alt_file?: string, +): OfficeRecentFilesWindows[] | ApplicationError { + const paths: string[] = []; + if (alt_file !== undefined) { + paths.push(alt_file); + } else { + const volume = getEnvValue("SystemDrive"); + if (volume === "") { + return new ApplicationError(`OFFICE`, `no SystemDrive found`); + } + + const glob_office = `${volume}\\Users\\*\\NTUSER.*`; + const glob_paths = glob(glob_office); + if (glob_paths instanceof FileError) { + return new ApplicationError( + `OFFICE`, + `failed to glob office paths for macOS: ${glob_paths}`, + ); + } + for (const entry of glob_paths) { + if (!entry.is_file || entry.full_path.includes("Default User") || !entry.filename.toLowerCase().endsWith(".dat")) { + continue; + } + paths.push(entry.full_path); + } + } + + let all_values: OfficeRecentFilesWindows[] = []; + for (const reg of paths) { + const reg_data = getRegistry(reg); + if (reg_data instanceof WindowsError) { + console.warn(`failed to parse Windows Registry ${reg}: ${reg_data}`); + continue; + } + + const values = extractMruRegistry(reg_data); + all_values = all_values.concat(values); + } + return all_values; +} + +/** + * Extract MRU info + * @param data Array of `Registry` entries + * @returns Array of `OfficeRecentFilesWindows` + */ +function extractMruRegistry(data: Registry[]): OfficeRecentFilesWindows[] { + const mrus: Registry[] = []; + const filter = ["\\Office\\", "\\File MRU"]; + for (const entries of data) { + if ( + !filter.every((item) => entries.path.includes(item)) || + entries.values.length === 0 || + entries.path.includes("\\File MRU\\") + ) { + continue; + } + mrus.push(entries); + } + + const office_mru: OfficeRecentFilesWindows[] = []; + for (const mru_path of mrus) { + for (const value of mru_path.values) { + if (!value.value.includes("Item")) { + continue; + } + + const mru_data = value.data.split("*"); + const path = mru_data.at(1) ?? ""; + const timestamp = mru_data.at(0); + if (timestamp === undefined) { + console.warn(`could not split MRU path properly: ${value.data}`); + continue; + } + + const match = /T0.*]\[/; + let time_data = timestamp.match(match)?.[0]; + if (time_data === undefined) { + console.warn(`could not match MRU path properly: ${timestamp}`); + continue; + } + + time_data = time_data.replace("T", "").replace("]", "").replace("[", ""); + const filetime = BigInt(`0x${time_data}`); + const last_opened = unixEpochToISO(filetimeToUnixEpoch(filetime)); + + const mru_entry: OfficeRecentFilesWindows = { + path, + last_opened, + application: officeType(mru_path.path), + evidence: mru_path.evidence, + key_path: mru_path.path, + timestamp_desc: "Last Opened", + artifact: "Office Recent File", + data_type: "application:office:recent:entry", + message: `Office Document Opened: "${path}"` + }; + + office_mru.push(mru_entry); + } + } + + return office_mru; +} + +/** + * Determine Office application associated with MRU entry + * @param path Registry key path + * @returns `OfficeApp` enum + */ +function officeType(path: string): OfficeApp { + if (path.includes(OfficeApp.WORD)) return OfficeApp.WORD; + if (path.includes(OfficeApp.ACCESS)) return OfficeApp.ACCESS; + if (path.includes(OfficeApp.EXCEL)) return OfficeApp.EXCEL; + if (path.includes(OfficeApp.ONENOTE)) return OfficeApp.ONENOTE; + if (path.includes(OfficeApp.POWERPOINT)) return OfficeApp.POWERPOINT; + + return OfficeApp.UNKNOWN; +} + +/** + * Function to parse Office bookmark data on macOS + * @param alt_file Optional alternative path to Office plist file + * @returns Array of `OfficeRecentFilesMacos` or `OfficeRecentFilesWindows` or `ApplicationError` + */ +function officeBookmarks( + alt_file?: string, +): OfficeRecentFilesMacos[] | ApplicationError { + const paths: string[] = []; + if (alt_file !== undefined) { + paths.push(alt_file); + } else { + const glob_office = + "/Users/*/Library/Containers/com.microsoft*/Data/Library/Preferences/com.microsoft.*.securebookmarks.plist"; + const glob_paths = glob(glob_office); + if (glob_paths instanceof FileError) { + return new ApplicationError( + `OFFICE`, + `failed to glob office paths for macOS: ${glob_paths}`, + ); + } + for (const entry of glob_paths) { + paths.push(entry.full_path); + } + } + + const office_files: OfficeRecentFilesMacos[] = []; + for (const entry of paths) { + const plist_data = getPlist(entry); + if (plist_data instanceof MacosError) { + console.warn(`failed to parse ${entry}: ${plist_data}`); + continue; + } + + if (plist_data instanceof Uint8Array || Array.isArray(plist_data)) { + continue; + } + + for (const value in plist_data) { + const bookmark_values = plist_data[value] as Record< + string, + number[] | string + >; + const data = bookmark_values["kBookmarkDataKey"]; + if (typeof data === "string") { + console.warn(`got string for kBookmarkDataKey? It should be bytes`); + continue; + } else if (data === undefined) { + console.warn(`got undefined for kBookmarkDataKey? It should be bytes`); + continue; + } + const book_data = Uint8Array.from(data); + + const book = parseBookmark(book_data); + if (book instanceof MacosError) { + console.warn(`failed to parse MRU bookmark data: ${book}`); + continue; + } + office_files.push(book); + } + } + return office_files; +} diff --git a/src/applications/onedrive/config.ts b/src/applications/onedrive/config.ts index 7dd68fe9..084e5b56 100644 --- a/src/applications/onedrive/config.ts +++ b/src/applications/onedrive/config.ts @@ -1,111 +1,113 @@ -import { getPlist, getRegistry } from "../../../mod"; -import { OneDriveAccount } from "../../../types/applications/onedrive"; -import { MacosError } from "../../macos/errors"; -import { unixEpochToISO } from "../../time/conversion"; -import { WindowsError } from "../../windows/errors"; -import { ApplicationError } from "../errors"; - -/** - * Function to extract Account details associated with OneDrive - * @param path Path to NTUSER.DAT file - * @returns Array of `OneDriveAccount` - */ -export function accountWindows(path: string): OneDriveAccount[] | ApplicationError { - const accounts: OneDriveAccount[] = []; - const values = getRegistry(path, "\\\\software\\\\microsoft\\\\onedrive\\\\accounts"); - if (values instanceof WindowsError) { - return new ApplicationError(`ONEDRIVE`, `Failed to parse ${path}: ${values.message}`); - } - - - for (const reg of values) { - if (reg.values.length === 0) { - continue; - } - // Lazy check to see if UserEmail key found in Registry Key value names - if (!JSON.stringify(reg.values).includes("UserEmail")) { - continue; - } - - const account: OneDriveAccount = { - email: "", - device_id: "", - account_id: "", - last_signin: "", - cid: "", - message: "", - datetime: "", - timestamp_desc: "OneDrive Last Signin", - artifact: "OneDrive Account Info", - data_type: "applications:onedrive:account:entry" - }; - for (const value of reg.values) { - switch (value.value) { - case "UserEmail": - account.email = value.data; - account.message = `Last signin by ${account.email}`; - break; - case "cid": - account.cid = value.data; - break; - case "LastSignInTime": - account.last_signin = unixEpochToISO(Number(value.data)); - account.datetime = account.last_signin; - break; - case "OneDriveDeviceId": - account.device_id = value.data; - break; - case "OneAuthAccountId": - account.account_id = value.data; - break; - } - } - accounts.push(account); - - } - - return accounts; -} - -/** - * Function to extract Account details associated with OneDrive - * @param path Path to OneDriveStandaloneSuite.plist plist file - * @returns Array of `OneDriveAccount` - */ -export function accountMacos(path: string): OneDriveAccount[] | ApplicationError { - const accounts: OneDriveAccount[] = []; - const values = getPlist(path); - if (values instanceof MacosError) { - return new ApplicationError(`ONEDRIVE`, `Failed to parse ${path}: ${values.message}`); - - } - - if (Array.isArray(values) || values instanceof Uint8Array) { - return new ApplicationError(`ONEDRIVE`, `Unexpected PLIST type. Wanted HashMap. Got array`); - } - - for (const key in values) { - // Lazy check if the plist data contains the data we want - if (!JSON.stringify(values[key]).includes("UserEmail")) { - continue; - } - const account_value = values[key] as Record; - - const account: OneDriveAccount = { - email: account_value["UserEmail"] as string | undefined ?? "", - device_id: account_value["OneDriveDeviceId"] as string | undefined ?? "", - account_id: account_value["OneAuthAccountId"] as string | undefined ?? "", - last_signin: "1970-01-01T00:00:00.000Z", - cid: account_value["cid"] as string | undefined ?? "", - message: account_value["UserEmail"] as string | undefined ?? "", - datetime: "1970-01-01T00:00:00.000Z", - timestamp_desc: "OneDrive Last Signin", - artifact: "OneDrive Account Info", - data_type: "applications:onedrive:account:entry" - }; - accounts.push(account); - } - - - return accounts; +import { getPlist, getRegistry } from "../../../mod"; +import { OneDriveAccount } from "../../../types/applications/onedrive"; +import { MacosError } from "../../macos/errors"; +import { unixEpochToISO } from "../../time/conversion"; +import { WindowsError } from "../../windows/errors"; +import { ApplicationError } from "../errors"; + +/** + * Function to extract Account details associated with OneDrive + * @param path Path to NTUSER.DAT file + * @returns Array of `OneDriveAccount` + */ +export function accountWindows(path: string): OneDriveAccount[] | ApplicationError { + const accounts: OneDriveAccount[] = []; + const values = getRegistry(path, "\\\\software\\\\microsoft\\\\onedrive\\\\accounts"); + if (values instanceof WindowsError) { + return new ApplicationError(`ONEDRIVE`, `Failed to parse ${path}: ${values.message}`); + } + + + for (const reg of values) { + if (reg.values.length === 0) { + continue; + } + // Lazy check to see if UserEmail key found in Registry Key value names + if (!JSON.stringify(reg.values).includes("UserEmail")) { + continue; + } + + const account: OneDriveAccount = { + email: "", + device_id: "", + account_id: "", + last_signin: "", + cid: "", + message: "", + datetime: "", + timestamp_desc: "OneDrive Last Signin", + artifact: "OneDrive Account Info", + data_type: "applications:onedrive:account:entry", + evidence: path, + }; + for (const value of reg.values) { + switch (value.value) { + case "UserEmail": + account.email = value.data; + account.message = `Last signin by ${account.email}`; + break; + case "cid": + account.cid = value.data; + break; + case "LastSignInTime": + account.last_signin = unixEpochToISO(Number(value.data)); + account.datetime = account.last_signin; + break; + case "OneDriveDeviceId": + account.device_id = value.data; + break; + case "OneAuthAccountId": + account.account_id = value.data; + break; + } + } + accounts.push(account); + + } + + return accounts; +} + +/** + * Function to extract Account details associated with OneDrive + * @param path Path to OneDriveStandaloneSuite.plist plist file + * @returns Array of `OneDriveAccount` + */ +export function accountMacos(path: string): OneDriveAccount[] | ApplicationError { + const accounts: OneDriveAccount[] = []; + const values = getPlist(path); + if (values instanceof MacosError) { + return new ApplicationError(`ONEDRIVE`, `Failed to parse ${path}: ${values.message}`); + + } + + if (Array.isArray(values) || values instanceof Uint8Array) { + return new ApplicationError(`ONEDRIVE`, `Unexpected PLIST type. Wanted HashMap. Got array`); + } + + for (const key in values) { + // Lazy check if the plist data contains the data we want + if (!JSON.stringify(values[key]).includes("UserEmail")) { + continue; + } + const account_value = values[key] as Record; + + const account: OneDriveAccount = { + email: account_value["UserEmail"] as string | undefined ?? "", + device_id: account_value["OneDriveDeviceId"] as string | undefined ?? "", + account_id: account_value["OneAuthAccountId"] as string | undefined ?? "", + last_signin: "1970-01-01T00:00:00.000Z", + cid: account_value["cid"] as string | undefined ?? "", + message: account_value["UserEmail"] as string | undefined ?? "", + datetime: "1970-01-01T00:00:00.000Z", + timestamp_desc: "OneDrive Last Signin", + artifact: "OneDrive Account Info", + data_type: "applications:onedrive:account:entry", + evidence: path, + }; + accounts.push(account); + } + + + return accounts; } \ No newline at end of file diff --git a/src/applications/onedrive/odl.ts b/src/applications/onedrive/odl.ts index 3ba275c9..a6674015 100644 --- a/src/applications/onedrive/odl.ts +++ b/src/applications/onedrive/odl.ts @@ -30,7 +30,7 @@ export function readOdlFiles(path: string): OneDriveLog[] | ApplicationError { if (data instanceof FileError) { return new ApplicationError(`ONEDRIVE`, `could not read log ${path}: ${data.message}`); } - let filename = ""; + let filename; if (path.includes("\\")) { filename = path.split("\\").pop() ?? ""; } else { @@ -239,7 +239,7 @@ function odl_entry( entry.version = version; entry.os_version = os_version; entry.filename = filename; - entry.path = path; + entry.evidence = path; entries.push(entry); } @@ -332,7 +332,6 @@ function parseData( ); const entry: OneDriveLog = { - path: "", filename: "", created: "", code_file, @@ -346,7 +345,8 @@ function parseData( datetime: "", timestamp_desc: "OneDrive Log Entry Created", artifact: "OneDrive Log", - data_type: "applications:onedrive:logs:entry" + data_type: "applications:onedrive:logs:entry", + evidence: "", }; return entry; diff --git a/src/applications/onedrive/onedrive.ts b/src/applications/onedrive/onedrive.ts index dfbfb313..316432e0 100644 --- a/src/applications/onedrive/onedrive.ts +++ b/src/applications/onedrive/onedrive.ts @@ -1,450 +1,451 @@ -import { dumpData, glob, Output, outputResults, platform, PlatformType, readTextFile } from "../../../mod"; -import { KeyInfo, OneDriveAccount, OneDriveLog, OnedriveProfile, OneDriveSyncEngineRecord } from "../../../types/applications/onedrive"; -import { getEnvValue } from "../../environment/mod"; -import { FileError } from "../../filesystem/errors"; -import { ApplicationError } from "../errors"; -import { accountMacos, accountWindows } from "./config"; -import { readOdlFiles } from "./odl"; -import { extractSyncEngine } from "./sqlite"; - -/** - * Class to parse OneDrive artifacts - */ -export class OneDrive { - private platform: PlatformType; - private user = ""; - private profiles: OnedriveProfile[] = []; - - /** - * Construct an `OneDrive` object that can parse all OneDrive supported artifacts for all users - * @param platform OS `PlatformType` - * @param [user="*"] Optional specific user to parse OneDrive info. Default is all users - * @param alt_path Optional directory that contains *all* OneDrive related artifact files - */ - constructor (platform: PlatformType.Darwin | PlatformType.Windows, user = "*", alt_path?: string) { - this.platform = platform; - this.user = user; - - if (alt_path !== undefined) { - let separator = "/"; - let config = "*.OneDriveStandaloneSuite.plist"; - if (this.platform === PlatformType.Windows) { - separator = "\\"; - config = "NTUSER.DAT"; - } - - const profile: OnedriveProfile = { - sync_db: this.getFiles(`${alt_path}${separator}SyncEngineDatabase.db`), - odl_files: this.getFiles(`${alt_path}${separator}*odl*`), - key_file: this.getFiles(`${alt_path}${separator}general.keystore`), - config_file: this.getFiles(`${alt_path}${separator}${config}`), - }; - this.profiles.push(profile); - return; - } - this.setupProfiles(); - } - - /** - * View all artifacts the `OneDrive` object found related to OneDrive - * @returns Array of `OnedriveProfile` - */ - public oneDriveProfiles(): OnedriveProfile[] { - return this.profiles; - } - - /** - * Function to parse OneDrive key files. By default all keys are parsed based on how the `OneDrive` class was initialized - * By default all key entries are returned. You may provide an optional `Output` object to instead output the results to a file - * If results are outputted to a file. An empty array is returned - * @param files Optional array of specific keys files to parse - * @param output Optional `Output` object to output results instead of returning them to the caller - * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL - * @returns Array of `KeyInfo` - */ - public oneDriveKeys(files?: string[], output?: Output, metadata_runtime = false): KeyInfo[] { - const keys: KeyInfo[] = []; - // If we only want to parse a subset of keys - if (files !== undefined) { - for (const entry of files) { - const key: KeyInfo = { - path: entry, - key: "", - }; - const data = readTextFile(entry); - if (data instanceof FileError) { - console.warn(`failed to read file ${entry}: ${data.message}`); - continue; - } - - const values = JSON.parse(data) as Record[]; - for (const value of values) { - key.key = value[ "Key" ] as string; - break; - } - keys.push(key); - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(keys, "onedrive_keys", output); - } else { - dumpData(keys, "onedrive_keys", output); - } - return []; - } - return keys; - } - - // Parse all keys - for (const profile of this.profiles) { - for (const entry of profile.key_file) { - const key: KeyInfo = { - path: entry, - key: "", - }; - const data = readTextFile(entry); - if (data instanceof FileError) { - console.warn(`failed to read file ${entry}: ${data.message}`); - continue; - } - - const values = JSON.parse(data) as Record[]; - for (const value of values) { - key.key = value[ "Key" ] as string; - break; - } - keys.push(key); - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(keys, "onedrive_keys", output); - } else { - dumpData(keys, "onedrive_keys", output); - } - return []; - } - } - return keys; - } - - /** - * Function to parse OneDrive Log (ODL) files. By default all logs are parsed based on how the `OneDrive` class was initialized - * By default all log entries are returned. You may provide an optional `Output` object to instead output the results to a file - * If results are outputted to a file. An empty array is returned - * @param files Optional array of specific ODL files to parse - * @param output Optional `Output` object to output results instead of returning them to the caller - * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL - * @returns Array of `OneDriveLog` - */ - public oneDriveLogs(files?: string[], output?: Output, metadata_runtime = false): OneDriveLog[] { - let logs: OneDriveLog[] = []; - // Check if we only want to parse a subset of logs - if (files !== undefined) { - for (const entry of files) { - const values = readOdlFiles(entry); - if (values instanceof ApplicationError) { - console.error(`${values}`); - continue; - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(values, "onedrive_odl_logs", output); - } else { - dumpData(values, "onedrive_odl_logs", output); - } - continue; - } - logs = logs.concat(values); - } - return logs; - } - - // Parse all logs - for (const profile of this.profiles) { - for (const entry of profile.odl_files) { - const values = readOdlFiles(entry); - if (values instanceof ApplicationError) { - console.error(`${values}`); - continue; - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(values, "onedrive_odl_logs", output); - } else { - dumpData(values, "onedrive_odl_logs", output); - } - continue; - } - logs = logs.concat(values); - } - } - return logs; - } - - /** - * Function to parse OneDrive Account info. By default all accounts are parsed based on how the `OneDrive` class was initialized - * By default all account entries are returned. You may provide an optional `Output` object to instead output the results to a file - * If results are outputted to a file. An empty array is returned - * @param files Optional array of specific account configs files to parse. Windows will be NTUSER.DAT. macOS will be plist files - * @param output Optional `Output` object to output results instead of returning them to the caller - * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL - * @returns Array of `OneDriveAccount` - */ - public oneDriveAccounts(files?: string[], output?: Output, metadata_runtime = false): OneDriveAccount[] { - let configs: OneDriveAccount[] = []; - // Check if we only want to parse a subset of accounts - if (files !== undefined) { - for (const entry of files) { - const values = this.platform === PlatformType.Windows ? accountWindows(entry) : accountMacos(entry); - if (values instanceof ApplicationError) { - console.error(`${values}`); - continue; - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(values, "onedrive_accounts", output); - } else { - dumpData(values, "onedrive_accounts", output); - } - continue; - } - configs = configs.concat(values); - } - return configs; - } - - // Parse all configs - for (const profile of this.profiles) { - for (const entry of profile.config_file) { - const values = this.platform === PlatformType.Windows ? accountWindows(entry) : accountMacos(entry); - if (values instanceof ApplicationError) { - console.error(`${values}`); - continue; - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(values, "onedrive_accounts", output); - } else { - dumpData(values, "onedrive_accounts", output); - } - continue; - } - configs = configs.concat(values); - } - } - return configs; - } - - /** - * Function to parse OneDrive Sync databases. By default all databases are parsed based on how the `OneDrive` class was initialized - * By default all database entries are returned. You may provide an optional `Output` object to instead output the results to a file - * If results are outputted to a file. An empty array is returned - * @param files Optional array of specific db files to query - * @param output Optional `Output` object to output results instead of returning them to the caller - * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL - * @returns Array of `OneDriveSyncEngineRecord` - */ - public oneDriveSyncDatabase(files?: string[], output?: Output, metadata_runtime = false): OneDriveSyncEngineRecord[] { - let db: OneDriveSyncEngineRecord[] = []; - // Check if we only want to parse a specific database - if (files !== undefined) { - for (const entry of files) { - const values = extractSyncEngine(entry); - if (values instanceof ApplicationError) { - console.error(`${values}`); - continue; - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(values, "onedrive_syncdb", output); - } else { - dumpData(values, "onedrive_syncdb", output); - } - continue; - } - db = db.concat(values); - } - return db; - } - - // Parse all databases - for (const profile of this.profiles) { - for (const entry of profile.sync_db) { - const values = extractSyncEngine(entry); - if (values instanceof ApplicationError) { - console.error(`${values}`); - continue; - } - if (output !== undefined) { - if (metadata_runtime) { - outputResults(values, "onedrive_syncdb", output); - } else { - dumpData(values, "onedrive_syncdb", output); - } - continue; - } - db = db.concat(values); - } - } - return db; - } - - /** - * Function that parses and timelines all OneDrive artifacts - * @param output `Object` object to output results - */ - public retrospect(output: Output): void { - this.oneDriveLogs(undefined, output); - this.oneDriveSyncDatabase(undefined, output); - this.oneDriveAccounts(undefined, output); - } - - /** - * Setup OneDrive paths for all users or specific user if provided - * @returns Nothing - */ - private setupProfiles(): void { - if (this.platform === PlatformType.Darwin) { - const base_users = `/Users/${this.user}`; - const all_users = glob(base_users); - if (all_users instanceof FileError) { - return; - } - - for (const user of all_users) { - const profile: OnedriveProfile = { - sync_db: [], - odl_files: [], - key_file: [], - config_file: [], - }; - const odl_glob = `${user.full_path}/Library/Logs/OneDrive/*/*odl*`; - profile.odl_files = this.getFiles(odl_glob); - - const sync_glob = `${user.full_path}/Library/Application Support/OneDrive/settings/*/SyncEngineDatabase.db`; - profile.sync_db = this.getFiles(sync_glob); - - const key_glob = `${user.full_path}/Library/Logs/OneDrive/*/general.keystore`; - profile.key_file = this.getFiles(key_glob); - - const config_glob = `${user.full_path}/Library/Group Containers/*.OneDriveStandaloneSuite/Library/Preferences/*.OneDriveStandaloneSuite.plist`; - profile.config_file = this.getFiles(config_glob); - - if (profile.config_file.length === 0 && - profile.key_file.length === 0 && - profile.odl_files.length === 0 && - profile.sync_db.length === 0) { - continue; - } - this.profiles.push(profile); - } - } - - let drive = getEnvValue("HOMEDRIVE"); - if (drive === "") { - drive = "C:"; - } - const base_users = `${drive}\\Users\\${this.user}`; - const all_users = glob(base_users); - if (all_users instanceof FileError) { - return; - } - - for (const user of all_users) { - const profile: OnedriveProfile = { - sync_db: [], - odl_files: [], - key_file: [], - config_file: [], - }; - - const odl_glob = `${user.full_path}\\AppData\\Local\\Microsoft\\OneDrive\\logs\\*\\*odl*`; - profile.odl_files = this.getFiles(odl_glob); - - const sync_glob = `${user.full_path}\\AppData\\Local\\Microsoft\\OneDrive\\settings\\*\\SyncEngineDatabase.db`; - profile.sync_db = this.getFiles(sync_glob); - - const key_glob = `${user.full_path}\\AppData\\Local\\Microsoft\\OneDrive\\logs\\*\\general.keystore`; - profile.key_file = this.getFiles(key_glob); - - const config_glob = `${user.full_path}\\NTUSER.*`; - - profile.config_file = this.getFiles(config_glob); - if (profile.key_file.length === 0 && - profile.odl_files.length === 0 && - profile.sync_db.length === 0) { - continue; - } - this.profiles.push(profile); - } - - - } - - /** - * Function to get file artifacts associated with OneDrive - * @param glob_string Glob string that points to files associated with OneDrive artifacts - * @returns Array of strings - */ - private getFiles(glob_string: string): string[] { - const files: string[] = []; - const glob_files = glob(glob_string); - if (glob_files instanceof FileError) { - return []; - } - - for (const entry of glob_files) { - if (entry.filename.toLowerCase().includes("ntuser.dat") && - !entry.filename.toLowerCase().endsWith("dat")) { - continue; - } - files.push(entry.full_path); - } - - return files; - } -} - -/** - * Function to test the OneDrive class - * This function should not be called unless you are developing the artemis-api - * Or want to validate the OneDrive parsing - */ -export function testOneDrive(): void { - let test = "../../tests/test_data/DFIRArtifactMuseum/onedrive/24.175.0830.0001/mock"; - let plat = PlatformType.Windows; - if (platform().toLowerCase() === "darwin") { - plat = PlatformType.Darwin; - test = "../../../tests/test_data/DFIRArtifactMuseum/onedrive/24.175.0830.0001/mock"; - } - - const client = new OneDrive(plat, undefined, test); - - const status = client.oneDriveLogs(); - if (status.length !== 367) { - throw `Got '${status.length}' expected "367".......OneDrive ❌`; - } - - const sync = client.oneDriveSyncDatabase(); - if (sync.length !== 43) { - throw `Got '${sync.length}' expected "43".......OneDrive ❌`; - } - - const account = client.oneDriveAccounts(); - if (account.length !== 0 && plat === PlatformType.Windows) { - throw `Got '${account.length}' expected "0".......OneDrive ❌`; - } else if (account.length !== 1 && plat === PlatformType.Darwin) { - throw `Got '${account.length}' expected "1".......OneDrive ❌`; - } - - if (plat === PlatformType.Darwin && account[ 0 ]?.account_id !== "aaaaaaaaa") { - throw `Got '${account[ 0 ]?.account_id}' expected "aaaaaaaaa".......OneDrive ❌`; - } - - const key = client.oneDriveKeys(); - if (key.length !== 1) { - throw `Got '${key.length}' expected "1"......OneDrive ❌`; - } - - console.info(` Mock OneDrive Class ✅`); +import { dumpData, glob, Output, outputResults, platform, PlatformType, readTextFile } from "../../../mod"; +import { KeyInfo, OneDriveAccount, OneDriveLog, OnedriveProfile, OneDriveSyncEngineRecord } from "../../../types/applications/onedrive"; +import { getEnvValue } from "../../environment/mod"; +import { FileError } from "../../filesystem/errors"; +import { ApplicationError } from "../errors"; +import { accountMacos, accountWindows } from "./config"; +import { readOdlFiles } from "./odl"; +import { extractSyncEngine } from "./sqlite"; + +/** + * Class to parse OneDrive artifacts + */ +export class OneDrive { + private platform: PlatformType; + private user = ""; + private profiles: OnedriveProfile[] = []; + + /** + * Construct an `OneDrive` object that can parse all OneDrive supported artifacts for all users + * @param platform OS `PlatformType` + * @param [user="*"] Optional specific user to parse OneDrive info. Default is all users + * @param alt_path Optional directory that contains *all* OneDrive related artifact files + */ + constructor (platform: PlatformType.Darwin | PlatformType.Windows, user = "*", alt_path?: string) { + this.platform = platform; + this.user = user; + + if (alt_path !== undefined) { + let separator = "/"; + let config = "*.OneDriveStandaloneSuite.plist"; + if (this.platform === PlatformType.Windows) { + separator = "\\"; + config = "NTUSER.DAT"; + } + + const profile: OnedriveProfile = { + sync_db: this.getFiles(`${alt_path}${separator}SyncEngineDatabase.db`), + odl_files: this.getFiles(`${alt_path}${separator}*odl*`), + key_file: this.getFiles(`${alt_path}${separator}general.keystore`), + config_file: this.getFiles(`${alt_path}${separator}${config}`), + }; + this.profiles.push(profile); + return; + } + this.setupProfiles(); + } + + /** + * View all artifacts the `OneDrive` object found related to OneDrive + * @returns Array of `OnedriveProfile` + */ + public oneDriveProfiles(): OnedriveProfile[] { + return this.profiles; + } + + /** + * Function to parse OneDrive key files. By default all keys are parsed based on how the `OneDrive` class was initialized + * By default all key entries are returned. You may provide an optional `Output` object to instead output the results to a file + * If results are outputted to a file. An empty array is returned + * @param files Optional array of specific keys files to parse + * @param output Optional `Output` object to output results instead of returning them to the caller + * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL + * @returns Array of `KeyInfo` + */ + public oneDriveKeys(files?: string[], output?: Output, metadata_runtime = false): KeyInfo[] { + const keys: KeyInfo[] = []; + // If we only want to parse a subset of keys + if (files !== undefined) { + for (const entry of files) { + const key: KeyInfo = { + path: entry, + key: "", + }; + const data = readTextFile(entry); + if (data instanceof FileError) { + console.warn(`failed to read file ${entry}: ${data.message}`); + continue; + } + + const values = JSON.parse(data) as Record[]; + for (const value of values) { + key.key = value[ "Key" ] as string; + break; + } + keys.push(key); + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(keys, "onedrive_keys", output); + } else { + dumpData(keys, "onedrive_keys", output); + } + return []; + } + return keys; + } + + // Parse all keys + for (const profile of this.profiles) { + for (const entry of profile.key_file) { + const key: KeyInfo = { + path: entry, + key: "", + }; + const data = readTextFile(entry); + if (data instanceof FileError) { + console.warn(`failed to read file ${entry}: ${data.message}`); + continue; + } + + const values = JSON.parse(data) as Record[]; + for (const value of values) { + key.key = value[ "Key" ] as string; + break; + } + keys.push(key); + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(keys, "onedrive_keys", output); + } else { + dumpData(keys, "onedrive_keys", output); + } + return []; + } + } + return keys; + } + + /** + * Function to parse OneDrive Log (ODL) files. By default all logs are parsed based on how the `OneDrive` class was initialized + * By default all log entries are returned. You may provide an optional `Output` object to instead output the results to a file + * If results are outputted to a file. An empty array is returned + * @param files Optional array of specific ODL files to parse + * @param output Optional `Output` object to output results instead of returning them to the caller + * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL + * @returns Array of `OneDriveLog` + */ + public oneDriveLogs(files?: string[], output?: Output, metadata_runtime = false): OneDriveLog[] { + let logs: OneDriveLog[] = []; + // Check if we only want to parse a subset of logs + if (files !== undefined) { + for (const entry of files) { + const values = readOdlFiles(entry); + if (values instanceof ApplicationError) { + console.error(`${values}`); + continue; + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(values, "onedrive_odl_logs", output); + } else { + dumpData(values, "onedrive_odl_logs", output); + } + continue; + } + logs = logs.concat(values); + } + return logs; + } + + // Parse all logs + for (const profile of this.profiles) { + for (const entry of profile.odl_files) { + const values = readOdlFiles(entry); + if (values instanceof ApplicationError) { + console.error(`${values}`); + continue; + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(values, "onedrive_odl_logs", output); + } else { + dumpData(values, "onedrive_odl_logs", output); + } + continue; + } + logs = logs.concat(values); + } + } + return logs; + } + + /** + * Function to parse OneDrive Account info. By default all accounts are parsed based on how the `OneDrive` class was initialized + * By default all account entries are returned. You may provide an optional `Output` object to instead output the results to a file + * If results are outputted to a file. An empty array is returned + * @param files Optional array of specific account configs files to parse. Windows will be NTUSER.DAT. macOS will be plist files + * @param output Optional `Output` object to output results instead of returning them to the caller + * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL + * @returns Array of `OneDriveAccount` + */ + public oneDriveAccounts(files?: string[], output?: Output, metadata_runtime = false): OneDriveAccount[] { + let configs: OneDriveAccount[] = []; + // Check if we only want to parse a subset of accounts + if (files !== undefined) { + for (const entry of files) { + const values = this.platform === PlatformType.Windows ? accountWindows(entry) : accountMacos(entry); + if (values instanceof ApplicationError) { + console.error(`${values}`); + continue; + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(values, "onedrive_accounts", output); + } else { + dumpData(values, "onedrive_accounts", output); + } + continue; + } + configs = configs.concat(values); + } + return configs; + } + + // Parse all configs + for (const profile of this.profiles) { + for (const entry of profile.config_file) { + const values = this.platform === PlatformType.Windows ? accountWindows(entry) : accountMacos(entry); + if (values instanceof ApplicationError) { + console.error(`${values}`); + continue; + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(values, "onedrive_accounts", output); + } else { + dumpData(values, "onedrive_accounts", output); + } + continue; + } + configs = configs.concat(values); + } + } + return configs; + } + + /** + * Function to parse OneDrive Sync databases. By default all databases are parsed based on how the `OneDrive` class was initialized + * By default all database entries are returned. You may provide an optional `Output` object to instead output the results to a file + * If results are outputted to a file. An empty array is returned + * @param files Optional array of specific db files to query + * @param output Optional `Output` object to output results instead of returning them to the caller + * @param [metadata_runtime=false] Append runtime metadata to the output. Default is false. Only applicable if the Output.Format is JSON or JSONL + * @returns Array of `OneDriveSyncEngineRecord` + */ + public oneDriveSyncDatabase(files?: string[], output?: Output, metadata_runtime = false): OneDriveSyncEngineRecord[] { + let db: OneDriveSyncEngineRecord[] = []; + // Check if we only want to parse a specific database + if (files !== undefined) { + for (const entry of files) { + const values = extractSyncEngine(entry); + if (values instanceof ApplicationError) { + console.error(`${values}`); + continue; + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(values, "onedrive_syncdb", output); + } else { + dumpData(values, "onedrive_syncdb", output); + } + continue; + } + db = db.concat(values); + } + return db; + } + + // Parse all databases + for (const profile of this.profiles) { + for (const entry of profile.sync_db) { + const values = extractSyncEngine(entry); + if (values instanceof ApplicationError) { + console.error(`${values}`); + continue; + } + if (output !== undefined) { + if (metadata_runtime) { + outputResults(values, "onedrive_syncdb", output); + } else { + dumpData(values, "onedrive_syncdb", output); + } + continue; + } + db = db.concat(values); + } + } + return db; + } + + /** + * Function that parses and timelines all OneDrive artifacts + * @param output `Object` object to output results + */ + public retrospect(output: Output): void { + this.oneDriveLogs(undefined, output); + this.oneDriveSyncDatabase(undefined, output); + this.oneDriveAccounts(undefined, output); + } + + /** + * Setup OneDrive paths for all users or specific user if provided + * @returns Nothing + */ + private setupProfiles(): void { + if (this.platform === PlatformType.Darwin) { + const base_users = `/Users/${this.user}`; + const all_users = glob(base_users); + if (all_users instanceof FileError) { + return; + } + + for (const user of all_users) { + const profile: OnedriveProfile = { + sync_db: [], + odl_files: [], + key_file: [], + config_file: [], + }; + const odl_glob = `${user.full_path}/Library/Logs/OneDrive/*/*odl*`; + profile.odl_files = this.getFiles(odl_glob); + + const sync_glob = `${user.full_path}/Library/Application Support/OneDrive/settings/*/SyncEngineDatabase.db`; + profile.sync_db = this.getFiles(sync_glob); + + const key_glob = `${user.full_path}/Library/Logs/OneDrive/*/general.keystore`; + profile.key_file = this.getFiles(key_glob); + + const config_glob = `${user.full_path}/Library/Group Containers/*.OneDriveStandaloneSuite/Library/Preferences/*.OneDriveStandaloneSuite.plist`; + profile.config_file = this.getFiles(config_glob); + + if (profile.config_file.length === 0 && + profile.key_file.length === 0 && + profile.odl_files.length === 0 && + profile.sync_db.length === 0) { + continue; + } + this.profiles.push(profile); + } + } + + let drive = getEnvValue("HOMEDRIVE"); + if (drive === "") { + drive = "C:"; + } + const base_users = `${drive}\\Users\\${this.user}`; + const all_users = glob(base_users); + if (all_users instanceof FileError) { + return; + } + + for (const user of all_users) { + const profile: OnedriveProfile = { + sync_db: [], + odl_files: [], + key_file: [], + config_file: [], + }; + + const odl_glob = `${user.full_path}\\AppData\\Local\\Microsoft\\OneDrive\\logs\\*\\*odl*`; + profile.odl_files = this.getFiles(odl_glob); + + const sync_glob = `${user.full_path}\\AppData\\Local\\Microsoft\\OneDrive\\settings\\*\\SyncEngineDatabase.db`; + profile.sync_db = this.getFiles(sync_glob); + + const key_glob = `${user.full_path}\\AppData\\Local\\Microsoft\\OneDrive\\logs\\*\\general.keystore`; + profile.key_file = this.getFiles(key_glob); + + const config_glob = `${user.full_path}\\NTUSER.*`; + + profile.config_file = this.getFiles(config_glob); + if (profile.key_file.length === 0 && + profile.odl_files.length === 0 && + profile.sync_db.length === 0) { + continue; + } + this.profiles.push(profile); + } + + + } + + /** + * Function to get file artifacts associated with OneDrive + * @param glob_string Glob string that points to files associated with OneDrive artifacts + * @returns Array of strings + */ + private getFiles(glob_string: string): string[] { + const files: string[] = []; + const glob_files = glob(glob_string); + if (glob_files instanceof FileError) { + return []; + } + + for (const entry of glob_files) { + // When globbing for user profile on Windows. Make sure to not get transaction files + if (entry.filename.toLowerCase().includes("ntuser.dat") && + !entry.filename.toLowerCase().endsWith("dat")) { + continue; + } + files.push(entry.full_path); + } + + return files; + } +} + +/** + * Function to test the OneDrive class + * This function should not be called unless you are developing the artemis-api + * Or want to validate the OneDrive parsing + */ +export function testOneDrive(): void { + let test = "../../tests/test_data/DFIRArtifactMuseum/onedrive/24.175.0830.0001/mock"; + let plat = PlatformType.Windows; + if (platform().toLowerCase() === "darwin") { + plat = PlatformType.Darwin; + test = "../../../tests/test_data/DFIRArtifactMuseum/onedrive/24.175.0830.0001/mock"; + } + + const client = new OneDrive(plat, undefined, test); + + const status = client.oneDriveLogs(); + if (status.length !== 367) { + throw `Got '${status.length}' expected "367".......OneDrive ❌`; + } + + const sync = client.oneDriveSyncDatabase(); + if (sync.length !== 43) { + throw `Got '${sync.length}' expected "43".......OneDrive ❌`; + } + + const account = client.oneDriveAccounts(); + if (account.length !== 0 && plat === PlatformType.Windows) { + throw `Got '${account.length}' expected "0".......OneDrive ❌`; + } else if (account.length !== 1 && plat === PlatformType.Darwin) { + throw `Got '${account.length}' expected "1".......OneDrive ❌`; + } + + if (plat === PlatformType.Darwin && account[ 0 ]?.account_id !== "aaaaaaaaa") { + throw `Got '${account[ 0 ]?.account_id}' expected "aaaaaaaaa".......OneDrive ❌`; + } + + const key = client.oneDriveKeys(); + if (key.length !== 1) { + throw `Got '${key.length}' expected "1"......OneDrive ❌`; + } + + console.info(` Mock OneDrive Class ✅`); } \ No newline at end of file diff --git a/src/applications/onedrive/sqlite.ts b/src/applications/onedrive/sqlite.ts index 74c57be0..5625e749 100644 --- a/src/applications/onedrive/sqlite.ts +++ b/src/applications/onedrive/sqlite.ts @@ -1,324 +1,324 @@ -import { - OneDriveSyncEngineFolder, - OneDriveSyncEngineRecord, -} from "../../../types/applications/onedrive"; -import { decode } from "../../encoding/base64"; -import { EncodingError } from "../../encoding/errors"; -import { bytesToHexString } from "../../encoding/strings"; -import { unixEpochToISO } from "../../time/conversion"; -import { ApplicationError } from "../errors"; -import { querySqlite } from "../sqlite"; - -/** - * Function to extract SyncEngineDatabase details - * @param path Path to the SyncEngineDatabase.db database - * @returns Array of `OneDriveSyncEngineRecord` entries or `ApplicationError` - */ -export function extractSyncEngine( - path: string, -): OneDriveSyncEngineRecord[] | ApplicationError { - const records = getRecords(path); - if (records instanceof ApplicationError) { - return records; - } - - const folders = getFolders(path); - if (folders instanceof ApplicationError) { - console.warn(`no folders returned ${folders.message}`); - return records; - } - - for (const record of records) { - for (const folder of folders) { - if (record.parent_resource_id === folder.resource_id) { - record.path = `${folder.parents.join("/")}/${record.filename}`; - record.directory = folder.parents.join("/"); - record.message = record.path; - if (record.message === "") { - record.message = record.filename; - } - } - } - } - - const meta = getMeta(path); - if (meta instanceof ApplicationError) { - console.warn(`no metadata returned ${meta.message}`); - return records; - } - - for (const record of records) { - for (const value of meta) { - if (record.resource_id === value.id) { - record.created_by = value.created_by; - record.modified_by = value.modified_by; - record.last_write_count = value.count; - record.graph_metadata = value.graph; - } - } - } - - return records; -} - -/** - * Function to extract file records from database - * @param path Path to the SyncEngineDatabase.db database - * @returns Array of `OneDriveSyncEngineRecord` entries or `ApplicationError` - */ -function getRecords( - path: string, -): OneDriveSyncEngineRecord[] | ApplicationError { - const query = - "SELECT parentResourceID, resourceID, eTag, fileName, fileStatus, spoPermissions, volumeID, itemIndex, lastChange, size, localHashDigest, sharedItem, mediaDateTaken, mediaWidth, mediaHeight, mediaDuration FROM od_ClientFile_Records"; - const results = querySqlite(path, query); - if (results instanceof ApplicationError) { - return new ApplicationError( - `ONEDRIVE`, - `failed to query ${path}: ${results.message}`, - ); - } - - const records: OneDriveSyncEngineRecord[] = []; - for (const value of results) { - const record: OneDriveSyncEngineRecord = { - parent_resource_id: value["parentResourceID"] as string, - resource_id: value["resourceID"] as string, - etag: value["eTag"] as string, - filename: value["fileName"] as string, - path: "", - directory: "", - file_status: value["fileStatus"] as number | null, - permissions: value["spoPermissions"] as number | null, - volume_id: value["volumeID"] as number | null, - item_index: value["itemIndex"] as number | null, - last_change: "1970-01-01T00:00:00.000Z", - size: value["size"] as number | null, - hash_digest: "", - shared_item: value["sharedItem"] as string | null, - media_date_taken: "", - media_width: value["mediaWidth"] as number | null, - media_height: value["mediaHeight"] as number | null, - media_duration: value["mediaDuration"] as number | null, - graph_metadata: "", - created_by: "", - modified_by: "", - last_write_count: 0, - db_path: path, - message: value["fileName"] as string, - datetime: "1970-01-01T00:00:00.000Z", - timestamp_desc: "OneDrive Sync Last Change", - artifact: "OneDrive Sync Record", - data_type: "applications:onedrive:sync:entry" - }; - - if (typeof value["lastChange"] === 'number') { - record.last_change = unixEpochToISO(value["lastChange"] as number); - record.datetime = record.last_change; - } - if (typeof value["mediaDateTaken"] === 'number') { - record.media_date_taken = unixEpochToISO( - value["mediaDateTaken"] as number, - ); - } - if (typeof value["localHashDigest"] === 'string') { - const data = decode(value["localHashDigest"] as string); - if (!(data instanceof EncodingError)) { - record.hash_digest = bytesToHexString(data); - } - } - records.push(record); - } - - return records; -} - -/** - * Function to extract folder records from database - * @param path Path to the SyncEngineDatabase.db database - * @returns Array of `OneDriveSyncEngineFolder` entries or `ApplicationError` - */ -function getFolders( - path: string, -): OneDriveSyncEngineFolder[] | ApplicationError { - const query = - "SELECT parentScopeID, parentResourceID, resourceID, eTag, folderName, folderStatus, spoPermissions, volumeID, itemIndex FROM od_ClientFolder_Records"; - - const results = querySqlite(path, query); - if (results instanceof ApplicationError) { - return new ApplicationError( - `ONEDRIVE`, - `failed to query ${path}: ${results.message}`, - ); - } - - const folders: OneDriveSyncEngineFolder[] = []; - for (const value of results) { - const folder: OneDriveSyncEngineFolder = { - parent_resource_id: value["parentResourceID"] as string, - resource_id: value["resourceID"] as string, - etag: value["eTag"] as string, - parent_scope_id: value["parentScopeID"] as string, - folder: value["folderName"] as string, - folder_status: value["folderStatus"] as number | null, - permissions: value["spoPermissions"] as number | null, - volume_id: value["volumeID"] as number | null, - item_index: value["itemIndex"] as number | null, - parents: [], - }; - folders.push(folder); - } - - for (const folder of folders) { - folder.parents = getParents(folder.resource_id, folders); - } - - return folders; -} - -/** - * Function to combine parent folder - * @param id Resource ID of the folder - * @param folders Array of `OneDriveSyncEngineFolder` - * @returns Array of parent folders - */ -function getParents(id: string, folders: OneDriveSyncEngineFolder[]): string[] { - let parents: string[] = []; - for (const folder of folders) { - if (folder.resource_id !== id) { - continue; - } - parents.push(folder.folder); - parents = parents.concat( - getParents(folder.parent_resource_id, folders), - ); - } - return parents.reverse(); -} - -interface SyncMeta { - created_by: string; - modified_by: string; - graph: string; - count: number; - id: string; -} - -/** - * Function to extract metadata for files from database - * @param path Path to the SyncEngineDatabase.db database - * @returns Array of `SyncMeta` entries or `ApplicationError` - */ -function getMeta(path: string): SyncMeta[] | ApplicationError { - const query = - "SELECT fileName, od_GraphMetadata_Records.* FROM od_GraphMetadata_Records INNER JOIN od_ClientFile_Records ON od_ClientFile_Records.resourceID = od_GraphMetadata_Records.resourceID"; - - const results = querySqlite(path, query); - if (results instanceof ApplicationError) { - return new ApplicationError( - `ONEDRIVE`, - `failed to query ${path}: ${results.message}`, - ); - } - - const values: SyncMeta[] = []; - for (const value of results) { - const meta: SyncMeta = { - created_by: value["createdBy"] as string, - modified_by: value["modifiedBy"] as string, - graph: value["graphMetadataJSON"] as string, - count: value["lastWriteCount"] as number, - id: value["resourceID"] as string, - }; - values.push(meta); - } - - return values; -} - -/** - * Function to test the OneDrive Sync Database parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the OneDrive Sync Database parsing - */ -export function testExtractSyncEngine(): void { - const test = "../../tests/test_data/DFIRArtifactMuseum/onedrive/24.175.0830.0001/settings/Personal/SyncEngineDatabase.db"; - let results = extractSyncEngine(test); - if (results instanceof ApplicationError) { - throw results; - } - - if (results.length !== 43) { - throw `Got "${results.length}" expected 43.......extractSyncEngine ❌` - } - - if (results[38]?.filename !== "4FDAA807B3CAB8D648CC3C82F11BB4B84D8ECF6E") { - throw `Got '${results[38]?.filename}' expected "4FDAA807B3CAB8D648CC3C82F11BB4B84D8ECF6E".......extractSyncEngine ❌` - } - - console.info(` Function extractSyncEngine ✅`); - - results = getRecords(test); - if (results instanceof ApplicationError) { - throw results; - } - - if (results.length !== 43) { - throw `Got "${results.length}" expected 43.......getRecords ❌` - } - - if (results[3]?.filename !== "Capture.PNG") { - throw `Got '${results[3]?.filename}' expected "Capture.PNG".......extractSyncEngine ❌` - } - - console.info(` Function getRecords ✅`); - - const folders = getFolders(test); - if (folders instanceof ApplicationError) { - throw folders; - } - - if (folders.length !== 11) { - throw `Got "${folders.length}" expected 11.......getFolders ❌` - } - - if (folders[3]?.folder !== "intro") { - throw `Got '${folders[3]?.folder}' expected "intro".......extractSyncEngine ❌` - } - - console.info(` Function getFolders ✅`); - - const parent: OneDriveSyncEngineFolder[] = [ - { - "parent_resource_id": "81b443a51d57432abea4457ec2023cfc", - "resource_id": "23814ecb0b784241bcec575edac5b493", - "etag": "\"{23814ECB-0B78-4241-BCEC-575EDAC5B493},37\"", - "parent_scope_id": "81b443a51d57432abea4457ec2023cfc", - "folder": "Personal Vault", - "folder_status": 7, - "permissions": 27, - "volume_id": null, - "item_index": null, - "parents": [] - } - ]; - const path = getParents("23814ecb0b784241bcec575edac5b493", parent); - if (path[0] !== "Personal Vault") { - throw `Got '${path[0]}' expected "Personal Vault".......getParents ❌` - } - console.info(` Function getParents ✅`); - - const meta = getMeta(test); - if (meta instanceof ApplicationError) { - throw meta; - } - - if (meta.length !== 43) { - throw `Got "${meta.length}" expected 43.......getMeta ❌` - } - - if (meta[27]?.id !== "fd01a69c261520b9805bcd0100000000") { - throw `Got "${meta[27]?.id}" expected "fd01a69c261520b9805bcd0100000000".......getMeta ❌` - } - console.info(` Function getMeta ✅`); +import { + OneDriveSyncEngineFolder, + OneDriveSyncEngineRecord, +} from "../../../types/applications/onedrive"; +import { decode } from "../../encoding/base64"; +import { EncodingError } from "../../encoding/errors"; +import { bytesToHexString } from "../../encoding/strings"; +import { unixEpochToISO } from "../../time/conversion"; +import { ApplicationError } from "../errors"; +import { querySqlite } from "../sqlite"; + +/** + * Function to extract SyncEngineDatabase details + * @param path Path to the SyncEngineDatabase.db database + * @returns Array of `OneDriveSyncEngineRecord` entries or `ApplicationError` + */ +export function extractSyncEngine( + path: string, +): OneDriveSyncEngineRecord[] | ApplicationError { + const records = getRecords(path); + if (records instanceof ApplicationError) { + return records; + } + + const folders = getFolders(path); + if (folders instanceof ApplicationError) { + console.warn(`no folders returned ${folders.message}`); + return records; + } + + for (const record of records) { + for (const folder of folders) { + if (record.parent_resource_id === folder.resource_id) { + record.path = `${folder.parents.join("/")}/${record.filename}`; + record.directory = folder.parents.join("/"); + record.message = record.path; + if (record.message === "") { + record.message = record.filename; + } + } + } + } + + const meta = getMeta(path); + if (meta instanceof ApplicationError) { + console.warn(`no metadata returned ${meta.message}`); + return records; + } + + for (const record of records) { + for (const value of meta) { + if (record.resource_id === value.id) { + record.created_by = value.created_by; + record.modified_by = value.modified_by; + record.last_write_count = value.count; + record.graph_metadata = value.graph; + } + } + } + + return records; +} + +/** + * Function to extract file records from database + * @param path Path to the SyncEngineDatabase.db database + * @returns Array of `OneDriveSyncEngineRecord` entries or `ApplicationError` + */ +function getRecords( + path: string, +): OneDriveSyncEngineRecord[] | ApplicationError { + const query = + "SELECT parentResourceID, resourceID, eTag, fileName, fileStatus, spoPermissions, volumeID, itemIndex, lastChange, size, localHashDigest, sharedItem, mediaDateTaken, mediaWidth, mediaHeight, mediaDuration FROM od_ClientFile_Records"; + const results = querySqlite(path, query); + if (results instanceof ApplicationError) { + return new ApplicationError( + `ONEDRIVE`, + `failed to query ${path}: ${results.message}`, + ); + } + + const records: OneDriveSyncEngineRecord[] = []; + for (const value of results) { + const record: OneDriveSyncEngineRecord = { + parent_resource_id: value["parentResourceID"] as string, + resource_id: value["resourceID"] as string, + etag: value["eTag"] as string, + filename: value["fileName"] as string, + path: "", + directory: "", + file_status: value["fileStatus"] as number | null, + permissions: value["spoPermissions"] as number | null, + volume_id: value["volumeID"] as number | null, + item_index: value["itemIndex"] as number | null, + last_change: "1970-01-01T00:00:00.000Z", + size: value["size"] as number | null, + hash_digest: "", + shared_item: value["sharedItem"] as string | null, + media_date_taken: "", + media_width: value["mediaWidth"] as number | null, + media_height: value["mediaHeight"] as number | null, + media_duration: value["mediaDuration"] as number | null, + graph_metadata: "", + created_by: "", + modified_by: "", + last_write_count: 0, + evidence: path, + message: value["fileName"] as string, + datetime: "1970-01-01T00:00:00.000Z", + timestamp_desc: "OneDrive Sync Last Change", + artifact: "OneDrive Sync Record", + data_type: "applications:onedrive:sync:entry" + }; + + if (typeof value["lastChange"] === 'number') { + record.last_change = unixEpochToISO(value["lastChange"] as number); + record.datetime = record.last_change; + } + if (typeof value["mediaDateTaken"] === 'number') { + record.media_date_taken = unixEpochToISO( + value["mediaDateTaken"] as number, + ); + } + if (typeof value["localHashDigest"] === 'string') { + const data = decode(value["localHashDigest"] as string); + if (!(data instanceof EncodingError)) { + record.hash_digest = bytesToHexString(data); + } + } + records.push(record); + } + + return records; +} + +/** + * Function to extract folder records from database + * @param path Path to the SyncEngineDatabase.db database + * @returns Array of `OneDriveSyncEngineFolder` entries or `ApplicationError` + */ +function getFolders( + path: string, +): OneDriveSyncEngineFolder[] | ApplicationError { + const query = + "SELECT parentScopeID, parentResourceID, resourceID, eTag, folderName, folderStatus, spoPermissions, volumeID, itemIndex FROM od_ClientFolder_Records"; + + const results = querySqlite(path, query); + if (results instanceof ApplicationError) { + return new ApplicationError( + `ONEDRIVE`, + `failed to query ${path}: ${results.message}`, + ); + } + + const folders: OneDriveSyncEngineFolder[] = []; + for (const value of results) { + const folder: OneDriveSyncEngineFolder = { + parent_resource_id: value["parentResourceID"] as string, + resource_id: value["resourceID"] as string, + etag: value["eTag"] as string, + parent_scope_id: value["parentScopeID"] as string, + folder: value["folderName"] as string, + folder_status: value["folderStatus"] as number | null, + permissions: value["spoPermissions"] as number | null, + volume_id: value["volumeID"] as number | null, + item_index: value["itemIndex"] as number | null, + parents: [], + }; + folders.push(folder); + } + + for (const folder of folders) { + folder.parents = getParents(folder.resource_id, folders); + } + + return folders; +} + +/** + * Function to combine parent folder + * @param id Resource ID of the folder + * @param folders Array of `OneDriveSyncEngineFolder` + * @returns Array of parent folders + */ +function getParents(id: string, folders: OneDriveSyncEngineFolder[]): string[] { + let parents: string[] = []; + for (const folder of folders) { + if (folder.resource_id !== id) { + continue; + } + parents.push(folder.folder); + parents = parents.concat( + getParents(folder.parent_resource_id, folders), + ); + } + return parents.reverse(); +} + +interface SyncMeta { + created_by: string; + modified_by: string; + graph: string; + count: number; + id: string; +} + +/** + * Function to extract metadata for files from database + * @param path Path to the SyncEngineDatabase.db database + * @returns Array of `SyncMeta` entries or `ApplicationError` + */ +function getMeta(path: string): SyncMeta[] | ApplicationError { + const query = + "SELECT fileName, od_GraphMetadata_Records.* FROM od_GraphMetadata_Records INNER JOIN od_ClientFile_Records ON od_ClientFile_Records.resourceID = od_GraphMetadata_Records.resourceID"; + + const results = querySqlite(path, query); + if (results instanceof ApplicationError) { + return new ApplicationError( + `ONEDRIVE`, + `failed to query ${path}: ${results.message}`, + ); + } + + const values: SyncMeta[] = []; + for (const value of results) { + const meta: SyncMeta = { + created_by: value["createdBy"] as string, + modified_by: value["modifiedBy"] as string, + graph: value["graphMetadataJSON"] as string, + count: value["lastWriteCount"] as number, + id: value["resourceID"] as string, + }; + values.push(meta); + } + + return values; +} + +/** + * Function to test the OneDrive Sync Database parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the OneDrive Sync Database parsing + */ +export function testExtractSyncEngine(): void { + const test = "../../tests/test_data/DFIRArtifactMuseum/onedrive/24.175.0830.0001/settings/Personal/SyncEngineDatabase.db"; + let results = extractSyncEngine(test); + if (results instanceof ApplicationError) { + throw results; + } + + if (results.length !== 43) { + throw `Got "${results.length}" expected 43.......extractSyncEngine ❌` + } + + if (results[38]?.filename !== "4FDAA807B3CAB8D648CC3C82F11BB4B84D8ECF6E") { + throw `Got '${results[38]?.filename}' expected "4FDAA807B3CAB8D648CC3C82F11BB4B84D8ECF6E".......extractSyncEngine ❌` + } + + console.info(` Function extractSyncEngine ✅`); + + results = getRecords(test); + if (results instanceof ApplicationError) { + throw results; + } + + if (results.length !== 43) { + throw `Got "${results.length}" expected 43.......getRecords ❌` + } + + if (results[3]?.filename !== "Capture.PNG") { + throw `Got '${results[3]?.filename}' expected "Capture.PNG".......extractSyncEngine ❌` + } + + console.info(` Function getRecords ✅`); + + const folders = getFolders(test); + if (folders instanceof ApplicationError) { + throw folders; + } + + if (folders.length !== 11) { + throw `Got "${folders.length}" expected 11.......getFolders ❌` + } + + if (folders[3]?.folder !== "intro") { + throw `Got '${folders[3]?.folder}' expected "intro".......extractSyncEngine ❌` + } + + console.info(` Function getFolders ✅`); + + const parent: OneDriveSyncEngineFolder[] = [ + { + "parent_resource_id": "81b443a51d57432abea4457ec2023cfc", + "resource_id": "23814ecb0b784241bcec575edac5b493", + "etag": "\"{23814ECB-0B78-4241-BCEC-575EDAC5B493},37\"", + "parent_scope_id": "81b443a51d57432abea4457ec2023cfc", + "folder": "Personal Vault", + "folder_status": 7, + "permissions": 27, + "volume_id": null, + "item_index": null, + "parents": [] + } + ]; + const path = getParents("23814ecb0b784241bcec575edac5b493", parent); + if (path[0] !== "Personal Vault") { + throw `Got '${path[0]}' expected "Personal Vault".......getParents ❌` + } + console.info(` Function getParents ✅`); + + const meta = getMeta(test); + if (meta instanceof ApplicationError) { + throw meta; + } + + if (meta.length !== 43) { + throw `Got "${meta.length}" expected 43.......getMeta ❌` + } + + if (meta[27]?.id !== "fd01a69c261520b9805bcd0100000000") { + throw `Got "${meta[27]?.id}" expected "fd01a69c261520b9805bcd0100000000".......getMeta ❌` + } + console.info(` Function getMeta ✅`); } \ No newline at end of file diff --git a/src/applications/rustdesk/logs.ts b/src/applications/rustdesk/logs.ts new file mode 100644 index 00000000..1537682d --- /dev/null +++ b/src/applications/rustdesk/logs.ts @@ -0,0 +1,130 @@ +import { RustDeskLogs, RustDeskUsers } from "../../../types/applications/rustdesk"; +import { FileError } from "../../filesystem/errors"; +import { readLines } from "../../filesystem/files"; +import { convertModifiedIso } from "../../time/conversion"; +import { ApplicationError } from "../errors"; + +/** + * Function to parse RustDesk logs + * @param path Path to RustDesk log file + * @param user `RustDeskUsers` object + * @returns Array of `RustDeskLogs` or `ApplicationError` + */ +export function readLogs(path: string, user: RustDeskUsers): RustDeskLogs[] | ApplicationError { + // Files not that large? + const lines = readLines(path, 0, 1000); + if (lines instanceof FileError) { + return new ApplicationError(`RUSTDESK`, `failed to read file ${path}: ${lines}`); + } + + const logs: RustDeskLogs[] = []; + let multiline_log = ""; + for (let i = 0; i < lines.length; i++) { + const line_value = lines[i]; + + if (line_value === "" || line_value === undefined) { + continue; + } + if (!line_value.startsWith("[")) { + multiline_log += `${line_value}\n`; + continue; + } + + if (multiline_log !== "") { + const previous_line = logs[logs.length - 1]; + if (previous_line !== undefined) { + previous_line.message = previous_line.message.concat(multiline_log); + logs[logs.length - 1] = previous_line; + } + + multiline_log = ""; + continue; + } + + const value = extractDetails(line_value, path, user.remote_id); + if (value instanceof ApplicationError) { + continue; + } + logs.push(value); + } + + return logs; +} + +/** + * Parse details of the RustDesk log + * @param log Log line + * @param evidence Path to log file + * @param remote_id ID associated with RustDesk + * @returns `RustDeskLogs` or `ApplicationError` + */ +function extractDetails(log: string, evidence: string, remote_id: string): RustDeskLogs | ApplicationError { + const date = log.split("]"); + const timestamp = date.at(0); + if (timestamp === undefined) { + return new ApplicationError(`RUSTDESK`, `improper log message: ${log}`); + } + + let remaining = date.at(1); + if (remaining === undefined) { + return new ApplicationError(`RUSTDESK`, `improper log message: ${log}`); + } + remaining = remaining.trim(); + + const level = remaining.split(" ").at(0); + if (level === undefined) { + return new ApplicationError(`RUSTDESK`, `improper log message: ${log}`); + } + + const code_path = remaining.split(" ").at(1); + if (code_path === undefined) { + return new ApplicationError(`RUSTDESK`, `improper log message: ${log}`); + } + + const message = date.slice(2).join("]").trim(); + const value: RustDeskLogs = { + evidence, + message, + datetime: convertModifiedIso(timestamp.replace("[", "")), + level: level.trim(), + code_path: code_path.replace("[", ""), + local_time: timestamp.replace("[", ""), + remote_id, + timestamp_desc: "Log Event", + artifact: "RustDesk Log", + data_type: "applications:rustdesk:log:entry" + }; + + return value; +} + +export function testRustDeskLogs(): void { + const test = "../../test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-11-54.log"; + const results = readLogs(test, { logs_path: "", remote_id: "1234", config_path: "test", version: "" }); + + if (results instanceof ApplicationError) { + throw results; + } + + if (results.length !== 51) { + throw `Got lenght ${results.length} expected 51.......readLogs ❌`; + } + + if (results[23]?.message !== "Clipboard listener subscribed: client-clipboard") { + throw `Got message ${results[23]?.message} expected "Clipboard listener subscribed: client-clipboard".......readLogs ❌`; + } + + console.info(` Function readLogs ✅`); + + const test_value = "[2025-11-23 19:44:47.656957 -05:00] INFO [src/core_main.rs:360] start --server with user centos"; + const value = extractDetails(test_value, test, "1234"); + if (value instanceof ApplicationError) { + throw value; + } + + if (value.datetime !== "2025-11-24T00:44:47.656Z") { + throw `Got datetime ${value.datetime} expected "2025-11-24T00:44:47.656Z".......extractDetails ❌`; + } + + console.info(` Function extractDetails ✅`); +} \ No newline at end of file diff --git a/src/applications/rustdesk/rmm.ts b/src/applications/rustdesk/rmm.ts new file mode 100644 index 00000000..682e78a2 --- /dev/null +++ b/src/applications/rustdesk/rmm.ts @@ -0,0 +1,212 @@ +import { glob, PlatformType, readTextFile } from "../../../mod"; +import { RustDeskLogs, RustDeskUsers } from "../../../types/applications/rustdesk"; +import { FileError } from "../../filesystem/errors"; +import { ApplicationError } from "../errors"; +import { readLogs } from "./logs"; + +/** + * Class to extract artifacts from RustDesk application + */ +export class RustDesk { + private paths: RustDeskUsers[] = []; + private platform: PlatformType; + + /** + * + * @param platform `PlatformType` enum value + * @param alt_path Optional alternative path to folder containing all RustDesk files. Overides `PlatformType` + * @returns `RustDesk` class instance + */ + constructor(platform: PlatformType, alt_path?: string) { + this.platform = platform; + + // Get AnyDesk data based on PlatformType + if (alt_path === undefined) { + const results = this.profiles(); + if (results instanceof ApplicationError) { + return; + } + + this.paths = results; + console.log(JSON.stringify(this.paths)); + return; + } + + const remote_id = this.id(this.platform, alt_path); + if (remote_id instanceof ApplicationError) { + return; + } + this.paths = [{ config_path: alt_path, version: '', logs_path: alt_path, remote_id }]; + + } + + /** + * Function to parse all logs associated with RustDesk application + * @param is_alt If optional alternative path was provided + * @returns Array of `RustDeskLogs` + */ + public logs(is_alt = false): RustDeskLogs[] { + let separator = "/"; + if (this.platform === PlatformType.Windows) { + separator = "\\"; + } + let hits: RustDeskLogs[] = []; + + if (is_alt && this.paths[0] !== undefined) { + const entries = glob(`${this.paths[0].logs_path}${separator}*.log`); + if (entries instanceof FileError) { + console.error(entries); + return hits; + } + for (const entry of entries) { + if (!entry.is_file) { + continue; + } + const values = readLogs(entry.full_path, this.paths[0]); + if (values instanceof ApplicationError) { + console.error(values); + return hits; + } + hits = hits.concat(values); + } + return hits; + } + + // Try parsing log files at default paths + for (const entry of this.paths) { + const path = `${entry.logs_path}${separator}*${separator}*`; + const entries = glob(path); + if (entries instanceof FileError) { + console.error(entries); + continue; + } + for (const log_file of entries) { + if (!log_file.is_file) { + continue; + } + const values = readLogs(log_file.full_path, entry); + if (values instanceof ApplicationError) { + console.error(values); + return hits; + } + hits = hits.concat(values); + } + } + + return hits; + } + + /** + * Grab basic profile information for installed RustDesk application + * @returns Array of `RustDeskUsers` or `ApplicationError` + */ + private profiles(): RustDeskUsers[] | ApplicationError { + let paths; + switch (this.platform) { + case PlatformType.Linux: { + const platform_paths = glob("/home/*/.config/rustdesk"); + if (platform_paths instanceof FileError) { + return new ApplicationError( + "RUSTDESK", + `failed to glob linux config paths: ${platform_paths}`, + ); + } + paths = platform_paths; + break; + } + case PlatformType.Windows: { + const platform_paths = glob("C:\\Users\\*\\AppData\\Roaming\\RustDesk"); + if (platform_paths instanceof FileError) { + return new ApplicationError( + "RUSTDESK", + `failed to glob windows config paths: ${platform_paths}`, + ); + } + paths = platform_paths; + break; + } + case PlatformType.Darwin: { + const platform_paths = glob("/Users/*/Library/Preferences/RustDesk"); + if (platform_paths instanceof FileError) { + return new ApplicationError( + "RUSTDESK", + `failed to glob macOS config paths: ${platform_paths}`, + ); + } + paths = platform_paths; + break; + } + default: { + return new ApplicationError( + "RUSTDESK", + `platform not supported: ${this.platform}`, + ); + } + } + + const clients: RustDeskUsers[] = []; + + for (const entry of paths) { + if (!entry.is_directory) { + continue; + } + + const remote_id = this.id(this.platform, entry.full_path); + if (remote_id instanceof ApplicationError) { + continue; + } + + const profile: RustDeskUsers = { + config_path: entry.full_path, + logs_path: entry.full_path.replace(".config/rustdesk", ".local/share/logs/RustDesk"), + version: '', + remote_id, + }; + + if (this.platform === PlatformType.Windows) { + profile.logs_path = `${entry.full_path}\\log` + } else if (this.platform === PlatformType.Darwin) { + profile.logs_path = entry.full_path.replace("Library/Preferences/RustDesk", "Library/logs/RustDesk") + } + + clients.push(profile); + } + return clients; + } + + /** + * Determine ID associated with RustDesk application + * @param platform `PlatformType` enum value + * @param path Base path to RustDesk application + * @returns + */ + private id(platform: PlatformType, path: string): string | ApplicationError { + let id_path = `${path}/RustDesk_local.toml`; + if (platform === PlatformType.Windows) { + id_path = `${path}\\RustDesk_local.toml`; + } + + const text_data = readTextFile(id_path); + if (text_data instanceof FileError) { + return new ApplicationError(`RUSTDESK`, `could not read ${id_path}: ${text_data}`); + } + + const id_regex = /remote_id.*/; + const match = text_data.match(id_regex); + if (match === null) { + return new ApplicationError(`RUSTDESK`, `could not match id regex`); + } + + const id_line = match?.at(0); + if (id_line === undefined) { + return new ApplicationError(`RUSTDESK`, `could not find id line got undefined`); + } + + const id_string = id_line.split("=").at(1); + if (id_string === undefined) { + return new ApplicationError(`RUSTDESK`, `could not find id got undefined`); + } + + return id_string.trim().replaceAll("'", ""); + } +} \ No newline at end of file diff --git a/src/applications/syncthing.ts b/src/applications/syncthing.ts index 5cafa15f..bbd3d1a0 100644 --- a/src/applications/syncthing.ts +++ b/src/applications/syncthing.ts @@ -1,6 +1,5 @@ import { glob, readLines } from "../../mod"; import { SyncthingClient, SyncthingLogs } from "../../types/applications/syncthing"; -import { GlobInfo } from "../../types/filesystem/globs"; import { FileError } from "../filesystem/errors"; import { PlatformType } from "../system/systeminfo"; import { ApplicationError } from "./errors"; @@ -58,14 +57,14 @@ export class Syncthing { const value = line.split(" "); const message = value.slice(4).join(" "); const log: SyncthingLogs = { - full_path: entry.full_path, tag: (value[0] ?? "").replace("[", "").replace("]", ""), datetime: `${(value[1] ?? "1970-01-01").replaceAll("/", "-")}T${value[2] ?? "00:00:00"}.000Z`, timestamp_desc: "Syncthing Log Entry", level: value[3] ?? "UNKNOWN", message, artifact: "Syncthing Log", - data_type: "application:syncthing:log:entry" + data_type: "application:syncthing:log:entry", + evidence: path, }; logs.push(log); } @@ -79,7 +78,7 @@ export class Syncthing { * @returns Array of `SyncthingClient` or `ApplicationError` */ private profiles(): SyncthingClient[] | ApplicationError { - let paths: GlobInfo[] = []; + let paths; switch (this.platform) { case PlatformType.Linux: { const linux_paths = glob("/home/*/.local/state/syncthing"); diff --git a/src/applications/vscode.ts b/src/applications/vscode.ts index f97134ab..ca2f1ae8 100644 --- a/src/applications/vscode.ts +++ b/src/applications/vscode.ts @@ -1,270 +1,270 @@ -import { Entries, Extensions, FileHistory, RecentFiles, RecentType, VscodeStorage } from "../../types/applications/vscode"; -import { encode } from "../encoding/base64"; -import { encodeBytes } from "../encoding/bytes"; -import { getEnvValue } from "../environment/env"; -import { FileError } from "../filesystem/errors"; -import { glob, readTextFile } from "../filesystem/files"; -import { PlatformType } from "../system/systeminfo"; -import { unixEpochToISO } from "../time/conversion"; -import { ApplicationError } from "./errors"; - -/** - * Return the local file history for all VSCode files. Also supports VSCodium. - * @param platform OS Platform type to lookup - * @param [include_content=false] Base64 the file history content. Default is false. If enabled expect that output to be very large - * @param alt_glob Alternative glob path to `entries.json` - * @returns Array of `FileHistory` entries or `ApplicationError` - */ -export function fileHistory( - platform: PlatformType, - include_content = false, - alt_glob?: string, -): FileHistory[] | ApplicationError { - // Get all user paths - let path = ""; - switch (platform) { - case PlatformType.Darwin: { - path = - "/Users/*/Library/Application Support/*Cod*/User/History/*/entries.json"; - break; - } - case PlatformType.Windows: { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - path = - `${drive}\\Users\\*\\AppData\\Roaming\\*Cod*\\User\\History\\*\\entries.json`; - break; - } - case PlatformType.Linux: { - path = "/home/*/.config/*Cod*/User/History/*/entries.json"; - } - } - - if (alt_glob !== undefined) { - path = alt_glob; - } - - const paths = glob(path); - if (paths instanceof FileError) { - return new ApplicationError("VSCODE", `failed to glob paths: ${paths}`); - } - - const entries: FileHistory[] = []; - for (const path of paths) { - if (!path.is_file) { - continue; - } - - // Read the JSON file - const string_data = readTextFile(path.full_path); - if (string_data instanceof FileError) { - console.warn(`Could not read file ${path.full_path}`); - continue; - } - - // Parse JSON file into the FileHistory format - const json_data: FileHistory = JSON.parse(string_data); - json_data.history_path = path.full_path; - const file_entries = json_data[ "entries" ] as Entries[]; - - // Loop through each history entry and read the contents - for (let i = 0; i < file_entries.length; i++) { - const entry = file_entries[ i ]; - if (entry === undefined) { - continue; - } - let hist_file = ""; - switch (platform) { - case PlatformType.Linux: - case PlatformType.Darwin: { - const dirs = path.full_path.split("/"); - dirs.pop(); - hist_file = `${dirs.join("/")}/${file_entries[ i ]?.id}`; - break; - } - case PlatformType.Windows: { - const dirs = path.full_path.split("\\"); - dirs.pop(); - hist_file = `${dirs.join("\\")}\\${file_entries[ i ]?.id}`; - break; - } - } - if (include_content) { - // Read file data - const history_data = readTextFile(hist_file); - if (history_data instanceof FileError) { - console.warn( - `Could not read history file ${hist_file}: ${history_data}`, - ); - continue; - } - // Base64 encode the history data - const history_encoded = encode(encodeBytes(history_data)); - entry.content = history_encoded; - } - - entry.timestamp = unixEpochToISO( - entry.timestamp as number, - ); - const flat_data: FileHistory = { - version: json_data.version, - resource: json_data.resource, - history_path: json_data.history_path, - message: `${json_data.resource} - History Entry: ${entry.id}`, - datetime: entry.timestamp, - timestamp_desc: "File Saved", - artifact: "File History", - data_type: "applications:vscode:filehistory:entry", - id: entry.id, - file_saved: entry.timestamp, - content: entry.content ?? "", - path: hist_file, - source: entry.source ?? "", - sourceDescription: entry.sourceDescription ?? "" - }; - entries.push(flat_data); - } - } - - return entries; -} - -/** - * Function to parse installed extensions for VSCode or VSCodium - * @param platform OS platform to get VScode extensions - * @param alt_path Alternative path to extensions.json file - * @returns Array of `Extensions` or `ApplicationError` - */ -export function getExtensions( - platform: PlatformType, - alt_path?: string, -): Extensions[] | ApplicationError { - // Get all user paths, unless alt_path is provided - let paths: string[] = []; - if (alt_path !== undefined) { - paths = [ alt_path ]; - } else { - let path = ""; - switch (platform) { - case PlatformType.Darwin: { - path = "/Users/*/.vscode*/extensions/extensions.json"; - break; - } - case PlatformType.Windows: { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - path = `${drive}:\\Users\\*\\.vscode*\\extensions\\extensions.json`; - break; - } - case PlatformType.Linux: { - path = "/home/*/.vscode*/extensions/extensions.json"; - } - } - - const glob_paths = glob(path); - if (glob_paths instanceof Error) { - return new ApplicationError("VSCODE", `failed to glob path: ${path}`); - } - for (const path of glob_paths) { - paths.push(path.full_path); - } - } - - const extensions: Extensions[] = []; - // Read extensions.json - for (const entry of paths) { - const extension_data = readTextFile(entry); - if (extension_data instanceof FileError) { - console.warn(`Could not read extension file ${entry}: ${extension_data}`); - continue; - } - const ext: Extensions = { - path: entry, - data: JSON.parse(extension_data), - }; - - extensions.push(ext); - } - - return extensions; -} - -/** - * Function to extract recent files and folders opened by VSCode. Also supports VSCodium. - * @param platform OS platorm to get recent files for - * @param alt_path Optional alternative path to `storage.json` - * @returns Array of `RecentFiles` or `ApplicationError` - */ -export function vscodeRecentFiles(platform: PlatformType, alt_path?: string): RecentFiles[] | ApplicationError { - // Get all user paths, unless alt_path is provided - let paths: string[] = []; - if (alt_path !== undefined) { - paths = [ alt_path ]; - } else { - let path = ""; - switch (platform) { - case PlatformType.Darwin: { - path = "/Users/*/Library/Application Support/*Cod*/User/globalStorage/storage.json"; - break; - } - case PlatformType.Windows: { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - path = `${drive}:\\Users\\*\\AppData\\Roaming\\*Cod*\\User\\globalStorage\\storage.json`; - break; - } - case PlatformType.Linux: { - path = "/home/*/.config/*Cod*/User/globalStorage/storage.json"; - } - } - - const glob_paths = glob(path); - if (glob_paths instanceof Error) { - return new ApplicationError("VSCODE", `failed to glob path: ${path}`); - } - for (const path of glob_paths) { - paths.push(path.full_path); - } - } - - const recents: RecentFiles[] = []; - // Read storage.json - for (const entry of paths) { - const storage = readTextFile(entry); - if (storage instanceof FileError) { - console.warn(`Could not read storage.json file ${entry}: ${storage}`); - continue; - } - - const storage_data = JSON.parse(storage) as VscodeStorage; - for (const item of storage_data.lastKnownMenubarData.menus.File.items) { - if (item.id !== "submenuitem.MenubarRecentMenu" || item.submenu === undefined) { - continue; - } - - for (const entries of item.submenu.items) { - if (entries.uri === undefined) { - continue; - } - const recent: RecentFiles = { - path_type: entries.id == "openRecentFolder" ? RecentType.Folder : RecentType.File, - path: entries.uri.path, - enabled: entries.enabled ?? false, - label: entries.label ?? "", - external: entries.uri.external, - storage_path: entry, - }; - recents.push(recent); - - } - } - } - - return recents; +import { Entries, Extensions, FileHistory, RecentFiles, RecentType, VscodeStorage } from "../../types/applications/vscode"; +import { encode } from "../encoding/base64"; +import { encodeBytes } from "../encoding/bytes"; +import { getEnvValue } from "../environment/env"; +import { FileError } from "../filesystem/errors"; +import { glob, readTextFile } from "../filesystem/files"; +import { PlatformType } from "../system/systeminfo"; +import { unixEpochToISO } from "../time/conversion"; +import { ApplicationError } from "./errors"; + +/** + * Return the local file history for all VSCode files. Also supports VSCodium. + * @param platform OS Platform type to lookup + * @param [include_content=false] Base64 the file history content. Default is false. If enabled expect that output to be very large + * @param alt_glob Alternative glob path to `entries.json` + * @returns Array of `FileHistory` entries or `ApplicationError` + */ +export function fileHistory( + platform: PlatformType, + include_content = false, + alt_glob?: string, +): FileHistory[] | ApplicationError { + // Get all user paths + let path = ""; + switch (platform) { + case PlatformType.Darwin: { + path = + "/Users/*/Library/Application Support/*Cod*/User/History/*/entries.json"; + break; + } + case PlatformType.Windows: { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = + `${drive}\\Users\\*\\AppData\\Roaming\\*Cod*\\User\\History\\*\\entries.json`; + break; + } + case PlatformType.Linux: { + path = "/home/*/.config/*Cod*/User/History/*/entries.json"; + } + } + + if (alt_glob !== undefined) { + path = alt_glob; + } + + const paths = glob(path); + if (paths instanceof FileError) { + return new ApplicationError("VSCODE", `failed to glob paths: ${paths}`); + } + + const entries: FileHistory[] = []; + for (const path of paths) { + if (!path.is_file) { + continue; + } + + // Read the JSON file + const string_data = readTextFile(path.full_path); + if (string_data instanceof FileError) { + console.warn(`Could not read file ${path.full_path}`); + continue; + } + + // Parse JSON file into the FileHistory format + const json_data: FileHistory = JSON.parse(string_data); + json_data.evidence = path.full_path; + const file_entries = json_data[ "entries" ] as Entries[]; + + // Loop through each history entry and read the contents + for (let i = 0; i < file_entries.length; i++) { + const entry = file_entries[ i ]; + if (entry === undefined) { + continue; + } + let hist_file = ""; + switch (platform) { + case PlatformType.Linux: + case PlatformType.Darwin: { + const dirs = path.full_path.split("/"); + dirs.pop(); + hist_file = `${dirs.join("/")}/${file_entries[ i ]?.id}`; + break; + } + case PlatformType.Windows: { + const dirs = path.full_path.split("\\"); + dirs.pop(); + hist_file = `${dirs.join("\\")}\\${file_entries[ i ]?.id}`; + break; + } + } + if (include_content) { + // Read file data + const history_data = readTextFile(hist_file); + if (history_data instanceof FileError) { + console.warn( + `Could not read history file ${hist_file}: ${history_data}`, + ); + continue; + } + // Base64 encode the history data + const history_encoded = encode(encodeBytes(history_data)); + entry.content = history_encoded; + } + + entry.timestamp = unixEpochToISO( + entry.timestamp as number, + ); + const flat_data: FileHistory = { + version: json_data.version, + resource: json_data.resource, + evidence: json_data.evidence, + message: `${json_data.resource} - History Entry: ${entry.id}`, + datetime: entry.timestamp, + timestamp_desc: "File Saved", + artifact: "File History", + data_type: "applications:vscode:filehistory:entry", + id: entry.id, + file_saved: entry.timestamp, + content: entry.content ?? "", + path: hist_file, + source: entry.source ?? "", + sourceDescription: entry.sourceDescription ?? "" + }; + entries.push(flat_data); + } + } + + return entries; +} + +/** + * Function to parse installed extensions for VSCode or VSCodium + * @param platform OS platform to get VScode extensions + * @param alt_path Alternative path to extensions.json file + * @returns Array of `Extensions` or `ApplicationError` + */ +export function getExtensions( + platform: PlatformType, + alt_path?: string, +): Extensions[] | ApplicationError { + // Get all user paths, unless alt_path is provided + let paths: string[] = []; + if (alt_path !== undefined) { + paths = [ alt_path ]; + } else { + let path = ""; + switch (platform) { + case PlatformType.Darwin: { + path = "/Users/*/.vscode*/extensions/extensions.json"; + break; + } + case PlatformType.Windows: { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = `${drive}:\\Users\\*\\.vscode*\\extensions\\extensions.json`; + break; + } + case PlatformType.Linux: { + path = "/home/*/.vscode*/extensions/extensions.json"; + } + } + + const glob_paths = glob(path); + if (glob_paths instanceof Error) { + return new ApplicationError("VSCODE", `failed to glob path: ${path}`); + } + for (const path of glob_paths) { + paths.push(path.full_path); + } + } + + const extensions: Extensions[] = []; + // Read extensions.json + for (const entry of paths) { + const extension_data = readTextFile(entry); + if (extension_data instanceof FileError) { + console.warn(`Could not read extension file ${entry}: ${extension_data}`); + continue; + } + const ext: Extensions = { + evidence: entry, + data: JSON.parse(extension_data), + }; + + extensions.push(ext); + } + + return extensions; +} + +/** + * Function to extract recent files and folders opened by VSCode. Also supports VSCodium. + * @param platform OS platform to get recent files for + * @param alt_path Optional alternative path to `storage.json` + * @returns Array of `RecentFiles` or `ApplicationError` + */ +export function vscodeRecentFiles(platform: PlatformType, alt_path?: string): RecentFiles[] | ApplicationError { + // Get all user paths, unless alt_path is provided + let paths: string[] = []; + if (alt_path !== undefined) { + paths = [ alt_path ]; + } else { + let path = ""; + switch (platform) { + case PlatformType.Darwin: { + path = "/Users/*/Library/Application Support/*Cod*/User/globalStorage/storage.json"; + break; + } + case PlatformType.Windows: { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = `${drive}:\\Users\\*\\AppData\\Roaming\\*Cod*\\User\\globalStorage\\storage.json`; + break; + } + case PlatformType.Linux: { + path = "/home/*/.config/*Cod*/User/globalStorage/storage.json"; + } + } + + const glob_paths = glob(path); + if (glob_paths instanceof Error) { + return new ApplicationError("VSCODE", `failed to glob path: ${path}`); + } + for (const path of glob_paths) { + paths.push(path.full_path); + } + } + + const recents: RecentFiles[] = []; + // Read storage.json + for (const entry of paths) { + const storage = readTextFile(entry); + if (storage instanceof FileError) { + console.warn(`Could not read storage.json file ${entry}: ${storage}`); + continue; + } + + const storage_data = JSON.parse(storage) as VscodeStorage; + for (const item of storage_data.lastKnownMenubarData.menus.File.items) { + if (item.id !== "submenuitem.MenubarRecentMenu" || item.submenu === undefined) { + continue; + } + + for (const entries of item.submenu.items) { + if (entries.uri === undefined) { + continue; + } + const recent: RecentFiles = { + path_type: entries.id == "openRecentFolder" ? RecentType.Folder : RecentType.File, + path: entries.uri.path, + enabled: entries.enabled ?? false, + label: entries.label ?? "", + external: entries.uri.external, + evidence: entry, + }; + recents.push(recent); + + } + } + } + + return recents; } \ No newline at end of file diff --git a/src/compression/decompress.ts b/src/compression/decompress.ts index c6fb2244..ebea56cf 100644 --- a/src/compression/decompress.ts +++ b/src/compression/decompress.ts @@ -1,91 +1,106 @@ -import { CompressionError } from "./errors"; - -/** - * Function to decompress zlib compressed data - * @param data The raw bytes to decompress - * @param wbits Value associated with zlib data. Use 0 (default) if there is no wbit value - * @param decom_size Decompress output size. Optional - * @returns Decompressed data or `CompressionError` - */ -export function decompress_zlib( - data: Uint8Array, - wbits: number = 0, - decom_size: number = 0, -): Uint8Array | CompressionError { - const max_wbit = 255; - if (wbits > max_wbit) { - return new CompressionError( - `ZLIB`, - `wbit value too large, should be less than 255`, - ); - } - try { - // @ts-expect-error: Custom Artemis function - const bytes: Uint8Array = js_decompress_zlib(data, wbits, decom_size); - return bytes; - } catch (err) { - return new CompressionError(`ZLIB`, `failed to decompress: ${err}`); - } -} - -/** - * Function to decompress gzip compressed data - * @param data Raw bytes to decompress - * @returns Decompressed data or `CompressionError` - */ -export function decompress_gzip( - data: Uint8Array, -): Uint8Array | CompressionError { - try { - // @ts-expect-error: Custom Artemis function - const bytes: Uint8Array = js_decompress_gzip(data); - return bytes; - } catch (err) { - return new CompressionError(`GZIP`, `failed to decompress: ${err}`); - } -} - -/** - * Function to decompress snappy compressed data - * @param data Raw bytes to decompress - * @returns Decompressed data or `CompressionError` - */ -export function decompress_snappy(data: Uint8Array): Uint8Array | CompressionError { - try { - // @ts-expect-error: Custom Artemis function - const bytes: Uint8Array = js_decompress_snappy(data); - return bytes; - } catch (err) { - return new CompressionError(`SNAPPY`, `failed to decompress: ${err}`); - } -} - -/** - * Function to decompress zstd compressed data - * @param data Raw bytes to decompress - * @returns Decompressed data or `CompressionError` - */ -export function decompress_zstd(data: Uint8Array): Uint8Array | CompressionError { - try { - // @ts-expect-error: Custom Artemis function - const bytes: Uint8Array = js_decompress_zstd(data); - return bytes; - } catch (err) { - return new CompressionError(`ZSTD`, `failed to decompress: ${err}`); - } -} - -/** - * Function to decompress lzvn compressed data - * @param data Raw bytes to decompress - * @returns Decompressed data or `CompressionError` - */ -export function decompress_lzvn(data: Uint8Array): Uint8Array | CompressionError { - try { - // @ts-expect-error: Custom Artemis function - const bytes: Uint8Array = js_decompress_lzvn(data); - return bytes; - } catch (err) { - return new CompressionError(`LZVN`, `failed to decompress: ${err}`); - } +import { CompressionError } from "./errors"; + +/** + * Function to decompress zlib compressed data + * @param data The raw bytes to decompress + * @param wbits Value associated with zlib data. Use 0 (default) if there is no wbit value + * @param decom_size Decompress output size. Optional + * @returns Decompressed data or `CompressionError` + */ +export function decompress_zlib( + data: Uint8Array, + wbits: number = 0, + decom_size: number = 0, +): Uint8Array | CompressionError { + const max_wbit = 255; + if (wbits > max_wbit) { + return new CompressionError( + `ZLIB`, + `wbit value too large, should be less than 255`, + ); + } + try { + // @ts-expect-error: Custom Artemis function + const bytes: Uint8Array = js_decompress_zlib(data, wbits, decom_size); + return bytes; + } catch (err) { + return new CompressionError(`ZLIB`, `failed to decompress: ${err}`); + } +} + +/** + * Function to decompress gzip compressed data + * @param data Raw bytes to decompress + * @returns Decompressed data or `CompressionError` + */ +export function decompress_gzip( + data: Uint8Array, +): Uint8Array | CompressionError { + try { + // @ts-expect-error: Custom Artemis function + const bytes: Uint8Array = js_decompress_gzip(data); + return bytes; + } catch (err) { + return new CompressionError(`GZIP`, `failed to decompress: ${err}`); + } +} + +/** + * Function to decompress snappy compressed data + * @param data Raw bytes to decompress + * @returns Decompressed data or `CompressionError` + */ +export function decompress_snappy(data: Uint8Array): Uint8Array | CompressionError { + try { + // @ts-expect-error: Custom Artemis function + const bytes: Uint8Array = js_decompress_snappy(data); + return bytes; + } catch (err) { + return new CompressionError(`SNAPPY`, `failed to decompress: ${err}`); + } +} + +/** + * Function to decompress zstd compressed data + * @param data Raw bytes to decompress + * @returns Decompressed data or `CompressionError` + */ +export function decompress_zstd(data: Uint8Array): Uint8Array | CompressionError { + try { + // @ts-expect-error: Custom Artemis function + const bytes: Uint8Array = js_decompress_zstd(data); + return bytes; + } catch (err) { + return new CompressionError(`ZSTD`, `failed to decompress: ${err}`); + } +} + +/** + * Function to decompress lzvn compressed data + * @param data Raw bytes to decompress + * @returns Decompressed data or `CompressionError` + */ +export function decompress_lzvn(data: Uint8Array): Uint8Array | CompressionError { + try { + // @ts-expect-error: Custom Artemis function + const bytes: Uint8Array = js_decompress_lzvn(data); + return bytes; + } catch (err) { + return new CompressionError(`LZVN`, `failed to decompress: ${err}`); + } +} + +/** + * Function to decompress lz4 compressed data. Only LZ4 decompressed using dictionary algorithm is supported + * @param data Raw bytes to decompress + * @returns Decompressed data or `CompressionError` + */ +export function decompress_lz4(data: Uint8Array, decom_size: number, initial_dict: Uint8Array): Uint8Array | CompressionError { + try { + // @ts-expect-error: Custom Artemis function + const bytes: Uint8Array = js_decompress_lz4(data, decom_size, initial_dict); + return bytes; + } catch (err) { + return new CompressionError(`LZ4`, `failed to decompress: ${err}`); + } } \ No newline at end of file diff --git a/src/compression/errors.ts b/src/compression/errors.ts index 9afe3e50..11fc295e 100644 --- a/src/compression/errors.ts +++ b/src/compression/errors.ts @@ -1,10 +1,11 @@ -import { ErrorBase } from "../utils/error"; - -export type ErrorName = - | "ZLIB" - | "GZIP" - | "SNAPPY" - | "ZSTD" - | "LZVN"; - -export class CompressionError extends ErrorBase { } +import { ErrorBase } from "../utils/error"; + +export type ErrorName = + | "ZLIB" + | "GZIP" + | "SNAPPY" + | "ZSTD" + | "LZVN" + | "LZ4"; + +export class CompressionError extends ErrorBase { } diff --git a/src/encoding/uuid.ts b/src/encoding/uuid.ts index ecc72095..d5a37f72 100644 --- a/src/encoding/uuid.ts +++ b/src/encoding/uuid.ts @@ -1,29 +1,29 @@ -import { Endian } from "../nom/helpers"; - -/** - * Format bytes to a GUID value. Must specify Endianess - * @param format `Endian` format to use when converting to GUID. BE common on macOS. LE common on Windows - * @param data Raw bytes to convert to GUID - * @returns GUID string - */ -export function formatGuid(format: Endian, data: Uint8Array): string { - if (format === Endian.Be) { - // @ts-expect-error: Custom Artemis function - const result = js_format_guid_be_bytes(data); - return result; - } - - // @ts-expect-error: Custom Artemis function - const result = js_format_guid_le_bytes(data); - return result; -} - -/** - * Generate a UUID string - * @returns A UUID string - */ -export function generateUuid(): string { - // @ts-expect-error: Custom Artemis function - const result = js_generate_uuid(); - return result; -} +import { Endian } from "../nom/helpers"; + +/** + * Format bytes to a GUID value. Must specify Endianness + * @param format `Endian` format to use when converting to GUID. BE common on macOS. LE common on Windows + * @param data Raw bytes to convert to GUID + * @returns GUID string + */ +export function formatGuid(format: Endian, data: Uint8Array): string { + if (format === Endian.Be) { + // @ts-expect-error: Custom Artemis function + const result = js_format_guid_be_bytes(data); + return result; + } + + // @ts-expect-error: Custom Artemis function + const result = js_format_guid_le_bytes(data); + return result; +} + +/** + * Generate a UUID string + * @returns A UUID string + */ +export function generateUuid(): string { + // @ts-expect-error: Custom Artemis function + const result = js_generate_uuid(); + return result; +} diff --git a/src/environment/env.ts b/src/environment/env.ts index 0c0ac062..9ece21cd 100644 --- a/src/environment/env.ts +++ b/src/environment/env.ts @@ -1,37 +1,37 @@ -import { platform } from "../system/systeminfo"; - -/** - * Get all Environment variables associated with artemis process - * @returns Record list of Environment variable values - */ -export function listEnv(): Record { - // @ts-expect-error: Custom Artemis function - const data: Record = js_env(); - return data; -} - -/** - * Get a single Environment variable value. Returns empty string if variable does not exist - * @param key Environment variable name - * @returns Value of the provided Environment variable name - */ -export function getEnvValue(key: string): string { - // @ts-expect-error: Custom Artemis function - const data: string = js_env_value(key); - return data; -} - -/** - * Function to get the Windows SystemDrive letter - * @returns The SystemDrive letter for Windows or empty string if called on non-Windows platforms - */ -export function getSystemDrive(): string { - if (platform().toLowerCase().includes("windows")) { - return ""; - } - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - return drive; +import { platform } from "../system/systeminfo"; + +/** + * Get all Environment variables associated with artemis process + * @returns Record list of Environment variable values + */ +export function listEnv(): Record { + // @ts-expect-error: Custom Artemis function + const data: Record = js_env(); + return data; +} + +/** + * Get a single Environment variable value. Returns empty string if variable does not exist + * @param key Environment variable name + * @returns Value of the provided Environment variable name + */ +export function getEnvValue(key: string): string { + // @ts-expect-error: Custom Artemis function + const data: string = js_env_value(key); + return data; +} + +/** + * Function to get the Windows SystemDrive letter + * @returns The SystemDrive letter for Windows or empty string if called on non-Windows platforms + */ +export function getSystemDrive(): string { + if (!platform().toLowerCase().includes("windows")) { + return ""; + } + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + return drive; } \ No newline at end of file diff --git a/src/esxi/accounts.ts b/src/esxi/accounts.ts new file mode 100644 index 00000000..430fa91a --- /dev/null +++ b/src/esxi/accounts.ts @@ -0,0 +1,81 @@ +import { readLines, stat } from "../../mod"; +import { Accounts } from "../../types/esxi/accounts"; +import { FileError } from "../filesystem/errors"; +import { EsxiError } from "./error"; + +/** + * Parse user accounts + * @param alt_path Optional path to /etc/passwd + * @returns Array of `Accounts` or `EsxiError` + */ +export function esxiAccounts(alt_path?: string): Accounts[] | EsxiError { + let user_passwd = "/etc/passwd"; + if (alt_path !== undefined) { + user_passwd = alt_path; + } + + const users: Accounts[] = []; + // 1 line per account. Unlikely to see over 100 accounts on ESXi? + const lines = readLines(user_passwd, 0, 100); + if (lines instanceof FileError) { + return new EsxiError(`ACCOUNTS`, `failed to read ${user_passwd}: ${lines}`); + } + + + let datetime = "1970-01-01T00:00:00.000Z"; + // Get las modified time of /etc/passwd file + const meta = stat(user_passwd); + if (!(meta instanceof FileError)) { + datetime = meta.modified; + } + + for (const entry of lines) { + // Semicolons should not be allowed in user name or description fields + const entries = entry.split(":"); + + const value: Accounts = { + message: `ESXi account '${entries.at(0) ?? "Unknown"}'`, + datetime, + timestamp_desc: "Passwd File Modified", + artifact: "ESXi User Account", + data_type: "esxi:accounts:entry", + evidence: user_passwd, + uid: Number(entries.at(2) ?? 0), + gid: Number(entries.at(3) ?? 0), + info: entries.at(4) ?? "Unknown", + shell: entries.at(6) ?? "Unknown", + home: entries.at(5) ?? "Unknown", + }; + users.push(value); + } + + + return users; +} + +/** + * Function to test ESXi accounts parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the ESXi accounts parsing + */ +export function testEsxiAccounts(): void { + const test = "../../test_data/esxi/accounts/passwd.txt"; + const results = esxiAccounts(test); + if (results instanceof EsxiError) { + throw results; + } + + if (results.length !== 4) { + throw `Got ${results.length} expected 4.......esxiAccounts ❌`; + } + + if (results[ 2 ]?.message !== "ESXi account 'vpxuser'") { + throw `Got ${results[ 2 ]?.message} expected "ESXi account 'vpxuser".......esxiAccounts ❌`; + } + + if (!results[ 3 ]?.evidence.includes("passwd.txt")) { + throw `Got ${results[ 2 ]?.evidence} expected "passwd.txt".......esxiAccounts ❌`; + } + + console.info(` Function esxiAccounts ✅`); +} \ No newline at end of file diff --git a/src/esxi/error.ts b/src/esxi/error.ts new file mode 100644 index 00000000..726dab56 --- /dev/null +++ b/src/esxi/error.ts @@ -0,0 +1,9 @@ +import { ErrorBase } from "../utils/error"; + +export type ErrorName = + | "SHELLHISTORY" + | "VIBPACKAGE" + | "SYSLOG" + | "ACCOUNTS"; + +export class EsxiError extends ErrorBase { } diff --git a/src/esxi/logs/shell.ts b/src/esxi/logs/shell.ts new file mode 100644 index 00000000..b8650eff --- /dev/null +++ b/src/esxi/logs/shell.ts @@ -0,0 +1,145 @@ +import { ShellHistory } from "../../../types/esxi/logs/shell"; +import { FileError } from "../../filesystem/errors"; +import { glob, readLines } from "../../filesystem/files"; +import { EsxiError } from "../error"; + +/** + * Function to parse ESXi `shell.log` file + * @param alt_path Optional alternative glob to `shell.log` + * @returns Array of `ShellHistory` or `EsxiError` + */ +export function shellLogHistory(alt_path?: string): ShellHistory[] | EsxiError { + let shell_glob = "/vmfs/volumes/*/log/shell.log"; + if (alt_path !== undefined) { + shell_glob = alt_path; + } + + const paths = glob(shell_glob); + if (paths instanceof FileError) { + return new EsxiError(`SHELLHISTORY`, `failed to glob: ${shell_glob}: ${paths}`); + } + + let history: ShellHistory[] = []; + for (const entry of paths) { + if (!entry.is_file) { + continue; + } + + const values = readHistory(entry.full_path); + if (values instanceof EsxiError) { + continue; + } + + history = history.concat(values); + + } + + return history; +} + +/** + * Parse each line of the ESXi `shell.log` file + * @param full_path Path to the `shell.log` file + * @returns Array of `ShellHistory` or `EsxiError` + */ +function readHistory(full_path: string): ShellHistory[] | EsxiError { + const limit = 200; + let offset = 0; + + const values: ShellHistory[] = []; + while (true) { + const lines = readLines(full_path, offset, limit); + if (lines instanceof FileError) { + return new EsxiError(`SHELLHISTORY`, `could not read file ${full_path}: ${lines}`); + } + offset += limit; + for (const line of lines) { + const value: ShellHistory = { + message: "", + datetime: "", + timestamp_desc: "Shell Command Execution", + artifact: "ESXi Shell History", + data_type: "esxi:shell:entry", + pid: 0, + account: "", + command: "", + evidence: full_path, + category: "", + }; + + if (line.includes(" shell[")) { + const entry = line.split(" "); + value.datetime = entry.at(0) ?? "1970-01-01T00:00:00.000Z"; + value.category = entry.at(1) ?? ""; + const pid_string = entry.at(2) ?? 0; + if (pid_string !== 0) { + const pid_value = pid_string.split("[").at(1) ?? "0"; + value.pid = Number(pid_value.replace("]:", "")); + } + const account = entry.at(3) ?? ""; + if (!account.startsWith("[")) { + value.message = entry.slice(3).join(" "); + value.command = entry.slice(3).join(" "); + values.push(value); + continue; + } + + value.account = account.replace("[", "").replace("]:", ""); + value.message = entry.slice(4).join(" "); + value.command = entry.slice(4).join(" "); + values.push(value); + continue; + } + + const entry = line.split(" "); + value.datetime = entry.at(0) ?? "1970-01-01T00:00:00.000Z"; + value.category = entry.at(1) ?? ""; + const pid_string = entry.at(2) ?? 0; + if (pid_string !== 0) { + const pid_value = pid_string.split("[").at(1) ?? "0"; + value.pid = Number(pid_value.replace("]:", "")); + } + value.message = entry.slice(3).join(" "); + value.command = entry.slice(3).join(" "); + values.push(value); + } + + + if (lines.length < limit) { + break; + } + } + + return values; +} + +/** + * Function to test ESXi shell log parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the ESXi shell log parsing + */ +export function testShellLogHistory(): void { + const test = "../../test_data/esxi/logs/shell.log"; + const results = shellLogHistory(test); + if (results instanceof EsxiError) { + throw results; + } + + if (results.length !== 164) { + throw `Got ${results.length} expected 164.......shellLogHistory ❌`; + } + + if (results[ 12 ]?.message !== "ls") { + throw `Got ${results[ 12 ]?.message} expected "ls".......shellLogHistory ❌`; + } + + console.info(` Function shellLogHistory ✅`); + + + const bad_file = readHistory("fake path"); + if (!(bad_file instanceof EsxiError)) { + throw bad_file; + } + + console.info(` Function readHistory ✅`); +} \ No newline at end of file diff --git a/src/esxi/logs/syslog.ts b/src/esxi/logs/syslog.ts new file mode 100644 index 00000000..2650bbd1 --- /dev/null +++ b/src/esxi/logs/syslog.ts @@ -0,0 +1,201 @@ +import { Syslog } from "../../../types/esxi/logs/syslog"; +import { decompress_gzip } from "../../compression/decompress"; +import { CompressionError } from "../../compression/errors"; +import { extractUtf8String } from "../../encoding/strings"; +import { FileError } from "../../filesystem/errors"; +import { glob, readFile, readLines } from "../../filesystem/files"; +import { NomError } from "../../nom/error"; +import { take, takeUntil } from "../../nom/parsers"; +import { EsxiError } from "../error"; + +/** + * Function to parse ESXi `syslog.log` file + * @param alt_path Optional alternative glob to `syslog.log` + * @returns Array of `Syslog` or `EsxiError` + */ +export function sysLogEsxi(alt_path?: string): Syslog[] | EsxiError { + let syslog_glob = "/vmfs/volumes/*/log/syslog.log*"; + if (alt_path !== undefined) { + syslog_glob = alt_path; + } + + const paths = glob(syslog_glob); + if (paths instanceof FileError) { + return new EsxiError(`SYSLOG`, `failed to glob: ${syslog_glob}: ${paths}`); + } + + let logs: Syslog[] = []; + for (const entry of paths) { + if (!entry.is_file) { + continue; + } + + if (entry.filename.endsWith(".gz")) { + const values = decompressSyslog(entry.full_path); + if (values instanceof EsxiError) { + continue; + } + logs = logs.concat(values); + } + + const values = readSyslog(entry.full_path); + if (values instanceof EsxiError) { + continue; + } + + logs = logs.concat(values); + + } + + return logs; +} + +/** + * Parse each line of the ESXi `syslog.log` file + * @param full_path Path to the `syslog.log` file + * @returns Array of `Syslog` or `EsxiError` + */ +function readSyslog(full_path: string): Syslog[] | EsxiError { + const limit = 200; + let offset = 0; + + const values: Syslog[] = []; + while (true) { + const lines = readLines(full_path, offset, limit); + if (lines instanceof FileError) { + return new EsxiError(`SYSLOG`, `could not read file ${full_path}: ${lines}`); + } + offset += limit; + for (const line of lines) { + values.push(extractLine(line, full_path)); + } + + if (lines.length < limit) { + break; + } + } + + return values; +} + +/** + * Function to decompress gzip compressed syslog files + * @param full_path Full path to compressed syslog file + * @returns Array of `Syslog` or `EsxiError` + */ +function decompressSyslog(full_path: string): Syslog[] | EsxiError { + let bytes: Uint8Array | FileError | CompressionError; + bytes = readFile(full_path); + if (bytes instanceof FileError) { + return new EsxiError(`SYSLOG`, `failed to read file ${full_path}: ${bytes}`); + } + + bytes = decompress_gzip(bytes); + if (bytes instanceof CompressionError) { + return new EsxiError(`SYSLOG`, `failed to decompress file ${full_path}: ${bytes}`); + } + + const newline = new Uint8Array([ 10 ]); + const values: Syslog[] = []; + while (bytes.length !== 0) { + let decom_bytes = takeUntil(bytes, newline); + if (decom_bytes instanceof NomError) { + break; + } + const line = extractLine(extractUtf8String(decom_bytes.nommed as Uint8Array), full_path); + values.push(line); + + // Nom the newline now + decom_bytes = take(decom_bytes.remaining, 1); + if (decom_bytes instanceof NomError) { + break; + } + bytes = decom_bytes.remaining as Uint8Array; + } + return values; +} + +/** + * Parse a syslog line entry + * @param line A syslog line entry + * @param evidence Full path to the syslog file + * @returns `Syslog` object + */ +function extractLine(line: string, evidence: string): Syslog { + const value: Syslog = { + message: "", + datetime: "", + pid: 0, + evidence, + category: "", + process: "", + timestamp_desc: "Syslog Entry Generated", + artifact: "ESXi Syslog", + data_type: "esxi:syslog:entry" + }; + + const entry = line.split(" "); + value.datetime = entry.at(0) ?? "1970-01-01T00:00:00.000Z"; + value.category = entry.at(1) ?? ""; + const pid_string = entry.at(2) ?? ""; + const pid_strings = pid_string.split("["); + const pid_value = pid_strings.at(1) ?? "0"; + value.pid = Number(pid_value.replace("]:", "")); + value.process = pid_strings.at(0) ?? ""; + + value.message = entry.slice(3).join(" "); + + return value; +} + +/** + * Function to test ESXi syslog log parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the ESXi syslog log parsing + */ +export function testSyslogEsxi(): void { + const test = "../../test_data/esxi/logs/syslog*"; + const results = sysLogEsxi(test); + if (results instanceof EsxiError) { + throw results; + } + + if (results.length !== 9435) { + throw `Got ${results.length} expected 8462.......sysLogEsxi ❌`; + } + + if (results[ 12 ]?.message !== "executing stop for daemon storageRM.") { + throw `Got ${results[ 12 ]?.message} expected "executing stop for daemon storageRM.".......sysLogEsxi ❌`; + } + + console.info(` Function sysLogEsxi ✅`); + + const bad_file = readSyslog("fake path"); + if (!(bad_file instanceof EsxiError)) { + throw bad_file; + } + + console.info(` Function readSyslog ✅`); + + const result = extractLine("2026-04-05T00:01:00.925Z Db(15) backup-check[136534]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedentv-2670021833313565255.xml'", test); + if (result.datetime !== "2026-04-05T00:01:00.925Z") { + throw `Got ${result.datetime} expected "2026-04-05T00:01:00.925Z".......extractLine ❌`; + } + + console.info(` Function extractLine ✅`); + + const gz_results = decompressSyslog("../../test_data/esxi/logs/syslog.5.gz"); + if (gz_results instanceof EsxiError) { + throw gz_results; + } + + if (gz_results.length !== 8462) { + throw `Got ${gz_results.length} expected 8462.......decompressSyslog ❌`; + } + + if (gz_results[ 204 ]?.message !== "No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedentv-7929314746453517149.xml'") { + throw `Got ${gz_results[ 204 ]?.message} expected "No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedentv-7929314746453517149.xml'".......decompressSyslog ❌`; + } + + console.info(` Function decompressSyslog ✅`); +} \ No newline at end of file diff --git a/src/esxi/vib.ts b/src/esxi/vib.ts new file mode 100644 index 00000000..ac98bd92 --- /dev/null +++ b/src/esxi/vib.ts @@ -0,0 +1,164 @@ +import { glob } from "../../mod"; +import { RawVibXml, VibInfo, VibPayload } from "../../types/esxi/vib"; +import { EncodingError } from "../encoding/errors"; +import { readXml } from "../encoding/xml"; +import { FileError } from "../filesystem/errors"; +import { EsxiError } from "./error"; + +/** + * Parse vSphere Installation Bundles (VIB) package info + * @param alt_path Optional alternative glob path to folder containing VIB xml files + * @returns Array of `VibInfo` or `EsxiError` + */ +export function getVibs(alt_path?: string): VibInfo[] | EsxiError { + let vib_glob = "/var/db/esximg/vibs/*.xml"; + if (alt_path !== undefined) { + vib_glob = alt_path; + } + + const paths = glob(vib_glob); + if (paths instanceof FileError) { + return new EsxiError(`VIBPACKAGE`, `failed to glob: ${vib_glob}: ${paths}`); + } + + const vibs: VibInfo[] = []; + for (const entry of paths) { + if (!entry.is_file || !entry.filename.endsWith(".xml")) { + continue; + } + + const value = parseVib(entry.full_path); + if (value instanceof EsxiError) { + continue; + } + + vibs.push(value); + } + + return vibs; +} + +/** + * Parse the VIB xml info + * @param full_path Path to VIB xml file + * @returns `VibInfo` or `EsxiError` + */ +function parseVib(full_path: string): VibInfo | EsxiError { + const info = readXml(full_path); + if (info instanceof EncodingError) { + return new EsxiError(`VIBPACKAGE`, `could not read vib package at ${full_path}: ${info}`); + } + + const raw_vib = info as unknown as RawVibXml; + + let timestamp; + let message; + if (Array.isArray(raw_vib.vib.installdate)) { + timestamp = raw_vib.vib.installdate.at(0) ?? "1970-01-01T00:00:00.000Z"; + message = `VIB package '${raw_vib.vib.name.at(0) ?? "Unknown"}' installed`; + } else { + timestamp = raw_vib.vib[ "release-date" ].at(0) ?? "1970-01-01T00:00:00.000Z"; + message = `VIB package '${raw_vib.vib.name.at(0) ?? "Unknown"}' released`; + } + let datetime = "1970-01-01T00:00:00.000Z"; + let timezone = "+00:00"; + if (timestamp.includes("+")) { + datetime = `${timestamp.split("+").at(0) ?? "1970-01-01T00:00:00.000"}Z`; + timezone = `+${timestamp.split("+").at(1) ?? "00:00"}`; + } else if (timestamp.includes("-")) { + const index = timestamp.lastIndexOf("-"); + datetime = `${timestamp.slice(0, index)}Z`; + + timezone = `${timestamp.slice(index)}`; + } + const vib: VibInfo = { + message, + datetime, + timestamp_desc: raw_vib.vib.installdate !== undefined ? "VIB Package Installed" : "VIB Package Released", + install_date: raw_vib.vib.installdate !== undefined ? raw_vib.vib.installdate.at(0) ?? "1970-01-01T00:00:00.000Z" : "1970-01-01T00:00:000Z", + artifact: "ESXi VIB Package", + data_type: "esxi:vib:entry", + vib_version: Number(raw_vib.vib.$.version.at(0) ?? 0), + name: raw_vib.vib.name.at(0) ?? "Unknown", + version: raw_vib.vib.version.at(0) ?? "Unknown", + vendor: raw_vib.vib.vendor.at(0) ?? "Unknown", + summary: raw_vib.vib.summary.at(0) ?? "Unknown", + description: raw_vib.vib.description.at(0) ?? "Unknown", + release_date: raw_vib.vib[ "release-date" ].at(0) ?? "1970-01-01T00:00:00.000Z", + level: raw_vib.vib[ "acceptance-level" ].at(0) ?? "Unknown", + vib_type: raw_vib.vib.type.at(0) ?? "Unknown", + payloads: [], + urls: [], + evidence: full_path, + timezone, + installed: raw_vib.vib.installdate !== undefined ? true : false + }; + + for (const url of raw_vib.vib.urls) { + if (typeof url === 'string') { + break; + } + for (const value of url.url) { + vib.urls.push(value._); + } + } + + const payloads = raw_vib.vib.payloads; + for (const entry of payloads) { + for (const value of entry.payload) { + const payload_info: VibPayload = { + payload_type: value.$.type, + uncompressed_size: Number(value.$[ "uncompressed-size" ] ?? 0), + size: Number(value.$.size), + sha1_compressed: "", + sha256_compressed: "", + sha256: "" + }; + // Get hashes now + for (const hash of value.checksum) { + if (hash.$[ "verify-process" ] === "gunzip" && hash.$[ "checksum-type" ] === "sha-256") { + payload_info.sha256_compressed = hash._; + } else if (hash.$[ "verify-process" ] === "gunzip" && hash.$[ "checksum-type" ] === "sha-1") { + payload_info.sha1_compressed = hash._; + } else if (hash.$[ "checksum-type" ] === "sha-256") { + payload_info.sha256 = hash._; + } + } + vib.payloads.push(payload_info); + } + } + return vib; +}; + + +/** + * Function to test ESXi VIB package parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the ESXi VIB package parsing + */ +export function testGetVibs(): void { + const test = "../../test_data/esxi/vibs/artemis--2860263652605340392.xml"; + const results = getVibs(test); + if (results instanceof EsxiError) { + throw results; + } + + if (results[ 0 ]?.message != "VIB package 'artemis' installed") { + throw `Got ${results[ 0 ]?.message} expected "VIB package 'artemis' installed".......getVibs ❌`; + } + + if (results[ 0 ]?.datetime != "2026-04-04T19:06:39.106766Z") { + throw `Got ${results[ 0 ].datetime} expected "2026-04-04T19:06:39.106766Z".......getVibs ❌`; + } + + + console.info(` Function getVibs ✅`); + + + const bad_file = parseVib("fake path"); + if (!(bad_file instanceof EsxiError)) { + throw bad_file; + } + + console.info(` Function parseVib ✅`); +} \ No newline at end of file diff --git a/src/filesystem/acquire.ts b/src/filesystem/acquire.ts deleted file mode 100644 index 9c3fcea1..00000000 --- a/src/filesystem/acquire.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Output } from "../system/output"; -import { FileError } from "./errors"; - -/** - * Function to acquire a file from the system - * @param path Path to file to acquire - * @param output `Output` structure to pass to artemis - * @returns True on success or `SystemError` - */ -export function acquireFile(path: string, output: Output): boolean | FileError { - try { - // @ts-expect-error: Custom Artemis function - const status: boolean = js_acquire_file(path, output); - return status; - } catch (err) { - return new FileError(`ACQUIRE`, `failed to acquire file: ${err}`); - } -} diff --git a/src/filesystem/errors.ts b/src/filesystem/errors.ts index 125c542e..9582e9e5 100644 --- a/src/filesystem/errors.ts +++ b/src/filesystem/errors.ts @@ -1,13 +1,13 @@ -import { ErrorBase } from "../utils/error"; - -export type ErrorName = - | "READ_DIR" - | "STAT" - | "HASH" - | "READ_TEXT_FILE" - | "READ_FILE" - | "GLOB" - | "ACQUIRE" - | "READ_LINES"; - -export class FileError extends ErrorBase { } +import { ErrorBase } from "../utils/error"; + +export type ErrorName = + | "READ_DIR" + | "STAT" + | "HASH" + | "READ_TEXT_FILE" + | "READ_FILE" + | "GLOB" + | "READ_LINES" + | "READER"; + +export class FileError extends ErrorBase { } diff --git a/src/filesystem/reader.ts b/src/filesystem/reader.ts new file mode 100644 index 00000000..26fce7ab --- /dev/null +++ b/src/filesystem/reader.ts @@ -0,0 +1,39 @@ +import { FileError } from "./errors"; + +/** + * Class that exposes a simple reader to allow caller to stream reading a file + */ +export class BufReader { + private reader: unknown; + /** + * Start reading a file + * @param path Full path to file that should be streamed + */ + constructor(path: string) { + // @ts-expect-error: Custom Artemis class + this.reader = new JsBufReader(path); + } + + /** + * Function to read bytes from a file + * @param offset Offset to start reading bytes + * @param bytes How many bytes that should be read + * @returns `Uint8Array` or `FileError` + */ + public readBytes(offset: number, bytes: number): Uint8Array | FileError { + if (offset < 0) { + return new FileError(`READER`, `Cannot seek to negative offset ${offset}`); + } + if (bytes < 0) { + return new FileError(`READER`, `Cannot read to negative bytes ${bytes}`); + } + + try { + // @ts-expect-error: Custom Artemis class function + const results = this.reader.read(offset, bytes); + return results; + } catch (err) { + return new FileError(`READER`, `could not read bytes ${err}`); + } + } +} \ No newline at end of file diff --git a/src/freebsd/packages.ts b/src/freebsd/packages.ts index 6fb5185b..88b09a6f 100644 --- a/src/freebsd/packages.ts +++ b/src/freebsd/packages.ts @@ -54,7 +54,8 @@ export function getPkgs(offset: number, limit: number, alt_path?: string): Pkg[] datetime: unixEpochToISO(entry[ "time" ] as number ?? 0), timestamp_desc: "Package Installed", artifact: "FreeBSD PKG", - data_type: "freebsd:pkg:entry" + data_type: "freebsd:pkg:entry", + evidence: path, }; pkg_entries.push(pkg_entry); } diff --git a/src/linux/abrt.ts b/src/linux/abrt.ts index a44d82c0..47fb0887 100644 --- a/src/linux/abrt.ts +++ b/src/linux/abrt.ts @@ -62,7 +62,7 @@ async function readAbrtDir(path: string): Promise { hostname: "", last_occurrence: "", user: "", - data_directory: path, + evidence: path, backtrace: "", environment: "", home: "", diff --git a/src/linux/firmware.ts b/src/linux/firmware.ts index d87b7fba..8f863014 100644 --- a/src/linux/firmware.ts +++ b/src/linux/firmware.ts @@ -49,7 +49,8 @@ export function firmwareHistory(alt_path?: string): FirmwareHistory[] | LinuxErr datetime: unixEpochToISO(entry[ "device_created" ] as number), timestamp_desc: "Firmware Device Created", artifact: "Firmware Updates", - data_type: "linux:firmware:entry" + data_type: "linux:firmware:entry", + evidence: path, }; const meta = firm.metadata.split(";"); for (const value of meta) { diff --git a/src/linux/gnome/epiphany.ts b/src/linux/gnome/epiphany.ts index 4c688717..14ef26d4 100644 --- a/src/linux/gnome/epiphany.ts +++ b/src/linux/gnome/epiphany.ts @@ -79,7 +79,7 @@ export class Epiphany { thumbnail_update_time: unixEpochToISO(entry["thumbnail_update_time"] as bigint), hidden_from_overview: Boolean(entry["hidden_from_overview"] as number), visit_type: this.visitType(entry["visit_type"] as number), - db_path: db, + evidence: db, target_url: entry["url"] as string | null ?? "", title: entry["title"] as string | null ?? "", referring_visit: entry["referring_visit"] as string | null, @@ -133,7 +133,7 @@ export class Epiphany { is_secure: false, is_http_only: false, same_site: false, - db_path: db, + evidence: db, message: `Cookie for ${entry["host"] as string | null} | ${entry["name"] as string | null ?? ""}`, datetime: "", timestamp_desc: "Cookie Expires", @@ -191,7 +191,7 @@ export class Epiphany { url: value["$"]["url"] ?? "", title: value["$"]["title"] ?? "", history: value["$"]["history"] ?? "", - session_path: xml_file, + evidence: xml_file, message: `Session for ${value["$"]["url"] ?? ""}`, datetime: "1970-01-01T00:00:00.000Z", timestamp_desc: "N/A", @@ -232,7 +232,7 @@ export class Epiphany { const perms: EpiphanyPermissions = { url: "", permissions: {}, - file_path: perm_file, + evidence: perm_file, message: "", datetime: "1970-01-01T00:00:00.000Z", timestamp_desc: "N/A", @@ -283,7 +283,7 @@ export class Epiphany { printer: "", pages: "", collate: false, - file_path: print_file, + evidence: print_file, message: "", datetime: "1970-01-01T00:00:00.000Z", timestamp_desc: "N/A", diff --git a/src/linux/gnome/extensions.ts b/src/linux/gnome/extensions.ts index b9124053..1be26c5f 100644 --- a/src/linux/gnome/extensions.ts +++ b/src/linux/gnome/extensions.ts @@ -42,9 +42,9 @@ export function getGnomeExtensions( } const ext_data: Extension = JSON.parse(data); - ext_data.extension_path = extension.full_path; + ext_data.evidence = extension.full_path; - const meta = stat(ext_data.extension_path); + const meta = stat(ext_data.evidence); if (!(meta instanceof FileError)) { ext_data.created = meta.created; ext_data.accessed = meta.accessed; diff --git a/src/linux/gnome/gedit.ts b/src/linux/gnome/gedit.ts index b439c0cd..69ef8d49 100644 --- a/src/linux/gnome/gedit.ts +++ b/src/linux/gnome/gedit.ts @@ -56,7 +56,7 @@ export function geditRecentFiles( const recent: RecentFiles = { path: doc[ "$" ][ "uri" ] ?? "", accessed: unixEpochToISO(Number(doc[ "$" ][ "atime" ])), - gedit_source: entry.full_path, + evidence: entry.full_path, message: `Accessed: ${doc[ "$" ][ "uri" ] ?? ""}`, datetime: `${unixEpochToISO(Number(doc[ "$" ][ "atime" ])) ?? "1970-01-01T00:00:00.000Z"}`, timestamp_desc: "Last Accessed", diff --git a/src/linux/gnome/gvfs.ts b/src/linux/gnome/gvfs.ts index 80c3726d..a7314fa5 100644 --- a/src/linux/gnome/gvfs.ts +++ b/src/linux/gnome/gvfs.ts @@ -426,7 +426,7 @@ function getChildren( parents: string[], base_time: number, keywords: string[], - source: string, + evidence: string, ): GvfsEntry[] | NomError { const child_data = take(data, offset); if (child_data instanceof NomError) { @@ -511,7 +511,7 @@ function getChildren( metadata, last_change: unixEpochToISO(last_change.value + base_time), path: `${parents.join("/")}/${name}`.replace("//", "/"), - source, + evidence, message: `${parents.join("/")}/${name}`.replace("//", "/"), datetime: unixEpochToISO(last_change.value + base_time), timestamp_desc: "Last Changed", @@ -527,7 +527,7 @@ function getChildren( parents, base_time, keywords, - source, + evidence, ); if (nested_child instanceof NomError) { return children; diff --git a/src/linux/gnome/usage.ts b/src/linux/gnome/usage.ts index 980fef9e..4de17ee0 100644 --- a/src/linux/gnome/usage.ts +++ b/src/linux/gnome/usage.ts @@ -66,7 +66,7 @@ export function gnomeAppUsage(alt_path?: string): AppUsage[] | LinuxError { id: app[ "$" ][ "id" ] ?? "", score: Number(app[ "$" ][ "score" ]), "last-seen": unixEpochToISO(Number(app[ "$" ][ "last-seen" ])), - source: entry.full_path, + evidence: entry.full_path, message: app[ "$" ][ "id" ] ?? "", datetime: unixEpochToISO(Number(app[ "$" ][ "last-seen" ])), timestamp_desc: "Last Seen", diff --git a/src/linux/kde/falkon/falkon.ts b/src/linux/kde/falkon/falkon.ts index c40abead..b398e91e 100644 --- a/src/linux/kde/falkon/falkon.ts +++ b/src/linux/kde/falkon/falkon.ts @@ -122,7 +122,7 @@ export class Falkon { location: BookmarkLocation.Bar, url: entry[ "description" ] as string, visit_count: entry[ "visit_count" ] as number, - path: book_path, + evidence: book_path, version: path.version, message: entry[ "description" ] as string, datetime: created, @@ -142,7 +142,7 @@ export class Falkon { location: BookmarkLocation.Menu, url: entry[ "url" ] as string, visit_count: entry[ "visit_count" ] as number, - path: book_path, + evidence: book_path, version: path.version, message: entry[ "url" ] as string, datetime: created, @@ -162,7 +162,7 @@ export class Falkon { location: BookmarkLocation.Other, url: entry[ "description" ] as string, visit_count: entry[ "visit_count" ] as number, - path: book_path, + evidence: book_path, version: path.version, message: entry[ "description" ] as string, datetime: created, diff --git a/src/linux/kde/falkon/sqlite.ts b/src/linux/kde/falkon/sqlite.ts index 5e10e515..c6e49faf 100644 --- a/src/linux/kde/falkon/sqlite.ts +++ b/src/linux/kde/falkon/sqlite.ts @@ -30,7 +30,7 @@ export function falkonHistory(paths: FalkonProfile[], platform: PlatformType, un id: entry[ "id" ] as number, url: entry[ "url" ] as string, unfold: undefined, - db_path: full_path, + evidence: full_path, version: path.version, title: entry[ "title" ] as string | null, visited: unixEpochToISO(entry[ "date" ] as number), @@ -95,7 +95,7 @@ export function falkonCookie(paths: FalkonProfile[], platform: PlatformType, que source_type: getChromiumCookieType(entry[ "source_type" ] as number), has_cross_site_ancestor: !!(entry[ "has_cross_site_ancestor" ] as number), message: entry[ "host_key" ] as string, - db_path: full_path, + evidence: full_path, version: path.version, datetime: unixEpochToISO(webkitToUnixEpoch(entry[ "creation_utc" ] as number)), timestamp_desc: "Falkon Cookie Created", diff --git a/src/linux/kde/kate.ts b/src/linux/kde/kate.ts index 18a8b213..54c3ae45 100644 --- a/src/linux/kde/kate.ts +++ b/src/linux/kde/kate.ts @@ -47,7 +47,7 @@ export function kateRecentFiles(alt_path?: string): RecentFiles[] { if (line.startsWith("File") && line.includes("=")) { const file_value = line.split("="); const file: RecentFiles = { - session_file: value.full_path, + evidence: value.full_path, // Linux file names can contain '=' symbol file: file_value.slice(1).join("="), session_file_created, diff --git a/src/linux/rpm.ts b/src/linux/rpm.ts index cef4c23e..839d9afe 100644 --- a/src/linux/rpm.ts +++ b/src/linux/rpm.ts @@ -65,6 +65,7 @@ export function getRpmInfo( } const rpm: RpmPackages = { + evidence: path, name: "", version: "", release: "", diff --git a/src/linux/sqlite/wtmpdb.ts b/src/linux/sqlite/wtmpdb.ts index 32137701..65f5f7cc 100644 --- a/src/linux/sqlite/wtmpdb.ts +++ b/src/linux/sqlite/wtmpdb.ts @@ -36,7 +36,8 @@ export function queryLogons(alt_path?: string): LastLogons[] | LinuxError { datetime: unixEpochToISO(entry[ "Login" ] as number ?? 0), timestamp_desc: "User Logon", artifact: "wtmpdb Logons", - data_type: "linux:wtmpdb:entry" + data_type: "linux:wtmpdb:entry", + evidence: path, }; values.push(logon); } diff --git a/src/macos/bom.ts b/src/macos/bom.ts index 196dda89..3ddaf7db 100644 --- a/src/macos/bom.ts +++ b/src/macos/bom.ts @@ -160,7 +160,7 @@ export function parseBom(path: string): Bom | MacosError { } bom.files = boms; - bom.bom_path = path; + bom.evidence = path; return bom; } @@ -189,7 +189,7 @@ export function parseReceipt(path: string): Bom | MacosError { install_process_name: receipt["InstallProcessName"] ?? "", install_prefix_path: receipt["InstallPrefixPath"] ?? "", path, - bom_path: "", + evidence: "", files: [], }; return bom; diff --git a/src/macos/errors.ts b/src/macos/errors.ts index 8cf34ae2..01f1c871 100644 --- a/src/macos/errors.ts +++ b/src/macos/errors.ts @@ -30,6 +30,7 @@ export type ErrorName = | "COOKIES" | "SYSTEMSTATS" | "AUTHORIZATIONS" - | "STATS"; + | "STATS" + | "SHARED_FILELIST"; -export class MacosError extends ErrorBase {} +export class MacosError extends ErrorBase { } diff --git a/src/macos/homebrew.ts b/src/macos/homebrew.ts index 9d27d367..5c1f45a7 100644 --- a/src/macos/homebrew.ts +++ b/src/macos/homebrew.ts @@ -191,7 +191,7 @@ export function getCasks(glob_path?: string): HomebrewFormula[] { } /** - * Function to parse the Ruby formula associated with Hoembrew package + * Function to parse the Ruby formula associated with Homebrew package * @param path Path to the Ruby file to parse * @returns `HomebrewFormula` or FileError */ @@ -202,12 +202,12 @@ function parseRuby(path: string): HomebrewFormula | FileError { const reg_url = /(?<=url ).*$/m; const reg_name = /(?<=name ).*$/m; - const versionvalue = path.split("/"); - let version = ""; + const version_value = path.split("/"); + let version; if (path.includes("/Casks/")) { - version = versionvalue[ versionvalue.length - 4 ] ?? ""; + version = version_value[ version_value.length - 4 ] ?? ""; } else { - version = versionvalue[ versionvalue.length - 3 ] ?? ""; + version = version_value[ version_value.length - 3 ] ?? ""; } const rubyText = readTextFile(path); diff --git a/src/macos/plist/apps.ts b/src/macos/plist/apps.ts index 167635a4..3d39fe85 100644 --- a/src/macos/plist/apps.ts +++ b/src/macos/plist/apps.ts @@ -111,7 +111,7 @@ function parsePlist(path: string, skip_icon: boolean): Applications | MacosError } data = data as Record; - let icon_file = ""; + let icon_file; if (`${data[ "CFBundleIconFile" ]}`.includes("/")) { icon_file = `${data[ "CFBundleIconFile" ]}`; } else if (`${data[ "CFBundleIconFile" ]}`.includes("icns")) { @@ -137,7 +137,7 @@ function parsePlist(path: string, skip_icon: boolean): Applications | MacosError display_name: `${data[ "CFBundleExecutable" ]}`, copyright: `${data[ "NSHumanReadableCopyright" ]}`, icon: !skip_icon ? readIcon(icon_file) : "", - info: path, + evidence: path, }; return app; diff --git a/src/macos/plist/docktile.ts b/src/macos/plist/docktile.ts index 9a8f0310..91e0f920 100644 --- a/src/macos/plist/docktile.ts +++ b/src/macos/plist/docktile.ts @@ -24,13 +24,13 @@ function checkDockPersistence(apps: Applications[]): Applications[] { const dockTileApps: Applications[] = []; for (const app of apps) { // Only want to check Info.plist in PlugIns path - if (!app.info.toLowerCase().includes("plugins")) { + if (!app.evidence.toLowerCase().includes("plugins")) { continue; } - const data = getPlist(app.info); + const data = getPlist(app.evidence); if (data instanceof MacosError || data instanceof Uint8Array) { - console.error(`Failed to parse plist ${app.info}: ${data}`); + console.error(`Failed to parse plist ${app.evidence}: ${data}`); continue; } diff --git a/src/macos/plist/lulu.ts b/src/macos/plist/lulu.ts index d16d78a8..5b950268 100644 --- a/src/macos/plist/lulu.ts +++ b/src/macos/plist/lulu.ts @@ -1,6 +1,5 @@ import { LuluAction, - LuluRules, Rule, } from "../../../types/macos/plist/lulu"; import { MacosError } from "../errors"; @@ -11,7 +10,7 @@ import { getPlist } from "../plist"; * @param alt_file Optional path to Lulu `rules.plist` file * @returns `LuluRules` object or `MacosError` */ -export function luluRules(alt_file?: string): LuluRules | MacosError { +export function luluRules(alt_file?: string): Rule[] | MacosError { let file = "/Library/Objective-See/LuLu/rules.plist"; if (alt_file !== undefined) { file = alt_file; @@ -27,15 +26,12 @@ export function luluRules(alt_file?: string): LuluRules | MacosError { // Rules format at: https://github.com/objective-see/LuLu/blob/master/LuLu/Extension/Rules.m#L33 const objects = plist_data as Record; - const rules: LuluRules = { - path: file, - rules: [], - }; const object_data = objects[ "$objects" ]; if (object_data === undefined) { return new MacosError(`LULU`, `Got undefined for LuLu FW objects`); } + const rules: Rule[] = []; for (const entry of object_data) { if (typeof entry !== "object") { continue; @@ -66,9 +62,10 @@ export function luluRules(alt_file?: string): LuluRules | MacosError { pid: object_data[ rule_value[ "pid" ] as number ] as number, endpoint_port: object_data[ rule_value[ "endpointPort" ] as number ] as number, + evidence: file, }; - rules.rules.push(rule); + rules.push(rule); } return rules; @@ -90,7 +87,7 @@ function getAction(data: number): LuluAction { interface LuluSigning { "NS.keys": number[]; "NS.objects": number[]; - "$class": string; + "$class": string | number; } /** @@ -124,7 +121,11 @@ function getCodeSigning( const value = objects.at(value_key as number) as | string | Record - | number; + | number + | undefined; + if (value === undefined) { + continue; + } if (typeof value === "string" || typeof value === "number") { if (key === "signatureSigner") { cs_info[ key ] = getSignStatus(Number(value)); @@ -179,3 +180,53 @@ function getSignStatus(status: number): Signer { return Signer.UNKNOWN; } } + + +export function testLuluRules(): void { + const lulu_test = "../../test_data/macos/lulu/rules.plist"; + const results = luluRules(lulu_test); + if (results instanceof MacosError) { + throw console.log(results); + } + + + if (results.length !== 253) { + throw `Got ${results.length} wanted "253".......luluRules ❌`; + } + + if (results[ 3 ]?.file !== "storekitagent") { + throw `Got ${results[ 3 ]?.file} wanted "storekitagent".......luluRules ❌`; + } + + if (results[ 123 ]?.file !== "Precize") { + throw `Got ${results[ 123 ]?.file} wanted "Precize".......luluRules ❌`; + } + + console.info(` Function luluRules ✅`); + + if (getAction(1) !== LuluAction.ALLOW) { + throw `Got ${LuluAction.BLOCK} wanted "${LuluAction.ALLOW}".......getAction ❌`; + } + + if (getAction(0) !== LuluAction.BLOCK) { + throw `Got ${LuluAction.ALLOW} wanted "${LuluAction.BLOCK}".......getAction ❌`; + } + + console.info(` Function getAction ✅`); + + const test_data: LuluSigning = { "NS.keys": [ 542, 543, 544, 545 ], "NS.objects": [ 22, 272, 273, 546 ], "$class": 279 }; + const results_code = getCodeSigning(test_data, []); + if (Object.keys(results_code).length !== 0) { + throw `Got ${Object.keys(results_code).length} wanted "0".......getCodeSigning ❌`; + } + + console.info(` Function getCodeSigning ✅`); + + const test_status = [ 1, 2, 3, 4 ]; + for (const entry of test_status) { + if (getSignStatus(entry) === Signer.UNKNOWN) { + throw `Got ${getSignStatus(entry)} wanted "${Signer.UNKNOWN}".......getSignStatus ❌`; + } + } + console.info(` Function getSignStatus ✅`); +} \ No newline at end of file diff --git a/src/macos/plist/sharefilelist.ts b/src/macos/plist/sharefilelist.ts new file mode 100644 index 00000000..43367938 --- /dev/null +++ b/src/macos/plist/sharefilelist.ts @@ -0,0 +1,198 @@ +import { GlobInfo } from "../../../types/filesystem/globs"; +import { BookmarkData } from "../../../types/macos/bookmark"; +import { SingleRequirement } from "../../../types/macos/codesigning"; +import { PlistDataType, RecentFiles, SharedFilelistRaw, SharedFileType } from "../../../types/macos/plist/sharefilelist"; +import { FileError } from "../../filesystem/errors"; +import { glob } from "../../filesystem/files"; +import { parseBookmark } from "../bookmark"; +import { parseRequirementBlob } from "../codesigning/blob"; +import { SigningError } from "../codesigning/errors"; +import { MacosError } from "../errors"; +import { getPlist } from "../plist"; + +/** + * Function to parse recent files on macOS + * @param alt_path Optional alternative path to SharedFilelist file (SFL) + * @returns Array of `RecentFiles` or `MacosError` + */ +export function recentFilesMacos(alt_path?: string): RecentFiles[] | MacosError { + let paths = [ "/Users/*/Library/Application Support/com.apple.sharedfilelist/*.sfl*", "/Users/*/Library/Application Support/com.apple.sharedfilelist/*/*.sfl*", ]; + if (alt_path !== undefined) { + paths = [ alt_path ]; + } + + let values: RecentFiles[] = []; + for (const entry of paths) { + const glob_paths = glob(entry); + if (glob_paths instanceof FileError) { + continue; + } + + const results = parsePlistFiles(glob_paths); + if (results instanceof MacosError) { + continue; + } + values = values.concat(results); + } + return values; +} + +/** + * Function to read binary plist file (sfl file) + * @param files Array of `GlobInfo` + * @returns Array of `RecentFiles` or `MacosError` + */ +function parsePlistFiles(files: GlobInfo[]): RecentFiles[] | MacosError { + const values: RecentFiles[] = []; + for (const entry of files) { + if (!entry.is_file) { + continue; + } + + const result = getPlist(entry.full_path); + if (result instanceof MacosError) { + continue; + } + + const raw_data = result as unknown as SharedFilelistRaw; + if (raw_data.$objects.includes("Bookmark")) { + const bookmark_result = parseBookmarkEntry(raw_data); + if (bookmark_result instanceof MacosError) { + continue; + } + + const shared_file_type = favoriteType(entry.full_path); + let signing_info: SingleRequirement[] = []; + if (raw_data.$objects.includes("com.apple.LSSharedFileList.AppIdentifier")) { + const signing = parseCodesign(raw_data); + if (signing instanceof MacosError) { + continue; + } + signing_info = signing; + } + + for (const book_entry of bookmark_result) { + let message = book_entry.path.length === 0 ? book_entry.url_string : book_entry.path; + if (message.length === 0 && book_entry.target_filename.length !== 0) { + message = book_entry.target_filename; + } else if (message.length === 0) { + message = "Unknown target"; + } + const datetime = book_entry.created.length === 0 ? "1970-01-01T00:00:00Z" : book_entry.created; + let value: RecentFiles = { + evidence: entry.full_path, + shared_file_type, + message: `Recent File '${message}'`, + datetime, + timestamp_desc: "Target File Created", + data_type: "macos:plist:recentfile:entry", + plist_data_type: PlistDataType.Bookmark, + artifact: "Recent Files" + }; + value = { ...value, ...book_entry }; + if (signing_info.length !== 0) { + value = { ...value, ...signing_info[ 0 ] }; + + } + values.push(value); + } + + } else if (raw_data.$objects.includes("com.apple.LSSharedFileList.AppIdentifier")) { + const signing = parseCodesign(raw_data); + if (signing instanceof MacosError) { + continue; + } + + const shared_file_type = favoriteType(entry.full_path); + + for (const sign of signing) { + let value: RecentFiles = { + evidence: entry.full_path, + shared_file_type, + message: `Recent File Application '${sign.identifier}'`, + datetime: '1970-01-01T00:00:00Z', + timestamp_desc: "None", + data_type: "macos:plist:recentfile:entry", + plist_data_type: PlistDataType.CodeSign, + artifact: "Recent Files" + }; + value = { ...value, ...sign }; + values.push(value); + } + } + } + return values; +} + +/** + * Function to parse the bookmark data in the binary plist + * @param data `SharedFilelistRaw` object + * @returns Array of `BookmarkData` or `MacosError` + */ +function parseBookmarkEntry(data: SharedFilelistRaw): BookmarkData[] | MacosError { + const values: BookmarkData[] = []; + for (const entry of data.$objects) { + if (!Array.isArray(entry)) { + continue; + } + + if (entry.length < 100 || entry[ 0 ] !== 98) { + continue; + } + const result = parseBookmark(new Uint8Array(entry)); + if (result instanceof MacosError) { + continue; + } + values.push(result); + } + + return values; +} + +/** + * Function to parse application signing info related to recent files + * @param data `SharedFilelistRaw` object + * @returns Array of `SingleRequirement` or `MacosError` + */ +function parseCodesign(data: SharedFilelistRaw): SingleRequirement[] | MacosError { + const values: SingleRequirement[] = []; + for (const entry of data.$objects) { + if (!Array.isArray(entry)) { + continue; + } + + if (entry.length < 20 && entry[ 0 ] !== 250 && entry[ 1 ] !== 222 && entry[ 2 ] !== 12) { + continue; + } + const result = parseRequirementBlob(new Uint8Array(entry)); + if (result instanceof SigningError) { + continue; + } + values.push(result); + } + + return values; +} + +/** + * Function to determine the recent file type + * @param path Path to sharedfilelist (SFL) + * @returns `SharedFileType` enum + */ +function favoriteType(path: string): SharedFileType { + if (path.includes("FavoriteVolumes")) { + return SharedFileType.VolumeFavorite; + } else if (path.includes("FavoriteItems")) { + return SharedFileType.FinderFavorite; + } else if (path.includes("ApplicationRecentDocuments")) { + return SharedFileType.ApplicationRecentFiles; + } else if (path.includes("ProjectsItems")) { + return SharedFileType.ProjectFavorite; + } else if (path.includes("RecentApplications")) { + return SharedFileType.RecentApplication; + } else if (path.includes("RecentDocuments")) { + return SharedFileType.RecentDocuments; + } else { + return SharedFileType.UnknownFavorite; + } +} \ No newline at end of file diff --git a/src/macos/plist/system_extensions.ts b/src/macos/plist/system_extensions.ts index 3ba4d398..df05839d 100644 --- a/src/macos/plist/system_extensions.ts +++ b/src/macos/plist/system_extensions.ts @@ -41,6 +41,7 @@ export function systemExtensions( categories: ext[ "categories" ] as string[], bundle_path: bundle[ "bundlePath" ] ?? "", team: ext[ "teamID" ] as string, + evidence: path, }; exts.push(sys_ext); } diff --git a/src/macos/plist/xprotect.ts b/src/macos/plist/xprotect.ts index 586b4abb..1144cfea 100644 --- a/src/macos/plist/xprotect.ts +++ b/src/macos/plist/xprotect.ts @@ -13,7 +13,7 @@ import { getPlist } from "../plist"; export function getXprotectDefinitions( alt_path?: string, ): XprotectEntries[] | MacosError { - let paths: string[] = []; + let paths; if (alt_path !== undefined) { paths = [ alt_path ]; } else { diff --git a/src/macos/safari/cookies.ts b/src/macos/safari/cookies.ts index dd205515..11bf63e8 100644 --- a/src/macos/safari/cookies.ts +++ b/src/macos/safari/cookies.ts @@ -19,7 +19,7 @@ export function safariCookies(paths: SafariProfile[], platform: PlatformType): C for (const path of paths) { let full_path = `${path.container_path}/Library/Cookies/Cookies.binarycookies`; if (platform === PlatformType.Windows) { - full_path = `${path.container_path}\\Library/Cookies\\Cookies.binarycookies`; + full_path = `${path.container_path}\\Library\\Cookies\\Cookies.binarycookies`; } const cookie = parseCookies(full_path); @@ -78,7 +78,7 @@ export function parseCookies(path: string): Cookie[] | MacosError { continue; } remaining = page_bytes.remaining as Uint8Array; - const cookie = parsePage(page_bytes.nommed as Uint8Array, page_size); + const cookie = parsePage(page_bytes.nommed as Uint8Array, page_size, path); if (cookie instanceof NomError) { return new MacosError( `COOKIES`, @@ -137,7 +137,7 @@ interface Page { remaining_bytes: Uint8Array; } -function parsePage(data: Uint8Array, size: number): Cookie[] | NomError { +function parsePage(data: Uint8Array, size: number, evidence: string): Cookie[] | NomError { const page_input = take(data, size); if (page_input instanceof NomError) { return page_input; @@ -177,7 +177,7 @@ function parsePage(data: Uint8Array, size: number): Cookie[] | NomError { continue; } - const cookie = parseRecord(start.remaining as Uint8Array); + const cookie = parseRecord(start.remaining as Uint8Array, evidence); if (cookie instanceof NomError) { continue; } @@ -187,7 +187,7 @@ function parsePage(data: Uint8Array, size: number): Cookie[] | NomError { return cookies; } -function parseRecord(data: Uint8Array): Cookie | NomError { +function parseRecord(data: Uint8Array, evidence: string): Cookie | NomError { let input = nomUnsignedFourBytes(data, Endian.Le); if (input instanceof NomError) { return input; @@ -303,7 +303,8 @@ function parseRecord(data: Uint8Array): Cookie | NomError { datetime: expiration, timestamp_desc: "Cookie Expires", artifact: "Website Cookie", - data_type: "macos:safari:cookies:entry" + data_type: "macos:safari:cookies:entry", + evidence, }; return record; diff --git a/src/macos/safari/plist.ts b/src/macos/safari/plist.ts index d46eba0e..e08f6ed0 100644 --- a/src/macos/safari/plist.ts +++ b/src/macos/safari/plist.ts @@ -82,7 +82,8 @@ export function safariDownloads(paths: SafariProfile[], platform: PlatformType, datetime: bookmark.created, timestamp_desc: "File Download Start", artifact: "File Download", - data_type: "macos:safari:downloads:entry" + data_type: "macos:safari:downloads:entry", + evidence: full_path, }; if (unfold && typeof client !== 'undefined') { const result = client.parseUrl(download.source_url); @@ -136,7 +137,8 @@ export function safariBookmarks(paths: SafariProfile[], platform: PlatformType): datetime: meta.created, timestamp_desc: "Bookmark Created", artifact: "Website Bookmark", - data_type: "macos:safari:bookmark:entry" + data_type: "macos:safari:bookmark:entry", + evidence: full_path, }; hits.push(book); } @@ -155,7 +157,8 @@ export function safariBookmarks(paths: SafariProfile[], platform: PlatformType): datetime: meta.created, timestamp_desc: "Bookmark Created", artifact: "Website Bookmark", - data_type: "macos:safari:bookmark:entry" + data_type: "macos:safari:bookmark:entry", + evidence: full_path, }; hits.push(book); } @@ -202,7 +205,8 @@ export function safariExtensions(paths: SafariProfile[], platform: PlatformType) datetime: exts[key]["AddedDate"].replace("Z", ".000Z"), timestamp_desc: "Extension Installed", artifact: "Browser Extension", - data_type: "macos:safari:extension:entry" + data_type: "macos:safari:extension:entry", + evidence: full_path, }; hits.push(value); } diff --git a/src/macos/safari/safari.ts b/src/macos/safari/safari.ts index cce257d2..2a838d48 100644 --- a/src/macos/safari/safari.ts +++ b/src/macos/safari/safari.ts @@ -153,7 +153,6 @@ export class Safari { } offset += limit; } - offset = 0; const cooks = this.cookies(); let status = dumpData(cooks, "retrospect_safari_cookies", output); diff --git a/src/macos/safari/sqlite.ts b/src/macos/safari/sqlite.ts index 7b51aa19..e3cdc259 100644 --- a/src/macos/safari/sqlite.ts +++ b/src/macos/safari/sqlite.ts @@ -56,7 +56,8 @@ export function safariHistory(paths: SafariProfile[], query: string, platform: P datetime: unixEpochToISO(cocoatimeToUnixEpoch(entry["visit_time"] as number)), timestamp_desc: "URL Visited", artifact: "URL History", - data_type: "macos:safari:history:entry" + data_type: "macos:safari:history:entry", + evidence: full_path, }; if (unfold && typeof client !== 'undefined') { const result = client.parseUrl(history_row.url); @@ -105,7 +106,8 @@ export function safariFavicons(paths: SafariProfile[], query: string, platform: datetime: unixEpochToISO(cocoatimeToUnixEpoch(entry["timestamp"] as number)), timestamp_desc: "Favicon Created", artifact: "URL Favicon", - data_type: "macos:safari:favicons:entry" + data_type: "macos:safari:favicons:entry", + evidence: full_path, }; hits.push(row); } diff --git a/src/macos/sqlite/tcc.ts b/src/macos/sqlite/tcc.ts index 2e3b2a8b..0626971d 100644 --- a/src/macos/sqlite/tcc.ts +++ b/src/macos/sqlite/tcc.ts @@ -4,7 +4,6 @@ import { ClientType, Reason, TccData, - TccValues, } from "../../../types/macos/sqlite/tcc"; import { glob } from "../../filesystem/files"; import { FileError } from "../../filesystem/errors"; @@ -22,9 +21,9 @@ import { unixEpochToISO } from "../../time/conversion"; * An optional path to the `TCC.db` can be provided. * Otherwise will parse all user and System `TCC.db` files. * @param alt_db Optional alternative path to TCC.db files - * @returns Array of `TccValues` or `MacosError` + * @returns Array of `TccData` or `MacosError` */ -export function queryTccDb(alt_db?: string): TccValues[] | MacosError { +export function queryTccDb(alt_db?: string): TccData[] | MacosError { let dbs: string[] = []; if (alt_db !== undefined) { dbs = [ alt_db ]; @@ -47,14 +46,15 @@ export function queryTccDb(alt_db?: string): TccValues[] | MacosError { } const query = "select * from access"; - const tcc_data: TccValues[] = []; + let tcc_data: TccData[] = []; for (const entry of dbs) { const results = querySqlite(entry, query); if (results instanceof ApplicationError) { return new MacosError("TCC", `failed to query ${entry}: ${results}`); } - tcc_data.push(getTccData(results, entry)); + + tcc_data = tcc_data.concat(getTccData(results, entry)); } return tcc_data; @@ -66,28 +66,33 @@ export function queryTccDb(alt_db?: string): TccValues[] | MacosError { * @param path path to the `TCC.db` file * @returns `TccValues` data from `TCC.db` file */ -function getTccData(data: Record[], path: string): TccValues { +function getTccData(data: Record[], path: string): TccData[] { const tcc_array: TccData[] = []; for (const entry of data) { const tcc_data: TccData = { - service: entry[ "service" ] as string, - client: entry[ "client" ] as string, - client_type: clientType(entry[ "client_type" ] as number), - auth_value: authValue(entry[ "auth_value" ] as number), - auth_reason: authReason(entry[ "auth_reason" ] as number), - auth_version: entry[ "auth_version" ] as number, + service: entry["service"] as string, + client: entry["client"] as string, + client_type: clientType(entry["client_type"] as number), + auth_value: authValue(entry["auth_value"] as number), + auth_reason: authReason(entry["auth_reason"] as number), + auth_version: entry["auth_version"] as number, cert: undefined, - policy_id: entry[ "policy_id" ] as number | undefined, - indirect_object_identifier_type: - entry[ "indirect_object_identifier_type" ] as number | undefined, - indirect_object_identifier: entry[ "indirect_object_identifier" ] as string, + policy_id: entry["policy_id"] as number | undefined, + indirect_object_identifier_type: entry["indirect_object_identifier_type"] as number | undefined, + indirect_object_identifier: entry["indirect_object_identifier"] as string, indirect_object_code_identity: undefined, - flags: entry[ "flags" ] as number | undefined, - last_modified: unixEpochToISO(entry[ "last_modified" ] as number), - pid: entry[ "pid" ] as number | null, - pid_version: entry[ "pid_version" ] as number | null, - boot_uuid: entry[ "boot_uuid" ] as string, - last_reminded: unixEpochToISO(entry[ "last_reminded" ] as number), + flags: entry["flags"] as number | undefined, + last_modified: unixEpochToISO(entry["last_modified"] as number), + pid: entry["pid"] as number | null, + pid_version: entry["pid_version"] as number | null, + boot_uuid: entry["boot_uuid"] as string, + last_reminded: unixEpochToISO(entry["last_reminded"] as number), + evidence: path, + message: `TCC client entry '${entry["client"] as string}'`, + datetime: unixEpochToISO(entry["last_modified"] as number), + timestamp_desc: "Last Modified", + artifact: "TCC Database", + data_type: "macos:sqlite:tcc:entry" }; if (entry[ "csreq" ] !== undefined && entry[ "csreq" ] !== null) { @@ -108,12 +113,7 @@ function getTccData(data: Record[], path: string): TccValues { tcc_array.push(tcc_data); } - const tcc_value: TccValues = { - db_path: path, - data: tcc_array, - }; - - return tcc_value; + return tcc_array; } /** diff --git a/src/macos/systemstats.ts b/src/macos/systemstats.ts index 938c6a42..db9f1463 100644 --- a/src/macos/systemstats.ts +++ b/src/macos/systemstats.ts @@ -15,7 +15,7 @@ import { MacosError } from "./errors"; /** * TODO: - * 1. Add wall time to the abosolute timestamp (field 10?) + * 1. Add wall time to the absolute timestamp (field 10?) * 2. Extract more protobuf fields * 3. Add uuid parsing * 4. Support decompressing gzip data @@ -78,7 +78,7 @@ function parseStats(stats_file: GlobInfo): SystemStats[] | MacosError { return new MacosError(`STATS`, `failed to parse file ${path} unknown byte: ${unknown}`); } - // Size of next string value. Always 0xE. May be part of unkonwn byte above (0xAE). But not 100% sure + // Size of next string value. Always 0xE. May be part of Unknown byte above (0xAE). But not 100% sure const size = nomUnsignedOneBytes(unknown.remaining); if (size instanceof NomError) { return new MacosError(`STATS`, `failed to parse file ${path} size byte: ${size}`); @@ -98,14 +98,14 @@ function parseStats(stats_file: GlobInfo): SystemStats[] | MacosError { return new MacosError(`STATS`, `failed to parse file ${path} protobuf byte: ${value}`); } - return extractInfo(value, stats_file, version) + return extractInfo(value, stats_file, version); } /// Try to extract Protobuf data into meaningful info function extractInfo(proto_data: Record, stats_file: GlobInfo, version: string): SystemStats[] { const values: SystemStats[] = []; const stat: SystemStats = { - source_path: stats_file.full_path, + evidence: stats_file.full_path, source_file: stats_file.filename, version, message: "", @@ -117,13 +117,13 @@ function extractInfo(proto_data: Record, stats_file: GlobInfo, }; for (const entry in proto_data) { // Get initial base values - switch (proto_data[entry]?.tag.field) { + switch (proto_data[ entry ]?.tag.field) { case 6: { - stat.build_version = proto_data[entry].value as string; + stat.build_version = proto_data[ entry ].value as string; break; } case 3: { - stat.datetime = unixEpochToISO(proto_data[entry].value as number) as string; + stat.datetime = unixEpochToISO(proto_data[ entry ].value as number) as string; break; } } @@ -131,8 +131,8 @@ function extractInfo(proto_data: Record, stats_file: GlobInfo, // Check for nested values // This should be the last value in the base data above // Tons of data here - if (Array.isArray(proto_data[entry]?.value)) { - for (const value of proto_data[entry].value as Record[]) { + if (Array.isArray(proto_data[ entry ]?.value)) { + for (const value of proto_data[ entry ].value as Record[]) { stat.message = JSON.stringify(value); values.push(Object.assign({}, stat)); } diff --git a/src/time/conversion.ts b/src/time/conversion.ts index 489e80b2..8dd5adc3 100644 --- a/src/time/conversion.ts +++ b/src/time/conversion.ts @@ -131,3 +131,23 @@ export function unixEpochToISO(timestamp: number | bigint): string { const milli_time = BigInt(timestamp) / BigInt(milliseconds); return new Date(Number(milli_time)).toISOString(); } + +/** + * Function to try to convert timestamp string to UTC + * @param timestamp Modified ISO timestamp. Ex: `2026-04-25 11:36:36.250534 -04:00` + */ +export function convertModifiedIso(timestamp: string): string { + let normal = timestamp; + + // `new Date` requires `T` between day and hour + if (!timestamp.includes("T")) { + normal = normal.replace(" ", "T"); + } + // No spaces are allowed between timezone value and timestamp + normal = normal.replace(" ", ""); + // Only millisecond precision is support for `new Date` + normal = normal.replace(/(\.\d{3})\d+/, '$1'); + + const utc_date = new Date(normal).toISOString(); + return utc_date; +} \ No newline at end of file diff --git a/src/timesketch/artifacts/windows/registry.ts b/src/timesketch/artifacts/windows/registry.ts index f42398d8..ae91f51a 100644 --- a/src/timesketch/artifacts/windows/registry.ts +++ b/src/timesketch/artifacts/windows/registry.ts @@ -1,38 +1,38 @@ -import { Registry } from "../../../../types/windows/registry"; -import { TimesketchTimeline } from "../../../../types/timesketch/timeline"; - -/** - * Function to timeline Registry - * @param data `Registry` array - * @returns Array `TimesketchTimeline` of Registry - */ -export function timelineRegistry(data: Registry[]): TimesketchTimeline[] { - const entries = []; - - for (const item of data) { - const entry: TimesketchTimeline = { - datetime: item.last_modified, - timestamp_desc: "Registry Last Modified", - message: "", - artifact: "Registry", - data_type: "windows:registry:key", - }; - entry["registry_path"] = item.registry_path; - entry["registry_file"] = item.registry_file; - entry["depth"] = item.depth; - entry["key"] = item.key; - entry["path"] = item.path; - entry["name"] = item.name; - entry["security_offset"] = item.security_offset; - for (const value of item.values) { - entry.message = `${item.path} | Value: ${value.value}`; - entry["value"] = value.value; - entry["data"] = value.data; - entry["reg_data_type"] = value.data_type; - - entries.push(Object.assign({}, entry)); - } - } - - return entries; -} +import { Registry } from "../../../../types/windows/registry"; +import { TimesketchTimeline } from "../../../../types/timesketch/timeline"; + +/** + * Function to timeline Registry + * @param data `Registry` array + * @returns Array `TimesketchTimeline` of Registry + */ +export function timelineRegistry(data: Registry[]): TimesketchTimeline[] { + const entries = []; + + for (const item of data) { + const entry: TimesketchTimeline = { + datetime: item.last_modified, + timestamp_desc: "Registry Last Modified", + message: "", + artifact: "Registry", + data_type: "windows:registry:key", + }; + entry["registry_path"] = item.evidence; + entry["registry_file"] = item.registry_file; + entry["depth"] = item.depth; + entry["key"] = item.key; + entry["path"] = item.path; + entry["name"] = item.name; + entry["security_offset"] = item.security_offset; + for (const value of item.values) { + entry.message = `${item.path} | Value: ${value.value}`; + entry["value"] = value.value; + entry["data"] = value.data; + entry["reg_data_type"] = value.data_type; + + entries.push(Object.assign({}, entry)); + } + } + + return entries; +} diff --git a/src/timesketch/client.ts b/src/timesketch/client.ts index 616e7224..fcac8f4d 100644 --- a/src/timesketch/client.ts +++ b/src/timesketch/client.ts @@ -1,351 +1,351 @@ -import { TimesketchAuth } from "../../types/timesketch/client"; -import { TimesketchError } from "./error"; -import { BodyType, ClientRequest, Protocol, request } from "../http/client"; -import { HttpError } from "../http/errors"; -import { extractUtf8String } from "../encoding/strings"; -import { - TimesketchArtifact, - TimesketchTimeline, -} from "../../types/timesketch/timeline"; -import { encodeBytes } from "../encoding/bytes"; -import { timelineArtifact } from "./timeline"; -import { TimelineResponse } from "../../types/timesketch/client"; - -/** - * Class to interact with a Timesketch server instance - */ -export class Timesketch { - private timesketch_auth: TimesketchAuth; - private token: string; - private cookie: string; - private timeline_name: string; - private opensearch_index: string; - - /** - * @param auth `TimesketchAuth` object used to authenticate to Timesketch - * @param name The name that should used for the timeline. If none is provided the artifact name will be used. It is **recommended** to provide a name (ex: the hostname) - * @param index The OpenSearch index that should be used when uploading data. If none is provided a new index will be created - */ - constructor (auth: TimesketchAuth, name = "", index = "") { - this.timesketch_auth = auth; - this.token = ""; - this.cookie = ""; - this.timeline_name = name; - this.opensearch_index = index; - } - - /** - * Function to timeline and upload data to Timesketch - * @param data Artifact data to Timeline. Must be the type specified by `artifact` - * @param artifact The artifact type that should be timeline - * @returns A `TimesketchError` if the data cannot be timeline or if it failed to upload to Timesketch - */ - public async timelineAndUpload( - data: unknown, - artifact: TimesketchArtifact, - ): Promise { - const timeline_data = timelineArtifact(data, artifact); - if (timeline_data instanceof TimesketchError) { - return timeline_data; - } - - await this.upload(timeline_data, artifact); - } - - /** - * Function to upload timelined data - * @param data Array of `TimesketchTimeline` - * @param artifact Name of artifact that was timelined - * @returns A `TimesketchError` if the data cannot be timeline or if it failed to upload to Timesketch - */ - public async upload( - data: TimesketchTimeline[], - artifact: string, - ): Promise { - if (data.length === 0) { - return new TimesketchError(`ARTIFACT`, `zero values for ${artifact}`); - } - - // If no token or cookie. We need to logon! - if (this.token === "" || this.cookie === "") { - const status = await this.authTimesketch(); - if (status instanceof TimesketchError) { - return status; - } - } - - if (this.timesketch_auth.sketch_id === undefined) { - await this.createSketch(artifact); - } - - // Verify user provived a valid Sketch ID - const id_status = await this.verifySketchId(); - if (id_status instanceof TimesketchError) { - return id_status; - } - - return await this.uploadTimeline(data, artifact); - } - - /** - * Function to upload timeline data to Timesketch - * @param data Array of `TimesketchTimeline` - * @param artifact artifact that was timelined - * @returns A `TimesketchError` if data cannot be uploaded. - */ - private async uploadTimeline( - data: TimesketchTimeline[], - artifact: string, - ): Promise { - if (this.timeline_name === "") { - this.timeline_name = artifact; - } - - const entries_strings: string[] = []; - // We have to convert the TimesketchTimeline object to a string :/ - for (let i = 0; i < data.length; i++) { - entries_strings.push(JSON.stringify(data[ i ])); - } - - const headers = { - "X-Csrftoken": this.token, - Cookie: this.cookie, - }; - - const client: ClientRequest = { - url: `${this.timesketch_auth.url}/api/v1/upload/`, - protocol: Protocol.POST, - headers, - body_type: BodyType.FORM, - verify_ssl: this.timesketch_auth.verify_ssl, - }; - - const chunk_size = 10000; - // Split data into chunks so Timesketch does not keel over - for (let i = 0; i < entries_strings.length; i += chunk_size) { - const chunk = entries_strings.slice(i, i + chunk_size); - // From: https://github.com/google/timesketch/blob/3c781e6bde4398e24cba7dd41c4f87ba4d6e5394/importer_client/python/timesketch_import_client/importer.py#L249 - const post_data: Record = { - name: this.timeline_name, - sketch_id: this.timesketch_auth.sketch_id, - data_label: artifact, - enable_stream: false, - provider: "artemis", - events: chunk.join("\n"), - }; - - if (this.opensearch_index !== "") { - post_data[ "index_name" ] = this.opensearch_index; - } - - const bytes = encodeBytes(JSON.stringify(post_data)); - const response = await request(client, bytes); - if (response instanceof HttpError) { - return new TimesketchError( - `UPLOAD`, - `failed to upload to Timesketch ${response}`, - ); - } - - if (response.status !== 201) { - return new TimesketchError( - `UPLOAD`, - `non-201 response for uploading data ${extractUtf8String( - new Uint8Array(response.body), - ) - }`, - ); - } - - const task_info: TimelineResponse = JSON.parse( - extractUtf8String(new Uint8Array(response.body)), - ); - - // Try to get the OpenSearch Index from the upload response - if (this.opensearch_index === "") { - this.opensearch_index = - task_info.objects.at(0)?.searchindex.index_name ?? ""; - } - } - } - - /** - * Function to create a sketch - * @param artifact The artifact used to name the sketch if a name is not provided - * @returns A `TimesketchError` if a sketch cannot be created - */ - private async createSketch( - artifact: string, - ): Promise { - if (this.timesketch_auth.sketch_name === undefined) { - this.timesketch_auth.sketch_name = artifact; - } - - const headers = { - "X-Csrftoken": this.token, - Cookie: this.cookie, - "Content-Type": "application/json", - }; - - const client: ClientRequest = { - url: `${this.timesketch_auth.url}/api/v1/sketches/`, - protocol: Protocol.POST, - headers, - }; - - const body = { - "name": this.timesketch_auth.sketch_name, - "description": `Timeline for ${this.timesketch_auth.sketch_name}`, - }; - const bytes = encodeBytes(JSON.stringify(body)); - const response = await request(client, bytes); - if (response instanceof HttpError) { - return new TimesketchError( - `SKETCH_ID`, - `failed to create sketch ${this.timesketch_auth.sketch_name}: ${response}`, - ); - } - - const result = extractUtf8String(new Uint8Array(response.body)); - const sketch_object = JSON.parse(result); - const objects_value = sketch_object[ "objects" ]; - if (objects_value === undefined) { - return new TimesketchError( - `SKETCH_ID`, - `failed no objects in sketch response: ${response}`, - ); - } - this.timesketch_auth.sketch_id = objects_value.at(0)[ "id" ]; - } - - /** - * Function to verify if provided Sketch ID exists - * @returns `TimesketchError` if we cannot verify the Sketch ID - */ - private async verifySketchId(): Promise { - const headers = { - "X-Csrftoken": this.token, - Cookie: this.cookie, - }; - - const client: ClientRequest = { - url: - `${this.timesketch_auth.url}/api/v1/sketches/${this.timesketch_auth.sketch_id}/`, - protocol: Protocol.GET, - headers, - verify_ssl: this.timesketch_auth.verify_ssl, - }; - - const response = await request(client); - if (response instanceof HttpError) { - return new TimesketchError( - `SKETCH_ID`, - `failed to verify Sketch ID ${this.timesketch_auth.sketch_id} ${response}`, - ); - } - - if (response.status !== 200) { - return new TimesketchError( - `SKETCH_ID`, - `non-200 response for verifying Sketch ID ${extractUtf8String( - new Uint8Array(response.body), - ) - }`, - ); - } - const id_text = extractUtf8String(new Uint8Array(response.body)); - const id_info = JSON.parse(id_text); - if (id_info[ "objects" ].length === 0) { - return new TimesketchError( - `SKETCH_ID`, - `no Sketch ID associated ${this.timesketch_auth.sketch_id}: ${id_text}`, - ); - } - } - - /** - * Function to authenticate to Timesketch - * @returns `TimesketchError` if authentication failed - */ - private async authTimesketch(): Promise { - const client: ClientRequest = { - url: `${this.timesketch_auth.url}/login/`, - protocol: Protocol.GET, - verify_ssl: this.timesketch_auth.verify_ssl, - }; - const response = await request(client); - if (response instanceof HttpError) { - return new TimesketchError( - `AUTH`, - `failed to start auth to Timesketch ${response}`, - ); - } - - const body_text = extractUtf8String(new Uint8Array(response.body)); - // I hope they don't change this much... - const value_regex = /value=".*"/m; - const value_token = body_text.match(value_regex); - - // Extract the CSRF Token - if (typeof value_token?.[ 0 ] === "string" && value_token?.[ 0 ].length > 50) { - this.token = value_token?.[ 0 ] - .replaceAll("value=", "") - .replaceAll('"', ""); - } - - // Also need first cookie - this.cookie = response.headers[ "set-cookie" ].split(";").at(0) ?? ""; - if (this.cookie === "") { - return new TimesketchError( - `AUTH`, - `failed to get Cookie for logon ${response.headers}`, - ); - } - - // We should be able to auth to Timesketch now! - const form = { - username: this.timesketch_auth.username, - password: this.timesketch_auth.password, - csrf_token: this.token, - }; - const headers = { - "Content-Type": "application/x-www-form-urlencoded", - referer: this.timesketch_auth.url, - Cookie: this.cookie, - }; - const form_string = JSON.stringify(form); - const bytes = encodeBytes(form_string); - - client.headers = headers; - client.body_type = BodyType.FORM; - client.follow_redirects = false; - client.protocol = Protocol.POST; - // Logon but do not follow the redirect. We need one more cookie - const auth_response = await request(client, bytes); - if (auth_response instanceof HttpError) { - return new TimesketchError( - `AUTH`, - `failed to logon and auth to Timesketch ${auth_response}`, - ); - } - - if (auth_response.status !== 302) { - return new TimesketchError( - `AUTH`, - `non-302 response for auth to Timesketch ${extractUtf8String( - new Uint8Array(auth_response.body), - ) - }`, - ); - } - - // Update the cookie so we remain authenticated - this.cookie = auth_response.headers[ "set-cookie" ].split(";").at(0) ?? ""; - if (this.cookie === "") { - return new TimesketchError( - `AUTH`, - `failed to get last Cookie for logon ${auth_response.headers}`, - ); - } - } -} +import { TimesketchAuth } from "../../types/timesketch/client"; +import { TimesketchError } from "./error"; +import { BodyType, ClientRequest, Protocol, request } from "../http/client"; +import { HttpError } from "../http/errors"; +import { extractUtf8String } from "../encoding/strings"; +import { + TimesketchArtifact, + TimesketchTimeline, +} from "../../types/timesketch/timeline"; +import { encodeBytes } from "../encoding/bytes"; +import { timelineArtifact } from "./timeline"; +import { TimelineResponse } from "../../types/timesketch/client"; + +/** + * Class to interact with a Timesketch server instance + */ +export class Timesketch { + private timesketch_auth: TimesketchAuth; + private token: string; + private cookie: string; + private timeline_name: string; + private opensearch_index: string; + + /** + * @param auth `TimesketchAuth` object used to authenticate to Timesketch + * @param name The name that should used for the timeline. If none is provided the artifact name will be used. It is **recommended** to provide a name (ex: the hostname) + * @param index The OpenSearch index that should be used when uploading data. If none is provided a new index will be created + */ + constructor (auth: TimesketchAuth, name = "", index = "") { + this.timesketch_auth = auth; + this.token = ""; + this.cookie = ""; + this.timeline_name = name; + this.opensearch_index = index; + } + + /** + * Function to timeline and upload data to Timesketch + * @param data Artifact data to Timeline. Must be the type specified by `artifact` + * @param artifact The artifact type that should be timeline + * @returns A `TimesketchError` if the data cannot be timeline or if it failed to upload to Timesketch + */ + public async timelineAndUpload( + data: unknown, + artifact: TimesketchArtifact, + ): Promise { + const timeline_data = timelineArtifact(data, artifact); + if (timeline_data instanceof TimesketchError) { + return timeline_data; + } + + await this.upload(timeline_data, artifact); + } + + /** + * Function to upload timelined data + * @param data Array of `TimesketchTimeline` + * @param artifact Name of artifact that was timelined + * @returns A `TimesketchError` if the data cannot be timeline or if it failed to upload to Timesketch + */ + public async upload( + data: TimesketchTimeline[], + artifact: string, + ): Promise { + if (data.length === 0) { + return new TimesketchError(`ARTIFACT`, `zero values for ${artifact}`); + } + + // If no token or cookie. We need to logon! + if (this.token === "" || this.cookie === "") { + const status = await this.authTimesketch(); + if (status instanceof TimesketchError) { + return status; + } + } + + if (this.timesketch_auth.sketch_id === undefined) { + await this.createSketch(artifact); + } + + // Verify user provived a valid Sketch ID + const id_status = await this.verifySketchId(); + if (id_status instanceof TimesketchError) { + return id_status; + } + + return await this.uploadTimeline(data, artifact); + } + + /** + * Function to upload timeline data to Timesketch + * @param data Array of `TimesketchTimeline` + * @param artifact artifact that was timelined + * @returns A `TimesketchError` if data cannot be uploaded. + */ + private async uploadTimeline( + data: TimesketchTimeline[], + artifact: string, + ): Promise { + if (this.timeline_name === "") { + this.timeline_name = artifact; + } + + const entries_strings: string[] = []; + // We have to convert the TimesketchTimeline object to a string :/ + for (let i = 0; i < data.length; i++) { + entries_strings.push(JSON.stringify(data[ i ])); + } + + const headers = { + "X-Csrftoken": this.token, + Cookie: this.cookie, + }; + + const client: ClientRequest = { + url: `${this.timesketch_auth.url}/api/v1/upload/`, + protocol: Protocol.POST, + headers, + body_type: BodyType.FORM, + verify_ssl: this.timesketch_auth.verify_ssl, + }; + + const chunk_size = 10000; + // Split data into chunks so Timesketch does not keel over + for (let i = 0; i < entries_strings.length; i += chunk_size) { + const chunk = entries_strings.slice(i, i + chunk_size); + // From: https://github.com/google/timesketch/blob/3c781e6bde4398e24cba7dd41c4f87ba4d6e5394/importer_client/python/timesketch_import_client/importer.py#L249 + const post_data: Record = { + name: this.timeline_name, + sketch_id: this.timesketch_auth.sketch_id, + data_label: artifact, + enable_stream: false, + provider: "artemis", + events: chunk.join("\n"), + }; + + if (this.opensearch_index !== "") { + post_data[ "index_name" ] = this.opensearch_index; + } + + const bytes = encodeBytes(JSON.stringify(post_data)); + const response = await request(client, bytes); + if (response instanceof HttpError) { + return new TimesketchError( + `UPLOAD`, + `failed to upload to Timesketch ${response}`, + ); + } + + if (response.status !== 201) { + return new TimesketchError( + `UPLOAD`, + `non-201 response for uploading data ${extractUtf8String( + new Uint8Array(response.body), + ) + }`, + ); + } + + const task_info: TimelineResponse = JSON.parse( + extractUtf8String(new Uint8Array(response.body)), + ); + + // Try to get the OpenSearch Index from the upload response + if (this.opensearch_index === "") { + this.opensearch_index = + task_info.objects.at(0)?.searchindex.index_name ?? ""; + } + } + } + + /** + * Function to create a sketch + * @param artifact The artifact used to name the sketch if a name is not provided + * @returns A `TimesketchError` if a sketch cannot be created + */ + private async createSketch( + artifact: string, + ): Promise { + if (this.timesketch_auth.sketch_name === undefined) { + this.timesketch_auth.sketch_name = artifact; + } + + const headers = { + "X-Csrftoken": this.token, + Cookie: this.cookie, + "Content-Type": "application/json", + }; + + const client: ClientRequest = { + url: `${this.timesketch_auth.url}/api/v1/sketches/`, + protocol: Protocol.POST, + headers, + }; + + const body = { + "name": this.timesketch_auth.sketch_name, + "description": `Timeline for ${this.timesketch_auth.sketch_name}`, + }; + const bytes = encodeBytes(JSON.stringify(body)); + const response = await request(client, bytes); + if (response instanceof HttpError) { + return new TimesketchError( + `SKETCH_ID`, + `failed to create sketch ${this.timesketch_auth.sketch_name}: ${response}`, + ); + } + + const result = extractUtf8String(new Uint8Array(response.body)); + const sketch_object = JSON.parse(result); + const objects_value = sketch_object[ "objects" ]; + if (objects_value === undefined) { + return new TimesketchError( + `SKETCH_ID`, + `failed no objects in sketch response: ${response}`, + ); + } + this.timesketch_auth.sketch_id = objects_value.at(0)[ "id" ]; + } + + /** + * Function to verify if provided Sketch ID exists + * @returns `TimesketchError` if we cannot verify the Sketch ID + */ + private async verifySketchId(): Promise { + const headers = { + "X-Csrftoken": this.token, + Cookie: this.cookie, + }; + + const client: ClientRequest = { + url: + `${this.timesketch_auth.url}/api/v1/sketches/${this.timesketch_auth.sketch_id}/`, + protocol: Protocol.GET, + headers, + verify_ssl: this.timesketch_auth.verify_ssl, + }; + + const response = await request(client); + if (response instanceof HttpError) { + return new TimesketchError( + `SKETCH_ID`, + `failed to verify Sketch ID ${this.timesketch_auth.sketch_id} ${response}`, + ); + } + + if (response.status !== 200) { + return new TimesketchError( + `SKETCH_ID`, + `non-200 response for verifying Sketch ID ${extractUtf8String( + new Uint8Array(response.body), + ) + }`, + ); + } + const id_text = extractUtf8String(new Uint8Array(response.body)); + const id_info = JSON.parse(id_text); + if (id_info[ "objects" ].length === 0) { + return new TimesketchError( + `SKETCH_ID`, + `no Sketch ID associated ${this.timesketch_auth.sketch_id}: ${id_text}`, + ); + } + } + + /** + * Function to authenticate to Timesketch + * @returns `TimesketchError` if authentication failed + */ + private async authTimesketch(): Promise { + const client: ClientRequest = { + url: `${this.timesketch_auth.url}/login/`, + protocol: Protocol.GET, + verify_ssl: this.timesketch_auth.verify_ssl, + }; + const response = await request(client); + if (response instanceof HttpError) { + return new TimesketchError( + `AUTH`, + `failed to start auth to Timesketch ${response}`, + ); + } + + const body_text = extractUtf8String(new Uint8Array(response.body)); + // I hope they don't change this much... + const value_regex = /value=".*"/m; + const value_token = body_text.match(value_regex); + + // Extract the CSRF Token + if (typeof value_token?.[ 0 ] === "string" && value_token?.[ 0 ].length > 50) { + this.token = value_token?.[ 0 ] + .replaceAll("value=", "") + .replaceAll('"', ""); + } + + // Also need first cookie + this.cookie = response.headers[ "set-cookie" ].split(";").at(0) ?? ""; + if (this.cookie === "") { + return new TimesketchError( + `AUTH`, + `failed to get Cookie for logon ${response.headers}`, + ); + } + + // We should be able to auth to Timesketch now! + const form = { + username: this.timesketch_auth.username, + password: this.timesketch_auth.password, + csrf_token: this.token, + }; + const headers = { + "Content-Type": "application/x-www-form-urlencoded", + referrer: this.timesketch_auth.url, + Cookie: this.cookie, + }; + const form_string = JSON.stringify(form); + const bytes = encodeBytes(form_string); + + client.headers = headers; + client.body_type = BodyType.FORM; + client.follow_redirects = false; + client.protocol = Protocol.POST; + // Logon but do not follow the redirect. We need one more cookie + const auth_response = await request(client, bytes); + if (auth_response instanceof HttpError) { + return new TimesketchError( + `AUTH`, + `failed to logon and auth to Timesketch ${auth_response}`, + ); + } + + if (auth_response.status !== 302) { + return new TimesketchError( + `AUTH`, + `non-302 response for auth to Timesketch ${extractUtf8String( + new Uint8Array(auth_response.body), + ) + }`, + ); + } + + // Update the cookie so we remain authenticated + this.cookie = auth_response.headers[ "set-cookie" ].split(";").at(0) ?? ""; + if (this.cookie === "") { + return new TimesketchError( + `AUTH`, + `failed to get last Cookie for logon ${auth_response.headers}`, + ); + } + } +} diff --git a/src/unfold/plugins/bing.ts b/src/unfold/plugins/bing.ts index c7be754b..79d12a95 100644 --- a/src/unfold/plugins/bing.ts +++ b/src/unfold/plugins/bing.ts @@ -1,218 +1,218 @@ -import type { Url } from "../../../types/http/unfold"; -import { EncodingError } from "../../encoding/errors"; -import { extractUtf8String } from "../../encoding/mod"; -import { parseProtobuf } from "../../encoding/protobuf"; -import { NomError } from "../../nom/error"; -import { takeUntil } from "../../nom/mod"; -import { decodeBase64Url, extractUUID } from "./encoding"; - -/** - * Class to parse Bing searches - */ -export class Bing { - private url: Url; - - constructor(url: Url) { - this.url = url; - } - - /** - * Function to extract Bing search query data - * https://github.com/obsidianforensics/unfurl/blob/main/unfurl/parsers/parse_bing.py - */ - public parseBing() { - for (const entry of this.url.query_pairs) { - const [key, ...param] = entry.split("="); - if (key === undefined) { - continue; - } - const value = param.join("="); - switch (key.toLowerCase()) { - case "form": { - if (value === "") { - break; - } - - this.url[key] = value; - break; - } - case "sk": { - if (value === "") { - break; - } - - this.url[key] = value; - break; - } - case "ghpl": { - if (value === "") { - break; - } - - this.url[key] = value; - break; - } - case "q": { - this.url["search_query"] = value; - break; - } - case "sp": { - this.url["suggested_position"] = value; - break; - } - case "lq": { - this.url[key] = value; - break; - } - case "pq": { - this.url["partial_query"] = value; - break; - } - case "sc": { - this.url["suggestion_count"] = value; - break; - } - case "qs": { - this.url["suggestion_type"] = value; - break; - } - case "ghc": { - this.url[key] = value; - break; - } - case "cvid": { - const uuid = value; - if (uuid.length !== 32) { - this.url["conversation_id"] = value; - break; - } - this.url["conversation_id"] = extractUUID(uuid); - break; - } - case "ghacc": { - this.url[key] = value; - break; - } - // A little complex - case "filters": { - // First part of filter contains base64 data - const start = takeUntil(value, '"'); - if (start instanceof NomError) { - break; - } - const filter_value = takeUntil( - start.remaining.slice(1), - '"', - ); - if (filter_value instanceof NomError) { - break; - } - - const data = decodeBase64Url(filter_value.nommed as string); - if (data instanceof EncodingError) { - break; - } - - this.url[key] = extractUtf8String(data).split("!"); - - // Remove quotes from second part of filter - const remaining = (filter_value.remaining as string) - .replaceAll('"', "").trim(); - const values = remaining.split(" "); - for (const entry of values) { - const filter_values = entry.split(":"); - this.url[`filters_${filter_values.at(0) ?? ""}`] = filter_values.at( - 1, - ); - } - break; - } - case "ghsh": { - this.url[key] = value; - break; - } - case "pglt": { - // Could be page generation or load timing - // According to Copilot - // Always seems have value 2083 - this.url[key] = value; - break; - } - case "pc": { - // Always seems have value U531 - this.url[key] = value; - break; - } - case "ver": { - this.url["version"] = value; - break; - } - case "ptn": { - this.url[key] = value; - break; - } - case "hsh": { - this.url[key] = value; - break; - } - case "psq": { - // Seems to contain a shorter search query term - // Compilot refers to this as a "compact query" - this.url['compact_search_query'] = value; - break; - } - case "ntb": { - this.url[key] = value; - break; - } - case "!": break; - case "p": { - // Needs to be combined with "u" query to fully base64 decode? - // Seems to contain a timestamp at the end - this.url[key] = value; - break; - } - case "u": { - // Have to combine with the "p" query in order to decode - // Contains URL - this.url[key] = value; - break; - } - case "fclid": { - // Probably somekind of click identifier? - // gclid - Google Click idnetifier - // fbclid - Facebook Click identifier - this.url[key] = value; - break; - } - case "gs_lcrp": { - // Similar to Google "gs_lp"? - const value = param.at(1); - if (value === undefined) { - break; - } - const bytes = decodeBase64Url(value); - if (bytes instanceof EncodingError) { - break; - } - const proto_data = parseProtobuf(bytes); - if (proto_data instanceof EncodingError) { - break; - } - this.url[key] = proto_data; - break; - } - case undefined: { - break; - } - default: { - console.warn( - `unknown bing key: ${key}. Value: ${value}`, - ); - this.url[key] = value; - break; - } - } - } - } -} +import type { Url } from "../../../types/http/unfold"; +import { EncodingError } from "../../encoding/errors"; +import { extractUtf8String } from "../../encoding/mod"; +import { parseProtobuf } from "../../encoding/protobuf"; +import { NomError } from "../../nom/error"; +import { takeUntil } from "../../nom/mod"; +import { decodeBase64Url, extractUUID } from "./encoding"; + +/** + * Class to parse Bing searches + */ +export class Bing { + private url: Url; + + constructor(url: Url) { + this.url = url; + } + + /** + * Function to extract Bing search query data + * https://github.com/obsidianforensics/unfurl/blob/main/unfurl/parsers/parse_bing.py + */ + public parseBing() { + for (const entry of this.url.query_pairs) { + const [key, ...param] = entry.split("="); + if (key === undefined) { + continue; + } + const value = param.join("="); + switch (key.toLowerCase()) { + case "form": { + if (value === "") { + break; + } + + this.url[key] = value; + break; + } + case "sk": { + if (value === "") { + break; + } + + this.url[key] = value; + break; + } + case "ghpl": { + if (value === "") { + break; + } + + this.url[key] = value; + break; + } + case "q": { + this.url["search_query"] = value; + break; + } + case "sp": { + this.url["suggested_position"] = value; + break; + } + case "lq": { + this.url[key] = value; + break; + } + case "pq": { + this.url["partial_query"] = value; + break; + } + case "sc": { + this.url["suggestion_count"] = value; + break; + } + case "qs": { + this.url["suggestion_type"] = value; + break; + } + case "ghc": { + this.url[key] = value; + break; + } + case "cvid": { + const uuid = value; + if (uuid.length !== 32) { + this.url["conversation_id"] = value; + break; + } + this.url["conversation_id"] = extractUUID(uuid); + break; + } + case "ghacc": { + this.url[key] = value; + break; + } + // A little complex + case "filters": { + // First part of filter contains base64 data + const start = takeUntil(value, '"'); + if (start instanceof NomError) { + break; + } + const filter_value = takeUntil( + start.remaining.slice(1), + '"', + ); + if (filter_value instanceof NomError) { + break; + } + + const data = decodeBase64Url(filter_value.nommed as string); + if (data instanceof EncodingError) { + break; + } + + this.url[key] = extractUtf8String(data).split("!"); + + // Remove quotes from second part of filter + const remaining = (filter_value.remaining as string) + .replaceAll('"', "").trim(); + const values = remaining.split(" "); + for (const entry of values) { + const filter_values = entry.split(":"); + this.url[`filters_${filter_values.at(0) ?? ""}`] = filter_values.at( + 1, + ); + } + break; + } + case "ghsh": { + this.url[key] = value; + break; + } + case "pglt": { + // Could be page generation or load timing + // According to Copilot + // Always seems have value 2083 + this.url[key] = value; + break; + } + case "pc": { + // Always seems have value U531 + this.url[key] = value; + break; + } + case "ver": { + this.url["version"] = value; + break; + } + case "ptn": { + this.url[key] = value; + break; + } + case "hsh": { + this.url[key] = value; + break; + } + case "psq": { + // Seems to contain a shorter search query term + // Copilot refers to this as a "compact query" + this.url['compact_search_query'] = value; + break; + } + case "ntb": { + this.url[key] = value; + break; + } + case "!": break; + case "p": { + // Needs to be combined with "u" query to fully base64 decode? + // Seems to contain a timestamp at the end + this.url[key] = value; + break; + } + case "u": { + // Have to combine with the "p" query in order to decode + // Contains URL + this.url[key] = value; + break; + } + case "fclid": { + // Probably some kind of click identifier? + // gclid - Google Click identifier + // fbclid - Facebook Click identifier + this.url[key] = value; + break; + } + case "gs_lcrp": { + // Similar to Google "gs_lp"? + const value = param.at(1); + if (value === undefined) { + break; + } + const bytes = decodeBase64Url(value); + if (bytes instanceof EncodingError) { + break; + } + const proto_data = parseProtobuf(bytes); + if (proto_data instanceof EncodingError) { + break; + } + this.url[key] = proto_data; + break; + } + case undefined: { + break; + } + default: { + console.warn( + `unknown bing key: ${key}. Value: ${value}`, + ); + this.url[key] = value; + break; + } + } + } + } +} diff --git a/src/unix/shell_history.ts b/src/unix/shell_history.ts index c154cdd2..30665848 100644 --- a/src/unix/shell_history.ts +++ b/src/unix/shell_history.ts @@ -1,13 +1,20 @@ import { GlobInfo } from "../../types/filesystem/globs"; import { + AshHistory, BashHistory, ZshHistory, } from "../../types/unix/shellhistory"; import { FileError } from "../filesystem/errors"; -import { glob, readTextFile } from "../filesystem/files"; +import { glob, readLines, readTextFile } from "../filesystem/files"; import { PlatformType } from "../system/systeminfo"; import { unixEpochToISO } from "../time/conversion"; +/** + * Get bash history for users + * @param platform `PlatformType` to extract bash history from + * @param alt_file Optional alternative history files + * @returns Array of `BashHistory` + */ export function getBashHistory(platform: PlatformType.Linux | PlatformType.Darwin, alt_file?: string): BashHistory[] { let paths: string[] = [ "/home/*/.bash_history" ]; if (platform === PlatformType.Darwin) { @@ -53,6 +60,12 @@ export function getBashHistory(platform: PlatformType.Linux | PlatformType.Darwi return history; } +/** + * Get zsh history for users + * @param platform `PlatformType` to extract zsh history from + * @param alt_file Optional alternative history files + * @returns Array of `ZshHistory` + */ export function getZshHistory(platform: PlatformType.Linux | PlatformType.Darwin, alt_file?: string): ZshHistory[] { let paths: string[] = [ "/home/*/.zsh_history" ]; if (platform === PlatformType.Darwin) { @@ -97,6 +110,72 @@ export function getZshHistory(platform: PlatformType.Linux | PlatformType.Darwin return history; } +/** + * Get ash history for users + * @param platform `PlatformType` to extract ash history from + * @param alt_file Optional alternative history files + * @returns Array of `AshHistory` + */ +export function getAshHistory(platform: PlatformType.Linux | PlatformType.Darwin, alt_file?: string): AshHistory[] { + let paths: string[] = [ "/home/*/.ash_history" ]; + if (platform === PlatformType.Darwin) { + paths.push("/home/*/.ash_sessions/*"); + paths.push("/var/root/.ash_history"); + paths.push("/var/root/.ash_sessions/*.history"); + } + + if (alt_file !== undefined) { + paths = [ alt_file ]; + } + + const history: AshHistory[] = []; + let glob_paths: GlobInfo[] = []; + for (const path of paths) { + const info = glob(path); + if (info instanceof FileError) { + continue; + } + + glob_paths = glob_paths.concat(info); + } + + for (const path of glob_paths) { + if (!path.is_file) { + continue; + } + if (path.full_path.includes("zsh_sessions") && !path.full_path.endsWith(".history")) { + continue; + } + + let offset = 0; + const limit = 200; + while (true) { + const data = readLines(path.full_path, offset, limit); + if (data instanceof FileError) { + console.warn(`Could not read ${path.full_path}: ${data}`); + break; + } + offset += limit; + + for (let i = 0; i < data.length; i++) { + const value: AshHistory = { + history: data[ i ] ?? "", + line: i, + evidence: path.full_path + }; + + history.push(value); + + } + if (data.length < limit) { + break; + } + } + } + + return history; +} + function parseBash(text: string, path: string): BashHistory[] { const lines = text.split("\n"); const timestamp_regex = /^#([0-9]+)$/; @@ -109,7 +188,7 @@ function parseBash(text: string, path: string): BashHistory[] { history: "", timestamp: "1970-01-01T00:00:00.000Z", line: 0, - path, + evidence: path, }; const time_hit = timestamp_regex.exec(lines[ i ] ?? ""); if (time_hit === null || time_hit.length === 0) { @@ -141,7 +220,7 @@ function parseZsh(text: string, path: string): ZshHistory[] { history: "", timestamp: "1970-01-01T00:00:00.000Z", line: 0, - path, + evidence: path, }; const time_hit = timestamp_regex.exec(lines[ i ] ?? ""); if (time_hit === null || time_hit.length < 3) { diff --git a/src/utils/error.ts b/src/utils/error.ts index bd934928..4167a67a 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -1,8 +1,8 @@ export class ErrorBase extends Error { - name: T; - message: string; + override name: T; + override message: string; - constructor(name: T, message: string) { + constructor (name: T, message: string) { super(); this.name = name; this.message = message; diff --git a/src/windows/amcache.ts b/src/windows/amcache.ts index a7a0277f..2e13728f 100644 --- a/src/windows/amcache.ts +++ b/src/windows/amcache.ts @@ -1,17 +1,17 @@ -import { Amcache } from "../../types/windows/amcache"; -import { WindowsError } from "./errors"; - -/** - * Function to parse `Amcache` entries on the systemdrive - * @param path Optional path to the Amcache file - * @returns Array of `Amcache` entries parsed from the sysystemdrive letter or `WindowsError` - */ -export function getAmcache(path?: string): Amcache[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const results = js_amcache(path); - return results; - } catch (err) { - return new WindowsError("AMCACHE", `failed to parse amcache: ${err}`); - } -} +import { Amcache } from "../../types/windows/amcache"; +import { WindowsError } from "./errors"; + +/** + * Function to parse `Amcache` entries on the system drive + * @param path Optional path to the Amcache file + * @returns Array of `Amcache` entries parsed from the system drive letter or `WindowsError` + */ +export function getAmcache(path?: string): Amcache[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const results = js_amcache(path); + return results; + } catch (err) { + return new WindowsError("AMCACHE", `failed to parse amcache: ${err}`); + } +} diff --git a/src/windows/appcrash.ts b/src/windows/appcrash.ts new file mode 100644 index 00000000..3f5ae997 --- /dev/null +++ b/src/windows/appcrash.ts @@ -0,0 +1,99 @@ +import { AppCrash } from "../../types/windows/appcrash"; +import { extractUtf16String } from "../encoding/strings"; +import { getSystemDrive } from "../environment/env"; +import { FileError } from "../filesystem/errors"; +import { glob, readFile } from "../filesystem/files"; +import { NomError } from "../nom/error"; +import { Endian, nomUnsignedTwoBytes } from "../nom/helpers"; +import { filetimeToUnixEpoch, unixEpochToISO } from "../time/conversion"; +import { WindowsError } from "./errors"; + +/** + * Function to parse Application crashes associated with Report.wer files + * @param alt_path Optional glob to alternative directory containing Application crashes (Report.wer files) + * @returns Array of `AppCrash` or `WindowsError` + */ +export function extractAppCrash(alt_path?: string): AppCrash[] | WindowsError { + const drive = getSystemDrive(); + let path = `${drive}\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\*\\Report.wer`; + if (alt_path !== undefined) { + path = alt_path; + } + + const glob_paths = glob(path); + if (glob_paths instanceof FileError) { + return new WindowsError(`APPCRASH`, `Failed to glob ${path}: ${glob_paths.message}`); + } + + const values: AppCrash[] = []; + for (const entry of glob_paths) { + if (!entry.is_file) { + continue; + } + + // Crash Report.wer files are pretty small. But are usually UTF16 encoded + // Quick and lazy work around + const bytes = readFile(entry.full_path); + if (bytes instanceof FileError) { + continue; + } + // Nom the BOM mark + const remaining = nomUnsignedTwoBytes(bytes, Endian.Le); + if (remaining instanceof NomError) { + continue; + } + const lines = extractUtf16String(remaining.remaining).split("\r\n"); + + const crash: AppCrash = { + timestamp_desc: "Application Crash", + artifact: "AppCrash File", + data_type: "windows:app:crash:entry", + evidence: entry.full_path, + message: "", + path: "", + datetime: "", + report_id: "", + report_type: 0, + application_name: "" + }; + for (const line of lines) { + if (line.startsWith("AppPath")) { + crash.path = line.split("=").at(1) ?? "Unknown"; + crash.message = `Application '${crash.path}' crashed`; + } else if (line.startsWith("AppName")) { + crash.application_name = line.split("=").at(1) ?? "Unknown"; + } else if (line.startsWith("ReportIdentifier")) { + crash.report_id = line.split("=").at(1) ?? "Unknown"; + } else if (line.startsWith("ReportType")) { + crash.report_type = Number(line.split("=").at(1) ?? 0); + } else if (line.startsWith("EventTime")) { + const timestamp = BigInt(line.split("=").at(1) ?? 0n); + crash.datetime = unixEpochToISO(filetimeToUnixEpoch(timestamp)); + } + } + values.push(crash); + } + + return values; +} + +/** + * Function to test Windows AppCrash parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows AppCrash parsing + */ +export function testExtractAppCrash(): void { + const test = "../../tests/test_data/windows/appcrashes/*"; + const results = extractAppCrash(test); + if (results instanceof WindowsError) { + throw results; + } + + if(results[0]?.path !== "C:\\Windows\\System32\\dllhost.exe") { + throw `Got ${results[0]?.path} expected 'C:\\Windows\\System32\\dllhost.exe'.......extractAppCrash ❌`; + } + + if(results[0]?.message !== `Application 'C:\\Windows\\System32\\dllhost.exe' crashed`) { + throw `Got ${results[0]?.message} expected 'Application 'C:\\Windows\\System32\\dllhost.exe' crashed'.......extractAppCrash ❌`; + } +} \ No newline at end of file diff --git a/src/windows/errors.ts b/src/windows/errors.ts index daa310f7..3cba65ae 100644 --- a/src/windows/errors.ts +++ b/src/windows/errors.ts @@ -1,48 +1,53 @@ -import { ErrorBase } from "../utils/error"; - -export type ErrorName = - | "PE" - | "SHIMDB" - | "AMCACHE" - | "BITS" - | "ESE" - | "EVENTLOG" - | "JUMPLIST" - | "NTFS" - | "PREFETCH" - | "RECYCLEBIN" - | "REGISTRY" - | "SEARCH" - | "SERVICES" - | "SHELLBAGS" - | "SHIMCACHE" - | "SHIMDB" - | "SHORTCUTS" - | "SRUM" - | "TASKS" - | "USERASSIST" - | "USERS" - | "USNJRNL" - | "LOGONS" - | "CHOCOLATEYINFO" - | "UPDATESHISTORY" - | "POWERSHELL" - | "SHELLITEMS" - | "MRU" - | "UAL" - | "WMIPERSIST" - | "USB" - | "SERVICEINSTALL" - | "OUTLOOK" - | "WORDWHEEL" - | "BAM" - | "SCRIPTBLOCK" - | "FIREWALL_RULES" - | "EVENTLOG_PROCESSTREE" - | "WIFI" - | "ADCERTIFICATES" - | "RDPLOGONS" - | "PCA" - | "EVENTLOG_DEFENDER_QUARANTINE"; - -export class WindowsError extends ErrorBase { } +import { ErrorBase } from "../utils/error"; + +export type ErrorName = + | "PE" + | "SHIMDB" + | "AMCACHE" + | "BITS" + | "ESE" + | "EVENTLOG" + | "JUMPLIST" + | "NTFS" + | "PREFETCH" + | "RECYCLEBIN" + | "REGISTRY" + | "SEARCH" + | "SERVICES" + | "SHELLBAGS" + | "SHIMCACHE" + | "SHIMDB" + | "SHORTCUTS" + | "SRUM" + | "TASKS" + | "USERASSIST" + | "USERS" + | "USNJRNL" + | "LOGONS" + | "CHOCOLATEYINFO" + | "UPDATESHISTORY" + | "POWERSHELL" + | "SHELLITEMS" + | "MRU" + | "UAL" + | "WMIPERSIST" + | "USB" + | "SERVICEINSTALL" + | "OUTLOOK" + | "WORDWHEEL" + | "BAM" + | "SCRIPTBLOCK" + | "FIREWALL_RULES" + | "EVENTLOG_PROCESSTREE" + | "WIFI" + | "ADCERTIFICATES" + | "RDPLOGONS" + | "PCA" + | "EVENTLOG_DEFENDER_QUARANTINE" + | "EVENTLOG_MSI_INSTALLED" + | "EVENTLOG_BITS" + | "EVENTLOG_VELOCIRAPTOR" + | "EVENTLOG_CRASH" + | "APPCRASH"; + +export class WindowsError extends ErrorBase { } diff --git a/src/windows/ese/certs.ts b/src/windows/ese/certs.ts index 6bc454e3..125b7e01 100644 --- a/src/windows/ese/certs.ts +++ b/src/windows/ese/certs.ts @@ -1,10 +1,10 @@ -import { TableInfo } from "../../../types/windows/ese" +import { TableInfo } from "../../../types/windows/ese"; import { Certificates, RequestAttributes, Requests, RequestType, StatusCode } from "../../../types/windows/ese/certs"; import { getEnvValue } from "../../environment/mod"; import { FileError } from "../../filesystem/errors"; import { glob } from "../../filesystem/files"; import { WindowsError } from "../errors"; -import { EseDatabase } from "../ese" +import { EseDatabase } from "../ese"; /** * Class to parse Windows AD Certificate Services @@ -16,7 +16,7 @@ export class ADCertificates extends EseDatabase { * Construct a `ADCertificates` object. By default it will parse the EDB file on the SystemDrive. * @param alt_path Optional alternative path the CertLog EDB file */ - constructor(alt_path?: string) { + constructor (alt_path?: string) { let path = "\\Windows\\System32\\CertLog\\*.edb"; if (alt_path !== undefined) { path = alt_path; @@ -31,11 +31,11 @@ export class ADCertificates extends EseDatabase { console.error(`Could not glob for EDB at ${path}`); return; } - if (paths.length < 1 || paths[0] === undefined) { + if (paths.length < 1 || paths[ 0 ] === undefined) { console.error(`No EDB files found`); return; } - path = paths[0].full_path; + path = paths[ 0 ].full_path; } super(path); @@ -54,9 +54,9 @@ export class ADCertificates extends EseDatabase { } const certs: Certificates[] = []; - const cert_data = rows["Certificates"]; + const cert_data = rows[ "Certificates" ]; if (cert_data === undefined) { - return new WindowsError(`ADCERTIFICATES`, `Could not find "Certificates" table`) + return new WindowsError(`ADCERTIFICATES`, `Could not find "Certificates" table`); } for (const row of cert_data) { @@ -115,9 +115,9 @@ export class ADCertificates extends EseDatabase { } const reqs: Requests[] = []; - const req_data = rows["Requests"]; + const req_data = rows[ "Requests" ]; if (req_data === undefined) { - return new WindowsError(`ADCERTIFICATES`, `Could not find "Requests" table`) + return new WindowsError(`ADCERTIFICATES`, `Could not find "Requests" table`); } for (const row of req_data) { @@ -181,9 +181,9 @@ export class ADCertificates extends EseDatabase { } const reqs: RequestAttributes[] = []; - const req_data = rows["RequestAttributes"]; + const req_data = rows[ "RequestAttributes" ]; if (req_data === undefined) { - return new WindowsError(`ADCERTIFICATES`, `Could not find "RequestAttributes" table`) + return new WindowsError(`ADCERTIFICATES`, `Could not find "RequestAttributes" table`); } for (const row of req_data) { diff --git a/src/windows/ese/ual.ts b/src/windows/ese/ual.ts index d52d3adf..d322e506 100644 --- a/src/windows/ese/ual.ts +++ b/src/windows/ese/ual.ts @@ -22,7 +22,7 @@ export class UserAccessLogging extends EseDatabase { * Construct a `UserAccessLogging` object based on the provided UAL file. Client.mdb and .mdb files contain the logon information. SystemIdentity.mdb contains role information * @param path Path to UAL related file. Such as SystemIdentity.mdb or Current.mdb or .mdb. */ - constructor (path: string) { + constructor(path: string) { super(path); this.info = { obj_id_table: 0, @@ -57,7 +57,7 @@ export class UserAccessLogging extends EseDatabase { return rows; } - const row_data = rows[ "ROLE_IDS" ]; + const row_data = rows["ROLE_IDS"]; if (row_data === undefined) { return new WindowsError( `UAL`, @@ -90,7 +90,8 @@ export class UserAccessLogging extends EseDatabase { if (rows instanceof WindowsError) { return rows; } - const row_data = rows[ "CLIENTS" ]; + + const row_data = rows["CLIENTS"]; if (row_data === undefined) { return new WindowsError( `UAL`, @@ -286,3 +287,42 @@ export class UserAccessLogging extends EseDatabase { return raw_ip.join("."); } } + +/** + * Function to test Windows UAL parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows UAL parsing + */ +export function testUserAccessLogging(): void { + const id = "../../../tests/test_data/DFIRArtifactMuseum/ual/SystemIdentity.mdb"; + const current = "../../../tests/test_data/DFIRArtifactMuseum/ual/Current.mdb"; + const roles = new UserAccessLogging(id); + + const clients = new UserAccessLogging(current); + const data = clients.getUserAccessLog(clients.pages, roles); + if (data instanceof WindowsError) { + throw data; + } + + if (data.length !== 0) { + throw `Got '${data.length}' expected "0".......getUserAccessLog ❌`; + } + + console.info(` Function getUserAccessLog ✅`); + + const info = roles.getRoleIds(roles.pages); + if (info instanceof WindowsError) { + throw info; + } + + if (info.length !== 13) { + throw `Got '${info.length}' expected "13".......getRoleIds ❌`; + } + + if (info[8]?.name !== "Windows Deployment Services") { + throw `Got '${info[8]?.name}' expected "Windows Deployment Services".......getRoleIds ❌`; + } + + console.info(` Function getRoleIds ✅`); + +} \ No newline at end of file diff --git a/src/windows/ese/updates.ts b/src/windows/ese/updates.ts index 601a6bf2..03ff433c 100644 --- a/src/windows/ese/updates.ts +++ b/src/windows/ese/updates.ts @@ -1,226 +1,226 @@ -import { EseTable, TableInfo } from "../../../types/windows/ese"; -import { - Operation, - ServerSelection, - UpdateHistory, -} from "../../../types/windows/ese/updates"; -import { EncodingError } from "../../encoding/errors"; -import { decode } from "../../encoding/mod"; -import { formatGuid } from "../../encoding/uuid"; -import { getEnvValue } from "../../environment/env"; -import { Endian } from "../../nom/helpers"; -import { nomUnsignedFourBytes, take } from "../../nom/mod"; -import { WindowsError } from "../errors"; -import { EseDatabase } from "../ese"; - -/** - * Class to parse history of Windows Updates - */ -export class Updates extends EseDatabase { - private info: TableInfo; - pages: number[]; - - private table = "tbHistory"; - - /** - * Contruct `Updates` to parse Windows Updates history. By default will parse updates at `\Windows\SoftwareDistribution\DataStore\DataStore.edb`. Unless you specify alternative file. - * @param alt_path Optional alternative path to `DataStore.edb` - */ - constructor(alt_path?: string) { - const default_path = getEnvValue("SystemDrive"); - let path = - `${default_path}\\Windows\\SoftwareDistribution\\DataStore\\DataStore.edb`; - if (alt_path !== undefined && alt_path.endsWith("DataStore.edb")) { - path = alt_path; - } - - super(path); - this.info = { - obj_id_table: 0, - table_page: 0, - table_name: "", - column_info: [], - long_value_page: 0, - }; - this.pages = []; - this.setupHistory(); - } - - /** - * Function to parse Windows Updates History - * @param pages Array of pages to parse for the update history table - * @returns Array of `UpdateHistory` or `WindowsError` - */ - public updateHistory(pages: number[]): UpdateHistory[] | WindowsError { - if (this.info.table_name === "") { - return new WindowsError( - `UPDATESHISTORY`, - `tbHistory info object not initialized property. Table name is empty`, - ); - } - const rows = this.getRows(pages, this.info); - if (rows instanceof WindowsError) { - return rows; - } - - const row_data = rows[this.table]; - if (row_data === undefined) { - return new WindowsError( - `UPDATESHISTORY`, - `Could not find "tbHistory" table. Got undefined`, - ); - } - - return this.parseHistory(row_data); - } - - private setupHistory() { - const catalog = this.catalogInfo(); - if (catalog instanceof WindowsError) { - return; - } - - this.info = this.tableInfo(catalog, this.table); - const pages = this.getPages(this.info.table_page); - if (pages instanceof WindowsError) { - return; - } - - this.pages = pages; - } - - /** - * Parse the rows and columns of the `tbHistory` table - * @param rows Nested Array of `EseTable` rows - * @returns Array of `UpdateHistory` entries - */ - private parseHistory(rows: EseTable[][]): UpdateHistory[] { - const udpates: UpdateHistory[] = []; - - for (const row of rows) { - const update: UpdateHistory = { - client_id: "", - support_url: "", - date: "", - description: "", - operation: Operation.Unknown, - server_selection: ServerSelection.Unknown, - service_id: "", - title: "", - update_id: "", - update_revision: 0, - categories: "", - more_info: "", - }; - for (const column of row) { - if (column.column_name === "ClientId") { - update.client_id = column.column_data; - } else if (column.column_name === "SupportUrl") { - update.support_url = column.column_data; - } else if (column.column_name === "Title") { - update.title = column.column_data; - } else if (column.column_name === "Description") { - update.description = column.column_data; - } else if (column.column_name === "Categories") { - update.categories = column.column_data; - } else if (column.column_name === "MoreInfoUrl") { - update.more_info = column.column_data; - } else if (column.column_name === "Date") { - update.date = column.column_data; - } else if (column.column_name === "Status") { - switch (column.column_data) { - case "1": { - update.operation = Operation.Installation; - break; - } - case "2": { - update.operation = Operation.Uninstallation; - break; - } - } - } else if (column.column_name === "UpdateId") { - const update_info = this.getUpdateId(column.column_data); - if (update_info instanceof Error) { - console.warn(`could not parse update id info ${update_info}`); - } else { - update.update_id = update_info["guid"] as string; - update.update_revision = update_info["revision"] as number; - } - } else if (column.column_name === "ServerId") { - const update_info = this.getUpdateId(column.column_data); - if (update_info instanceof Error) { - console.warn(`could not parse service id info ${update_info}`); - } else { - update.service_id = update_info["guid"] as string; - } - } else if (column.column_name === "ServerSelection") { - update.server_selection = this.getServerSelection(column.column_data); - } - } - udpates.push(update); - } - return udpates; - } - - /** - * Get update id information - * @param data column data to parse - * @returns Object containing a GUID and revision number - */ - private getUpdateId(data: string): Record | Error { - const input = decode(data); - if (input instanceof EncodingError) { - return input; - } - const guid_size = 16; - const guid = take(input, guid_size); - if (guid instanceof Error) { - return guid; - } - - const update_id: Record = {}; - update_id["guid"] = formatGuid(Endian.Le, guid.nommed as Uint8Array); - - if (guid.remaining.length === 0) { - return update_id; - } - - const revision_data = nomUnsignedFourBytes( - guid.remaining as Uint8Array, - Endian.Le, - ); - if (revision_data instanceof Error) { - return revision_data; - } - - update_id["revision"] = revision_data.value; - return update_id; - } - - /** - * Determine the server selection status - * @param selection column data to parse - * @returns `ServerSelection` status - */ - private getServerSelection(selection: string): ServerSelection { - const value = Number(selection); - // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/aa387280(v=vs.85) - switch (value - 1) { - case 0: { - return ServerSelection.Default; - } - case 1: { - return ServerSelection.ManagedServer; - } - case 2: { - return ServerSelection.WindowsUpdate; - } - case 3: { - return ServerSelection.Others; - } - default: { - return ServerSelection.Unknown; - } - } - } -} +import { EseTable, TableInfo } from "../../../types/windows/ese"; +import { + Operation, + ServerSelection, + UpdateHistory, +} from "../../../types/windows/ese/updates"; +import { EncodingError } from "../../encoding/errors"; +import { decode } from "../../encoding/mod"; +import { formatGuid } from "../../encoding/uuid"; +import { getEnvValue } from "../../environment/env"; +import { Endian } from "../../nom/helpers"; +import { nomUnsignedFourBytes, take } from "../../nom/mod"; +import { WindowsError } from "../errors"; +import { EseDatabase } from "../ese"; + +/** + * Class to parse history of Windows Updates + */ +export class Updates extends EseDatabase { + private info: TableInfo; + pages: number[]; + + private table = "tbHistory"; + + /** + * Construct `Updates` to parse Windows Updates history. By default will parse updates at `\Windows\SoftwareDistribution\DataStore\DataStore.edb`. Unless you specify alternative file. + * @param alt_path Optional alternative path to `DataStore.edb` + */ + constructor(alt_path?: string) { + const default_path = getEnvValue("SystemDrive"); + let path = + `${default_path}\\Windows\\SoftwareDistribution\\DataStore\\DataStore.edb`; + if (alt_path !== undefined && alt_path.endsWith("DataStore.edb")) { + path = alt_path; + } + + super(path); + this.info = { + obj_id_table: 0, + table_page: 0, + table_name: "", + column_info: [], + long_value_page: 0, + }; + this.pages = []; + this.setupHistory(); + } + + /** + * Function to parse Windows Updates History + * @param pages Array of pages to parse for the update history table + * @returns Array of `UpdateHistory` or `WindowsError` + */ + public updateHistory(pages: number[]): UpdateHistory[] | WindowsError { + if (this.info.table_name === "") { + return new WindowsError( + `UPDATESHISTORY`, + `tbHistory info object not initialized property. Table name is empty`, + ); + } + const rows = this.getRows(pages, this.info); + if (rows instanceof WindowsError) { + return rows; + } + + const row_data = rows[this.table]; + if (row_data === undefined) { + return new WindowsError( + `UPDATESHISTORY`, + `Could not find "tbHistory" table. Got undefined`, + ); + } + + return this.parseHistory(row_data); + } + + private setupHistory() { + const catalog = this.catalogInfo(); + if (catalog instanceof WindowsError) { + return; + } + + this.info = this.tableInfo(catalog, this.table); + const pages = this.getPages(this.info.table_page); + if (pages instanceof WindowsError) { + return; + } + + this.pages = pages; + } + + /** + * Parse the rows and columns of the `tbHistory` table + * @param rows Nested Array of `EseTable` rows + * @returns Array of `UpdateHistory` entries + */ + private parseHistory(rows: EseTable[][]): UpdateHistory[] { + const updates: UpdateHistory[] = []; + + for (const row of rows) { + const update: UpdateHistory = { + client_id: "", + support_url: "", + date: "", + description: "", + operation: Operation.Unknown, + server_selection: ServerSelection.Unknown, + service_id: "", + title: "", + update_id: "", + update_revision: 0, + categories: "", + more_info: "", + }; + for (const column of row) { + if (column.column_name === "ClientId") { + update.client_id = column.column_data; + } else if (column.column_name === "SupportUrl") { + update.support_url = column.column_data; + } else if (column.column_name === "Title") { + update.title = column.column_data; + } else if (column.column_name === "Description") { + update.description = column.column_data; + } else if (column.column_name === "Categories") { + update.categories = column.column_data; + } else if (column.column_name === "MoreInfoUrl") { + update.more_info = column.column_data; + } else if (column.column_name === "Date") { + update.date = column.column_data; + } else if (column.column_name === "Status") { + switch (column.column_data) { + case "1": { + update.operation = Operation.Installation; + break; + } + case "2": { + update.operation = Operation.Uninstallation; + break; + } + } + } else if (column.column_name === "UpdateId") { + const update_info = this.getUpdateId(column.column_data); + if (update_info instanceof Error) { + console.warn(`could not parse update id info ${update_info}`); + } else { + update.update_id = update_info["guid"] as string; + update.update_revision = update_info["revision"] as number; + } + } else if (column.column_name === "ServerId") { + const update_info = this.getUpdateId(column.column_data); + if (update_info instanceof Error) { + console.warn(`could not parse service id info ${update_info}`); + } else { + update.service_id = update_info["guid"] as string; + } + } else if (column.column_name === "ServerSelection") { + update.server_selection = this.getServerSelection(column.column_data); + } + } + updates.push(update); + } + return updates; + } + + /** + * Get update id information + * @param data column data to parse + * @returns Object containing a GUID and revision number + */ + private getUpdateId(data: string): Record | Error { + const input = decode(data); + if (input instanceof EncodingError) { + return input; + } + const guid_size = 16; + const guid = take(input, guid_size); + if (guid instanceof Error) { + return guid; + } + + const update_id: Record = {}; + update_id["guid"] = formatGuid(Endian.Le, guid.nommed as Uint8Array); + + if (guid.remaining.length === 0) { + return update_id; + } + + const revision_data = nomUnsignedFourBytes( + guid.remaining as Uint8Array, + Endian.Le, + ); + if (revision_data instanceof Error) { + return revision_data; + } + + update_id["revision"] = revision_data.value; + return update_id; + } + + /** + * Determine the server selection status + * @param selection column data to parse + * @returns `ServerSelection` status + */ + private getServerSelection(selection: string): ServerSelection { + const value = Number(selection); + // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/aa387280(v=vs.85) + switch (value - 1) { + case 0: { + return ServerSelection.Default; + } + case 1: { + return ServerSelection.ManagedServer; + } + case 2: { + return ServerSelection.WindowsUpdate; + } + case 3: { + return ServerSelection.Others; + } + default: { + return ServerSelection.Unknown; + } + } + } +} diff --git a/src/windows/eventlogs/bits.ts b/src/windows/eventlogs/bits.ts new file mode 100644 index 00000000..1940fb2e --- /dev/null +++ b/src/windows/eventlogs/bits.ts @@ -0,0 +1,116 @@ +import { BitsEvent, BitsState, RawBitsComplete, RawBitsCreate } from "../../../types/windows/eventlogs/bits"; +import { getSystemDrive } from "../../environment/env"; +import { WindowsError } from "../errors"; +import { getEventlogs } from "../eventlogs"; + +/** + * Function to extract Windows BITS events from EventLog + * @param alt_path Optional alternative path to the BITS EventLog + * @param limit Optional limit to iterate through the EventLog + * @returns Array of `BitsEvent` or `WindowsError` + */ +export function bitsEvents(alt_path?: string, limit = 10000): BitsEvent[] | WindowsError { + const drive = getSystemDrive(); + let path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-Bits-Client%4Operational.evtx`; + if (alt_path !== undefined) { + path = alt_path; + } + + let offset = 0; + const values: BitsEvent[] = []; + + while (true) { + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "EVENTLOG_BITS", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + + const recordsData = logs[1]; + if (recordsData.length === 0) { + break; + } + + const records = recordsData as unknown as RawBitsCreate[] | RawBitsComplete[]; + for (const record of records) { + if (isCreate(record)) { + const event_data = record.data.Event.EventData; + const entry: BitsEvent = { + status: BitsState.Created, + evidence: path, + job_id: event_data.jobId, + process: event_data.processPath, + pid: event_data.processId, + user: event_data.jobOwner, + title: event_data.jobTitle, + message: `BITS Job created '${event_data.jobTitle}' by '${event_data.jobOwner}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + file_count: 0, + provider: record.data.Event.System.Provider["#attributes"].Name, + event_id: 3, + bits_event_time: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + activity_id: record.data.Event.System.Correlation["#attributes"].ActivityID, + thread_id: record.data.Event.System.Execution["#attributes"].ThreadID, + bytes_transferred: 0, + timestamp_desc: "BITS Job Created", + artifact: "BITS EventLog", + data_type: "windows:eventlogs:bits:entry" + }; + values.push(entry); + } else if (isComplete(record)) { + const event_data = record.data.Event.EventData; + const entry: BitsEvent = { + status: BitsState.Completed, + evidence: path, + job_id: event_data.jobId, + process: "", + pid: record.data.Event.System.Execution["#attributes"].ProcessID, + user: event_data.jobOwner, + title: event_data.jobTitle, + message: `BITS Job completed '${event_data.jobTitle}' by '${event_data.jobOwner}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + file_count: event_data.fileCount, + provider: record.data.Event.System.Provider["#attributes"].Name, + event_id: 4, + bits_event_time: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + activity_id: record.data.Event.System.Correlation["#attributes"].ActivityID, + thread_id: record.data.Event.System.Execution["#attributes"].ThreadID, + bytes_transferred: event_data.bytesTransferred, + timestamp_desc: "BITS Job Completed", + artifact: "BITS EventLog", + data_type: "windows:eventlogs:bits:entry" + }; + values.push(entry); + } + } + offset += limit; + } + + return values; +} + +/** + * Function to determine if EventLog entry is `RawBitsCreate` or `RawBitsComplete` event + * @param record `RawBitsCreate` or `RawBitsComplete` event + * @returns `RawBitsCreate` + */ +function isCreate(record: RawBitsCreate | RawBitsComplete): record is RawBitsCreate { + if (record.data.Event.System.EventID === 3) { + return true; + } + return false; +} + +/** + * Function to determine if EventLog entry is `RawBitsCreate` or `RawBitsComplete` event + * @param record `RawBitsCreate` or `RawBitsComplete` event + * @returns `RawBitsComplete` + */ +function isComplete(record: RawBitsComplete | RawBitsCreate): record is RawBitsComplete { + if (record.data.Event.System.EventID === 4) { + return true; + } + return false; +} \ No newline at end of file diff --git a/src/windows/eventlogs/crash.ts b/src/windows/eventlogs/crash.ts new file mode 100644 index 00000000..404e723a --- /dev/null +++ b/src/windows/eventlogs/crash.ts @@ -0,0 +1,71 @@ +import { getEventlogs } from "../../../mod"; +import { CrashEvent, RawCrash } from "../../../types/windows/eventlogs/crash"; +import { getSystemDrive } from "../../environment/env"; +import { filetimeToUnixEpoch, unixEpochToISO } from "../../time/conversion"; +import { WindowsError } from "../errors"; + +/** + * + * @param alt_path Optional alternative path to `Microsoft-Windows-WER-Diag%4Operational.evtx` + * @param limit Optional limit to iterate through the EventLog + * @returns Array of `CrashEvent` or `WindowsError` + */ +export function crashEvents(alt_path?: string, limit = 1000): CrashEvent[] | WindowsError { + const drive = getSystemDrive(); + let path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-WER-Diag%4Operational.evtx`; + if (alt_path !== undefined) { + path = alt_path; + } + + let offset = 0; + const values: CrashEvent[] = []; + + while (true) { + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "EVENTLOG_CRASH", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + + const recordsData = logs[1]; + if (recordsData.length === 0) { + break; + } + const records = recordsData as unknown as RawCrash[]; + for (const entry of records) { + if (entry.data.Event.System.EventID !== 4) { + continue; + } + const data = entry.data.Event.EventData; + const timestamp = filetimeToUnixEpoch(data.StartTime); + const crash: CrashEvent = { + evidence: entry.evidence, + pid: Number(data.ProcessId), + path: data.ModuleName, + application_start: unixEpochToISO(timestamp), + crash_time: entry.data.Event.System.TimeCreated["#attributes"].SystemTime, + crash_time_from_start: Number(data.CrashTimeFromStart), + hostname: entry.data.Event.System.Computer, + provider: entry.data.Event.System.Provider["#attributes"].Name, + guid: entry.data.Event.System.Provider["#attributes"].Guid, + channel: entry.data.Event.System.Channel, + sid: entry.data.Event.System.Security["#attributes"].UserID, + trigger: data["#attributes"].Name, + timestamp_desc: "Application Crash", + artifact: "Crash EventLog", + data_type: "windows:eventlogs:crash:entry", + message: `Application '${data.ModuleName}' crashed`, + datetime: entry.data.Event.System.TimeCreated["#attributes"].SystemTime + }; + values.push(crash); + } + + console.log(JSON.stringify(recordsData)); + offset += limit; + + } + + return values; +} diff --git a/src/windows/eventlogs/defender.ts b/src/windows/eventlogs/defender.ts index 7b6b4994..2cb17e14 100644 --- a/src/windows/eventlogs/defender.ts +++ b/src/windows/eventlogs/defender.ts @@ -1,163 +1,162 @@ -import { getEventlogs } from "../../../mod"; -import { EventLogDefenderQuarantine, RawDefenderQuarantine } from "../../../types/windows/eventlogs/defender"; -import { getEnvValue } from "../../environment/mod"; -import { WindowsError } from "../errors"; - -/** - * - * @param alt_path Optional alternative path to `Microsoft-Windows-Windows Defender%4Operational.evtx` - * @param [limit=10000] Set optional different limit for streaming events - * @returns - */ -export function defenderQuarantineEventLog(alt_path?: string, limit = 10000): EventLogDefenderQuarantine[] | WindowsError { - let path = alt_path; - - if (path === undefined) { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-Windows Defender%4Operational.evtx` - } - - const events: EventLogDefenderQuarantine[] = [] - let offset = 0; - while (true) { - // Get records 10000 at a time - const logs = getEventlogs(path, offset, limit); - if (logs instanceof WindowsError) { - return new WindowsError( - "LOGONS", - `failed to parse eventlog ${path}: ${logs}`, - ); - } - const recordsData = logs[1]; - if (recordsData.length === 0) { - break; - } - - offset += limit; - const records = recordsData as RawDefenderQuarantine[]; - - // Loop through Event Log entries - for (const record of records) { - if (record.data.Event.System.EventID !== 1116 && record.data.Event.System.EventID !== 1117) { - continue; - } - const event_data = record.data.Event.EventData; - const action = record.data.Event.System.EventID === 1116 ? "Quarantined" : "Remediated" - const value: EventLogDefenderQuarantine = { - threat_name: event_data["Threat Name"], - threat_id: Number(event_data["Threat ID"]), - path: event_data.Path.split("_").slice(1).join("_"), - thread_id: record.data.Event.System.Execution["#attributes"].ThreadID, - process_id: record.data.Event.System.Execution["#attributes"].ProcessID, - product_name: event_data["Product Name"], - product_version: event_data["Product Version"], - detection_id: event_data["Detection ID"], - detection_time: event_data["Detection Time"], - severity_id: Number(event_data["Severity ID"]), - severity_name: event_data["Severity Name"], - category_id: Number(event_data["Category ID"]), - category_name: event_data["Category Name"], - fwlink: event_data.FWLink, - status_code: Number(event_data["Status Code"]), - status_description: event_data["Status Description"], - state: Number(event_data.State), - source_id: Number(event_data["Source ID"]), - source_name: event_data["Source Name"], - process_name: event_data["Process Name"], - detection_user: event_data["Detection User"], - origin_id: Number(event_data["Origin ID"]), - origin_name: event_data["Origin Name"], - execution_id: Number(event_data["Execution ID"]), - execution_name: event_data["Execution Name"], - type_id: Number(event_data["Type ID"]), - type_name: event_data["Type Name"], - pre_execution_status: Number(event_data["Pre Execution Status"]), - action_id: Number(event_data["Action ID"]), - action_name: event_data["Action Name"], - error_code: event_data["Error Code"], - error_description: event_data["Error Description"].trim(), - post_clean_status: Number(event_data["Post Clean Status"]), - additional_actions_id: Number(event_data["Additional Actions ID"]), - additional_actions_string: event_data["Additional Actions String"], - remediation_user: event_data["Remediation User"], - message: `Defender ${action} '${event_data.Path.split("_").slice(1).join("_")}'`, - datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, - security_intelligence_version: event_data["Security intelligence Version"], - av_version: "", - anti_spyware_version: "", - network_inspection_version: "", - engine_version: event_data["Engine Version"], - anti_malware_version: "", - network_inspection_engine_version: "", - event_id: record.data.Event.System.EventID, - artifact: "Malware Detection", - timestamp_desc: record.data.Event.System.EventID === 1116 ? "Malware Detected" : "Malware Remediated", - data_type: "windows:eventlogs:defender:entry", - evidence: path, - }; - - let versions = event_data["Security intelligence Version"].split(","); - const av = versions.at(0); - if (av !== undefined) { - value.av_version = av.replace("AV: ", "").trim(); - } - const spyware = versions.at(1); - if (spyware !== undefined) { - value.anti_spyware_version = spyware.replace("AS: ", "").trim(); - } - const nis = versions.at(2); - if (nis !== undefined) { - value.network_inspection_version = nis.replace("NIS: ", "").trim(); - } - - versions = event_data["Engine Version"].split(",") - const am = versions.at(0); - if (am !== undefined) { - value.anti_malware_version = am.replace("AM: ", "").trim(); - } - const nis_engine = versions.at(1); - if (nis_engine !== undefined) { - value.network_inspection_engine_version = nis_engine.replace("NIS: ", "").trim(); - } - - events.push(value); - } - } - - return events; -} - -/** - * Function to test Windows Defender EventLogs parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows Defender EventLogs parsing - */ -export function testDefenderQuarantineEventLog(): void { - const test = "../../tests/test_data/DFIRArtifactMuseum/eventlogs/Microsoft-Windows-Windows Defender%4Operational.evtx"; - const results = defenderQuarantineEventLog(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length !== 33) { - throw `Got '${results.length}' expected "33".......defenderQuarantineEventLog ❌`; - } - - if (results[4]?.category_name !== "Trojan") { - throw `Got '${results[4]?.category_name}' expected "Trojan".......defenderQuarantineEventLog ❌`; - } - - if (results[22]?.message !== "Defender Remediated 'pid:1364:237165111778183; process:_pid:1364,ProcessStart:132914198084076670'") { - throw `Got '${results[22]?.message}' expected "Defender Remediated 'pid:1364:237165111778183; process:_pid:1364,ProcessStart:132914198084076670'".......defenderQuarantineEventLog ❌`; - } - - if (results[15]?.anti_malware_version !== "1.1.18900.3") { - throw `Got '${results[15]?.anti_malware_version}' expected "1.1.18900.3".......defenderQuarantineEventLog ❌`; - } - - console.info(` Function defenderQuarantineEventLog ✅`); - +import { getEventlogs } from "../../../mod"; +import { EventLogDefenderQuarantine, RawDefenderQuarantine } from "../../../types/windows/eventlogs/defender"; +import { getEnvValue } from "../../environment/mod"; +import { WindowsError } from "../errors"; + +/** + * Function to extract Windows Defender quarantine events + * @param alt_path Optional alternative path to `Microsoft-Windows-Windows Defender%4Operational.evtx` + * @param [limit=10000] Set optional different limit for streaming events + * @returns + */ +export function defenderQuarantineEventLog(alt_path?: string, limit = 10000): EventLogDefenderQuarantine[] | WindowsError { + let path = alt_path; + + if (path === undefined) { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-Windows Defender%4Operational.evtx` + } + + const events: EventLogDefenderQuarantine[] = [] + let offset = 0; + while (true) { + // Get records 10000 at a time + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "LOGONS", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + const recordsData = logs[1]; + if (recordsData.length === 0) { + break; + } + + offset += limit; + const records = recordsData as unknown as RawDefenderQuarantine[]; + + // Loop through Event Log entries + for (const record of records) { + if (record.data.Event.System.EventID !== 1116 && record.data.Event.System.EventID !== 1117) { + continue; + } + const event_data = record.data.Event.EventData; + const action = record.data.Event.System.EventID === 1116 ? "Quarantined" : "Remediated" + const value: EventLogDefenderQuarantine = { + threat_name: event_data["Threat Name"], + threat_id: Number(event_data["Threat ID"]), + path: event_data.Path.split("_").slice(1).join("_"), + thread_id: record.data.Event.System.Execution["#attributes"].ThreadID, + process_id: record.data.Event.System.Execution["#attributes"].ProcessID, + product_name: event_data["Product Name"], + product_version: event_data["Product Version"], + detection_id: event_data["Detection ID"], + detection_time: event_data["Detection Time"], + severity_id: Number(event_data["Severity ID"]), + severity_name: event_data["Severity Name"], + category_id: Number(event_data["Category ID"]), + category_name: event_data["Category Name"], + fwlink: event_data.FWLink, + status_code: Number(event_data["Status Code"]), + status_description: event_data["Status Description"], + state: Number(event_data.State), + source_id: Number(event_data["Source ID"]), + source_name: event_data["Source Name"], + process_name: event_data["Process Name"], + detection_user: event_data["Detection User"], + origin_id: Number(event_data["Origin ID"]), + origin_name: event_data["Origin Name"], + execution_id: Number(event_data["Execution ID"]), + execution_name: event_data["Execution Name"], + type_id: Number(event_data["Type ID"]), + type_name: event_data["Type Name"], + pre_execution_status: Number(event_data["Pre Execution Status"]), + action_id: Number(event_data["Action ID"]), + action_name: event_data["Action Name"], + error_code: event_data["Error Code"], + error_description: event_data["Error Description"].trim(), + post_clean_status: Number(event_data["Post Clean Status"]), + additional_actions_id: Number(event_data["Additional Actions ID"]), + additional_actions_string: event_data["Additional Actions String"], + remediation_user: event_data["Remediation User"], + message: `Defender ${action} '${event_data.Path.split("_").slice(1).join("_")}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + security_intelligence_version: event_data["Security intelligence Version"], + av_version: "", + anti_spyware_version: "", + network_inspection_version: "", + engine_version: event_data["Engine Version"], + anti_malware_version: "", + network_inspection_engine_version: "", + event_id: record.data.Event.System.EventID, + artifact: "Malware Detection", + timestamp_desc: record.data.Event.System.EventID === 1116 ? "Malware Detected" : "Malware Remediated", + data_type: "windows:eventlogs:defender:entry", + evidence: path, + }; + + let versions = event_data["Security intelligence Version"].split(","); + const av = versions.at(0); + if (av !== undefined) { + value.av_version = av.replace("AV: ", "").trim(); + } + const spyware = versions.at(1); + if (spyware !== undefined) { + value.anti_spyware_version = spyware.replace("AS: ", "").trim(); + } + const nis = versions.at(2); + if (nis !== undefined) { + value.network_inspection_version = nis.replace("NIS: ", "").trim(); + } + + versions = event_data["Engine Version"].split(",") + const am = versions.at(0); + if (am !== undefined) { + value.anti_malware_version = am.replace("AM: ", "").trim(); + } + const nis_engine = versions.at(1); + if (nis_engine !== undefined) { + value.network_inspection_engine_version = nis_engine.replace("NIS: ", "").trim(); + } + + events.push(value); + } + } + + return events; +} + +/** + * Function to test Windows Defender EventLogs parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows Defender EventLogs parsing + */ +export function testDefenderQuarantineEventLog(): void { + const test = "../../tests/test_data/DFIRArtifactMuseum/eventlogs/Microsoft-Windows-Windows Defender%4Operational.evtx"; + const results = defenderQuarantineEventLog(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length !== 33) { + throw `Got '${results.length}' expected "33".......defenderQuarantineEventLog ❌`; + } + + if (results[4]?.category_name !== "Trojan") { + throw `Got '${results[4]?.category_name}' expected "Trojan".......defenderQuarantineEventLog ❌`; + } + + if (results[22]?.message !== "Defender Remediated 'pid:1364:237165111778183; process:_pid:1364,ProcessStart:132914198084076670'") { + throw `Got '${results[22]?.message}' expected "Defender Remediated 'pid:1364:237165111778183; process:_pid:1364,ProcessStart:132914198084076670'".......defenderQuarantineEventLog ❌`; + } + + if (results[15]?.anti_malware_version !== "1.1.18900.3") { + throw `Got '${results[15]?.anti_malware_version}' expected "1.1.18900.3".......defenderQuarantineEventLog ❌`; + } + + console.info(` Function defenderQuarantineEventLog ✅`); } \ No newline at end of file diff --git a/src/windows/eventlogs/logons.ts b/src/windows/eventlogs/logons.ts index d42bc7e8..94eba854 100644 --- a/src/windows/eventlogs/logons.ts +++ b/src/windows/eventlogs/logons.ts @@ -1,206 +1,264 @@ -import { - LogonsWindows, - LogonType, - Raw4624Logons, - Raw4634Logoffs, -} from "../../../types/windows/eventlogs/logons"; -import { WindowsError } from "../errors"; -import { getEventlogs } from "../eventlogs"; - -/** - * Function to parse Logon and Logoff events from Security.evtx file - * @param path Path to Security.evtx file - * @param [limit=10000] How many EventLog entries to query at a time. Default is 10,000 - * @returns Array of `LogonsWindows` entries - */ -export function logonsWindows(path: string, limit = 10000): LogonsWindows[] | WindowsError { - let offset = 0; - const logon_entries: LogonsWindows[] = []; - - const logon_eid = 4624; - const logoff_eid = 4634; - - while (true) { - // Get records 10000 at a time - const logs = getEventlogs(path, offset, limit); - if (logs instanceof WindowsError) { - return new WindowsError( - "LOGONS", - `failed to parse eventlog ${path}: ${logs}`, - ); - } - const recordsData = logs[ 1 ]; - if (recordsData.length === 0) { - break; - } - - offset += limit; - - const records = recordsData as Raw4624Logons[] | Raw4634Logoffs[]; - // Loop through Event Log entries - for (const record of records) { - // Parse Logon entries - if (record.data.Event.System.EventID === logon_eid && isLogon(record)) { - const logon_event = record.data.Event.EventData; - const entry: LogonsWindows = { - logon_type: checkLogonType(logon_event.LogonType), - sid: logon_event.TargetUserSid, - account_name: logon_event.TargetUserName, - account_domain: logon_event.TargetDomainName, - logon_id: logon_event.TargetLogonId, - logon_process: logon_event.LogonProcessName, - authentication_package: logon_event.AuthenticationPackageName, - source_ip: logon_event.IpAddress, - source_workstation: logon_event.WorkstationName, - eventlog_generated: record.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, - message: `Logon by ${logon_event.TargetUserName} from ${logon_event.IpAddress}`, - datetime: record.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, - timestamp_desc: "Account Logon", - artifact: "Logon EventLog", - data_type: "windows:eventlogs:logon:entry" - }; - logon_entries.push(entry); - } else if ( - record.data.Event.System.EventID === logoff_eid && isLogoff(record) - ) { - const logon_event = record.data.Event.EventData; - const entry: LogonsWindows = { - logon_type: checkLogonType(logon_event.LogonType), - sid: logon_event.TargetUserSid, - account_name: logon_event.TargetUserName, - account_domain: logon_event.TargetDomainName, - logon_id: logon_event.TargetLogonId, - logon_process: "", - authentication_package: "", - source_ip: "", - source_workstation: "", - eventlog_generated: record.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, - message: `Logoff by ${logon_event.TargetUserName}`, - datetime: record.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, - timestamp_desc: "Account Logoff", - artifact: "Logoff EventLog", - data_type: "windows:eventlogs:logoff:entry" - }; - logon_entries.push(entry); - } - } - } - - return logon_entries; -} - -/** - * Function to determine if a EventLog entry is a logon event - * @param record `Raw4624Logons` or `Raw4634Logoffs` - * @returns boolean if record is `Raw4624Logons` - */ -function isLogon( - record: Raw4624Logons | Raw4634Logoffs, -): record is Raw4624Logons { - if (record.data.Event.System.EventID === 4624) { - return true; - } - return false; -} - -/** - * Function to determine if a EventLog entry is a logoff event - * @param record `Raw4624Logons` or `Raw4634Logoffs` - * @returns boolean if record is `Raw4634Logoffs` - */ -function isLogoff( - record: Raw4624Logons | Raw4634Logoffs, -): record is Raw4634Logoffs { - if (record.data.Event.System.EventID === 4634) { - return true; - } - return false; -} - -/** - * Function to lookup logon types - * @param logon_type EventLog Logon Type number - * @returns LogonType enum - */ -function checkLogonType(logon_type: number): LogonType { - switch (logon_type) { - case 2: { - return LogonType.Interactive; - } - case 3: { - return LogonType.Network; - } - case 4: { - return LogonType.Batch; - } - case 5: { - return LogonType.Service; - } - case 7: { - return LogonType.Unlock; - } - case 8: { - return LogonType.NetworkCleartext; - } - case 9: { - return LogonType.NewCredentials; - } - case 10: { - return LogonType.RemoteInteractive; - } - case 11: { - return LogonType.CacheInteractive; - } - default: { - return LogonType.Unknown; - } - } -} - -/** - * Function to test Windows Logons parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows Logons parsing - */ -export function testLogonsWindows(): void { - const test = "../../tests/test_data/windows/eventlogs/Security.evtx"; - const results = logonsWindows(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length !== 200) { - throw `Got ${results.length} logon events, expected 200.......logonsWindows ❌`; - } - if (results[ 1 ] === undefined) { - throw `Got undefined logon event.......logonsWindows ❌`; - } - - if (results[ 1 ].eventlog_generated != "2022-10-31T03:30:46.218854Z") { - throw `Got ${results[ 1 ].eventlog_generated} for logon time, expected "2022-10-31T03:30:46.218854Z".......logonsWindows ❌`; - } - - console.info(` Function logonsWindows ✅`); - - - const logon_types = [ 2, 3, 4, 5, 7, 8, 9, 10, 11 ]; - for (const entry of logon_types) { - const type_result = checkLogonType(entry); - if (type_result === LogonType.Unknown) { - throw `Got Unknown logon type ${type_result}.......checkLogonType ❌`; - } - } - console.info(` Function checkLogonType ✅`); - - const logon = `{"event_record_id":84182,"timestamp":"2025-08-31T03:06:46.605720000Z","data":{"Event":{"#attributes":{"xmlns":"http://schemas.microsoft.com/win/2004/08/events/event"},"System":{"Provider":{"#attributes":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"54849625-5478-4994-A5BA-3E3B0328C30D"}},"EventID":4624,"Version":3,"Level":0,"Task":12544,"Opcode":0,"Keywords":"0x8020000000000000","TimeCreated":{"#attributes":{"SystemTime":"2025-08-31T03:06:46.605720Z"}},"EventRecordID":84182,"Correlation":null,"Execution":{"#attributes":{"ProcessID":876,"ThreadID":1340}},"Channel":"Security","Computer":"win","Security":null},"EventData":{"SubjectUserSid":"S-1-0-0","SubjectUserName":"-","SubjectDomainName":"-","SubjectLogonId":"0x0","TargetUserSid":"S-1-5-18","TargetUserName":"SYSTEM","TargetDomainName":"NT AUTHORITY","TargetLogonId":"0x3e7","LogonType":0,"LogonProcessName":"-","AuthenticationPackageName":"-","WorkstationName":"-","LogonGuid":"00000000-0000-0000-0000-000000000000","TransmittedServices":"-","LmPackageName":"-","KeyLength":0,"ProcessId":"0x4","ProcessName":"","IpAddress":"-","IpPort":"-","ImpersonationLevel":"-","RestrictedAdminMode":"-","RemoteCredentialGuard":"-","TargetOutboundUserName":"-","TargetOutboundDomainName":"-","VirtualAccount":"%%1843","TargetLinkedLogonId":"0x0","ElevatedToken":"%%1842"}}}}`; - if (!isLogon(JSON.parse(logon))) { - throw `Did not get logon event.......isLogon ❌`; - } - console.info(` Function isLogon ✅`); - - const logoff = `{"event_record_id":84336,"timestamp":"2025-08-31T03:07:39.322481000Z","data":{"Event":{"#attributes":{"xmlns":"http://schemas.microsoft.com/win/2004/08/events/event"},"System":{"Provider":{"#attributes":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"54849625-5478-4994-A5BA-3E3B0328C30D"}},"EventID":4634,"Version":0,"Level":0,"Task":12545,"Opcode":0,"Keywords":"0x8020000000000000","TimeCreated":{"#attributes":{"SystemTime":"2025-08-31T03:07:39.322481Z"}},"EventRecordID":84336,"Correlation":null,"Execution":{"#attributes":{"ProcessID":876,"ThreadID":2132}},"Channel":"Security","Computer":"win","Security":null},"EventData":{"TargetUserSid":"S-1-5-96-0-1","TargetUserName":"UMFD-1","TargetDomainName":"Font Driver Host","TargetLogonId":"0x1381d","LogonType":2}}}}`; - if (!isLogoff(JSON.parse(logoff))) { - throw `Did not get logoff event.......isLogoff ❌`; - } - console.info(` Function isLogoff ✅`); -} \ No newline at end of file +import { + LogonsWindows, + LogonType, + Raw4624Logons, + Raw4625FailedLogons, + Raw4634Logoffs, +} from "../../../types/windows/eventlogs/logons"; +import { WindowsError } from "../errors"; +import { getEventlogs } from "../eventlogs"; + +/** + * Function to parse Logon, Logoff, and Failed Logon events from Security.evtx file + * @param path Path to Security.evtx file + * @param [limit=10000] How many EventLog entries to query at a time. Default is 10,000 + * @returns Array of `LogonsWindows` entries + */ +export function logonsWindows(path: string, limit = 10000): LogonsWindows[] | WindowsError { + let offset = 0; + const logon_entries: LogonsWindows[] = []; + + const logon_eid = 4624; + const logoff_eid = 4634; + const failed_logon_eid = 4625; + + while (true) { + // Get records 10000 at a time + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "LOGONS", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + const recordsData = logs[1]; + if (recordsData.length === 0) { + break; + } + + offset += limit; + + const records = recordsData as unknown as Raw4624Logons[] | Raw4634Logoffs[] | Raw4625FailedLogons[]; + // Loop through Event Log entries + for (const record of records) { + // Parse Logon entries + if (record.data.Event.System.EventID === logon_eid && isLogon(record)) { + const logon_event = record.data.Event.EventData; + const system_event = record.data.Event.System; + const entry: LogonsWindows = { + logon_type: checkLogonType(logon_event.LogonType), + sid: logon_event.TargetUserSid, + account_name: logon_event.TargetUserName, + account_domain: logon_event.TargetDomainName, + logon_id: logon_event.TargetLogonId, + logon_process: logon_event.LogonProcessName, + authentication_package: logon_event.AuthenticationPackageName, + source_ip: logon_event.IpAddress, + source_workstation: logon_event.WorkstationName, + eventlog_generated: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + message: `Logon by '${logon_event.TargetUserName}' from '${logon_event.IpAddress}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + timestamp_desc: "Account Logon", + artifact: "Logon EventLog", + data_type: "windows:eventlogs:logon:entry", + evidence: path, + activity_id: system_event.Correlation === null ? "" : system_event.Correlation["#attributes"].ActivityID, + computer: record.data.Event.System.Computer, + channel: record.data.Event.System.Channel, + provider: record.data.Event.System.Provider["#attributes"].Name, + provider_guid: record.data.Event.System.Provider["#attributes"].Guid, + }; + logon_entries.push(entry); + } else if ( + record.data.Event.System.EventID === logoff_eid && isLogoff(record) + ) { + const logon_event = record.data.Event.EventData; + const entry: LogonsWindows = { + logon_type: checkLogonType(logon_event.LogonType), + sid: logon_event.TargetUserSid, + account_name: logon_event.TargetUserName, + account_domain: logon_event.TargetDomainName, + logon_id: logon_event.TargetLogonId, + logon_process: "", + authentication_package: "", + source_ip: "", + source_workstation: "", + eventlog_generated: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + message: `Logoff by '${logon_event.TargetUserName}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + timestamp_desc: "Account Logoff", + artifact: "Logoff EventLog", + data_type: "windows:eventlogs:logoff:entry", + evidence: path, + activity_id: "", + computer: record.data.Event.System.Computer, + channel: record.data.Event.System.Channel, + provider: record.data.Event.System.Provider["#attributes"].Name, + provider_guid: record.data.Event.System.Provider["#attributes"].Guid, + }; + logon_entries.push(entry); + } else if (record.data.Event.System.EventID === failed_logon_eid && isFailedLogon(record)) { + const failed_event = record.data.Event.EventData; + const entry: LogonsWindows = { + logon_type: checkLogonType(failed_event.LogonType), + sid: failed_event.TargetUserSid, + account_name: failed_event.TargetUserName, + account_domain: failed_event.TargetDomainName, + logon_process: failed_event.LogonProcessName, + authentication_package: failed_event.AuthenticationPackageName, + source_ip: failed_event.IpAddress, + source_workstation: failed_event.WorkstationName, + eventlog_generated: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + message: `Failed Logon by '${failed_event.TargetUserName}' from '${failed_event.IpAddress}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + evidence: path, + timestamp_desc: "Account Failed Logon", + artifact: "Failed Logon EventLog", + data_type: "windows:eventlogs:logon:failed:entry", + provider: record.data.Event.System.Provider["#attributes"].Name, + provider_guid: record.data.Event.System.Provider["#attributes"].Guid, + activity_id: record.data.Event.System.Correlation["#attributes"].ActivityID, + computer: record.data.Event.System.Computer, + channel: record.data.Event.System.Channel, + logon_id: "" + }; + logon_entries.push(entry); + } + } + } + + return logon_entries; +} + +/** + * Function to determine if a EventLog entry is a logon event + * @param record `Raw4624Logons` or `Raw4634Logoffs` + * @returns boolean if record is `Raw4624Logons` + */ +function isLogon( + record: Raw4624Logons | Raw4634Logoffs | Raw4625FailedLogons, +): record is Raw4624Logons { + if (record.data.Event.System.EventID === 4624) { + return true; + } + return false; +} + +/** + * Function to determine if a EventLog entry is a logoff event + * @param record `Raw4624Logons` or `Raw4634Logoffs` or `Raw4625FailedLogons` + * @returns boolean if record is `Raw4634Logoffs` + */ +function isLogoff( + record: Raw4624Logons | Raw4634Logoffs | Raw4625FailedLogons, +): record is Raw4634Logoffs { + if (record.data.Event.System.EventID === 4634) { + return true; + } + return false; +} + +/** + * Function to determine if a EventLog entry is a a failed logon event + * @param record `Raw4624Logons` or `Raw4634Logoffs` or `Raw4625FailedLogons` + * @returns boolean if record is `Raw4625FailedLogons` + */ +function isFailedLogon(record: Raw4624Logons | Raw4634Logoffs | Raw4625FailedLogons, +): record is Raw4625FailedLogons { + if (record.data.Event.System.EventID === 4625) { + return true; + } + return false; +} + +/** + * Function to lookup logon types + * @param logon_type EventLog Logon Type number + * @returns LogonType enum + */ +function checkLogonType(logon_type: number): LogonType { + switch (logon_type) { + case 2: { + return LogonType.Interactive; + } + case 3: { + return LogonType.Network; + } + case 4: { + return LogonType.Batch; + } + case 5: { + return LogonType.Service; + } + case 7: { + return LogonType.Unlock; + } + case 8: { + return LogonType.NetworkCleartext; + } + case 9: { + return LogonType.NewCredentials; + } + case 10: { + return LogonType.RemoteInteractive; + } + case 11: { + return LogonType.CacheInteractive; + } + default: { + return LogonType.Unknown; + } + } +} + +/** + * Function to test Windows Logons parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows Logons parsing + */ +export function testLogonsWindows(): void { + const test = "../../tests/test_data/windows/eventlogs/Security.evtx"; + const results = logonsWindows(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length !== 200) { + throw `Got ${results.length} logon events, expected 200.......logonsWindows ❌`; + } + if (results[1] === undefined) { + throw `Got undefined logon event.......logonsWindows ❌`; + } + + if (results[1].eventlog_generated != "2022-10-31T03:30:46.218854Z") { + throw `Got ${results[1].eventlog_generated} for logon time, expected "2022-10-31T03:30:46.218854Z".......logonsWindows ❌`; + } + + if (results[127]?.message != "Logon by 'SYSTEM' from '-'") { + throw `Got ${results[127]?.message} for message, expected "Logon by 'SYSTEM' from '-'".......logonsWindows ❌`; + } + + console.info(` Function logonsWindows ✅`); + + + const logon_types = [2, 3, 4, 5, 7, 8, 9, 10, 11]; + for (const entry of logon_types) { + const type_result = checkLogonType(entry); + if (type_result === LogonType.Unknown) { + throw `Got Unknown logon type ${type_result}.......checkLogonType ❌`; + } + } + console.info(` Function checkLogonType ✅`); + + const logon = `{"event_record_id":84182,"timestamp":"2025-08-31T03:06:46.605720000Z","data":{"Event":{"#attributes":{"xmlns":"http://schemas.microsoft.com/win/2004/08/events/event"},"System":{"Provider":{"#attributes":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"54849625-5478-4994-A5BA-3E3B0328C30D"}},"EventID":4624,"Version":3,"Level":0,"Task":12544,"Opcode":0,"Keywords":"0x8020000000000000","TimeCreated":{"#attributes":{"SystemTime":"2025-08-31T03:06:46.605720Z"}},"EventRecordID":84182,"Correlation":null,"Execution":{"#attributes":{"ProcessID":876,"ThreadID":1340}},"Channel":"Security","Computer":"win","Security":null},"EventData":{"SubjectUserSid":"S-1-0-0","SubjectUserName":"-","SubjectDomainName":"-","SubjectLogonId":"0x0","TargetUserSid":"S-1-5-18","TargetUserName":"SYSTEM","TargetDomainName":"NT AUTHORITY","TargetLogonId":"0x3e7","LogonType":0,"LogonProcessName":"-","AuthenticationPackageName":"-","WorkstationName":"-","LogonGuid":"00000000-0000-0000-0000-000000000000","TransmittedServices":"-","LmPackageName":"-","KeyLength":0,"ProcessId":"0x4","ProcessName":"","IpAddress":"-","IpPort":"-","ImpersonationLevel":"-","RestrictedAdminMode":"-","RemoteCredentialGuard":"-","TargetOutboundUserName":"-","TargetOutboundDomainName":"-","VirtualAccount":"%%1843","TargetLinkedLogonId":"0x0","ElevatedToken":"%%1842"}}}}`; + if (!isLogon(JSON.parse(logon))) { + throw `Did not get logon event.......isLogon ❌`; + } + console.info(` Function isLogon ✅`); + + const logoff = `{"event_record_id":84336,"timestamp":"2025-08-31T03:07:39.322481000Z","data":{"Event":{"#attributes":{"xmlns":"http://schemas.microsoft.com/win/2004/08/events/event"},"System":{"Provider":{"#attributes":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"54849625-5478-4994-A5BA-3E3B0328C30D"}},"EventID":4634,"Version":0,"Level":0,"Task":12545,"Opcode":0,"Keywords":"0x8020000000000000","TimeCreated":{"#attributes":{"SystemTime":"2025-08-31T03:07:39.322481Z"}},"EventRecordID":84336,"Correlation":null,"Execution":{"#attributes":{"ProcessID":876,"ThreadID":2132}},"Channel":"Security","Computer":"win","Security":null},"EventData":{"TargetUserSid":"S-1-5-96-0-1","TargetUserName":"UMFD-1","TargetDomainName":"Font Driver Host","TargetLogonId":"0x1381d","LogonType":2}}}}`; + if (!isLogoff(JSON.parse(logoff))) { + throw `Did not get logoff event.......isLogoff ❌`; + } + console.info(` Function isLogoff ✅`); +} diff --git a/src/windows/eventlogs/msi.ts b/src/windows/eventlogs/msi.ts new file mode 100644 index 00000000..0b1dcde6 --- /dev/null +++ b/src/windows/eventlogs/msi.ts @@ -0,0 +1,104 @@ +import { getEventlogs } from "../../../mod"; +import { MsiInstalled, RawMsiInstalled } from "../../../types/windows/eventlogs/msi"; +import { getEnvValue } from "../../environment/mod"; +import { WindowsError } from "../errors"; + +/** + * Function to extract installed MSI applications + * @param alt_path Optional alternative path to `Application.evtx` + * @param [limit=10000] Set optional different limit for streaming events + * @returns + */ +export function msiInstalled(alt_path?: string, limit = 10000): MsiInstalled[] | WindowsError { + let path = alt_path; + + if (path === undefined) { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = `${drive}\\Windows\\System32\\winevt\\Logs\\Application.evtx`; + } + + const events: MsiInstalled[] = []; + let offset = 0; + while (true) { + // Get records 10000 at a time + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "EVENTLOG_MSI_INSTALLED", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + const recordsData = logs[ 1 ] as unknown as RawMsiInstalled[]; + + offset += limit; + + for (const entry of recordsData) { + if (typeof entry.data.Event.System.EventID !== 'object' || typeof entry.data.Event.System.Provider !== 'object') { + continue; + } + if (entry.data.Event.System.EventID[ "#text" ] !== 1033 || entry.data.Event.System.Provider[ "#attributes" ].Name !== "MsiInstaller") { + continue; + } + + const entry_event = entry.data.Event.System; + const entry_data = entry.data.Event.EventData; + const value: MsiInstalled = { + name: entry_data.Data[ "#text" ].at(0) ?? "Unknown", + language: Number(entry_data.Data[ "#text" ].at(2) ?? 0), + version: entry_data.Data[ "#text" ].at(1) ?? "Unknown", + manufacturer: entry_data.Data[ "#text" ].at(4) ?? "Unknown", + installation_status: Number(entry_data.Data[ "#text" ].at(3) ?? 0), + hostname: entry_event.Computer, + sid: entry_event.Security[ "#attributes" ].UserID, + pid: entry_event.Execution[ "#attributes" ].ProcessID, + thread_id: entry_event.Execution[ "#attributes" ].ThreadID, + message: `MSI '${entry_data.Data[ "#text" ].at(0) ?? "Unknown"}' installed`, + datetime: entry.timestamp, + timestamp_desc: "MSI Installed", + artifact: "EventLog MSI Installed 1033", + data_type: "windows:eventlog:application:msi", + evidence: path, + }; + events.push(value); + } + if (recordsData.length < limit) { + break; + } + } + + return events; +} + +/** + * Function to test Windows MSI Installed EventLogs parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows MSI Installed EventLogs parsing + */ +export function testMsiInstalled(): void { + const test = "../../tests/test_data/DFIRArtifactMuseum/eventlogs/Application.evtx"; + const results = msiInstalled(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length !== 6) { + throw `Got '${results.length}' expected "6".......msiInstalled ❌`; + } + + if (results[ 4 ]?.name !== "VMware Tools") { + throw `Got '${results[ 4 ]?.name}' expected "VMware Tools".......msiInstalled ❌`; + } + + if (results[ 5 ]?.message !== "MSI 'Microsoft Update Health Tools' installed") { + throw `Got '${results[ 5 ]?.message}' expected "MSI 'Microsoft Update Health Tools' installed".......msiInstalled ❌`; + } + + if (results[ 2 ]?.version !== "14.28.29913") { + throw `Got '${results[ 2 ]?.version}' expected "14.28.29913".......msiInstalled ❌`; + } + + console.info(` Function msiInstalled ✅`); +} \ No newline at end of file diff --git a/src/windows/eventlogs/processtree.ts b/src/windows/eventlogs/processtree.ts index c38d099a..8dcf1d28 100644 --- a/src/windows/eventlogs/processtree.ts +++ b/src/windows/eventlogs/processtree.ts @@ -1,208 +1,208 @@ -import { getEventlogs } from "../../../mod"; -import { EventLogProcessTree, ProcTracker, RawProcess } from "../../../types/windows/eventlogs/processtree"; -import { getEnvValue } from "../../environment/mod"; -import { WindowsError } from "../errors"; - -/** - * Attempt to create a process tree from Security.evtx file - * @param path Optional path to Security.evtx file - * @returns Array of `EventLogProcessTree` or `WindowsError` - */ -export function processTreeEventLogs(path?: string): EventLogProcessTree[] | WindowsError { - let log_path = path; - if (log_path === undefined) { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - log_path = `${drive}\\Windows\\System32\\winevt\\Logs\\Security.evtx`; - } - let offset = 0; - const limit = 5000; - const eid = 4688; - - const procs_map: Record = {}; - const raw_procs: RawProcess[] = []; - while (true) { - const logs = getEventlogs(log_path, offset, limit); - if (logs instanceof WindowsError) { - return new WindowsError( - "EVENTLOG_PROCESSTREE", - `failed to parse eventlog ${path}: ${logs}`, - ); - } - - // If empty we are done. No more entries are in the log - if (logs[1].length === 0) { - break; - } - - // Increase to next chunk of entries - offset += limit; - const data = logs[1] as RawProcess[]; - for (const entry of data) { - // Skip non process 4688 events - if (entry.data.Event.System.EventID !== eid) { - continue; - } - const logon_id = Number(entry.data.Event.EventData.SubjectLogonId); - const parent = Number(entry.data.Event.EventData.ProcessId); - const pid = Number(entry.data.Event.EventData.NewProcessId); - const name = entry.data.Event.EventData.NewProcessName; - const parent_name = entry.data.Event.EventData.ParentProcessName ?? ""; - const track: ProcTracker = { - login_id: logon_id, - pid: pid, - parent: parent, - name, - parent_name, - record: entry.event_record_id, - }; - // Track new process - if (procs_map[`${logon_id}_${pid}`] !== undefined) { - const old = procs_map[`${logon_id}_${pid}`] as ProcTracker | ProcTracker[]; - - if (Array.isArray(old)) { - old.push(track); - procs_map[`${logon_id}_${pid}`] = old; - continue; - } - procs_map[`${logon_id}_${pid}`] = [old, track]; - continue; - } - procs_map[`${logon_id}_${pid}`] = track; - raw_procs.push(entry); - } - } - return createProcessTree(raw_procs, procs_map, log_path); -} - -/** - * Generated Process Trees from EventLog data - * @param data Array of `RawProcess` EventLogs - * @param proc_maps `ProcTracker` object - * @param evtx_path Path to the parsed evtx file - * @returns Array of `EventLogProcessTree` - */ -function createProcessTree(data: RawProcess[], proc_maps: Record, evtx_path: string): EventLogProcessTree[] { - const entries: EventLogProcessTree[] = []; - for (const value of data) { - const raw_event = value.data.Event.EventData; - - const logon_id = Number(raw_event.SubjectLogonId); - const parent_pid = Number(raw_event.ProcessId); - const pid = Number(raw_event.NewProcessId); - const message = `${raw_event.NewProcessName}`; - const tracker: string[] = []; - const tree_message = getParent(logon_id, parent_pid, proc_maps, message, tracker, value.event_record_id); - const entry: EventLogProcessTree = { - pid, - parent_pid, - process_name: raw_event.NewProcessName.split("\\").pop() ?? "", - process_path: raw_event.NewProcessName, - parent_name: (raw_event.ParentProcessName ?? "").split("\\").pop() ?? "", - parent_path: raw_event.ParentProcessName ?? "", - user: raw_event.SubjectUserName, - sid: raw_event.SubjectUserSid, - domain: raw_event.SubjectDomainName, - commandline: raw_event.CommandLine ?? "", - message: tree_message, - datetime: value.timestamp, - timestamp_desc: "EventLog Generated", - artifact: "EventLogs Process Tree", - evtx_path, - data_type: "windows:eventlogs:proctree:entry", - record: value.event_record_id, - logon_id, - }; - entries.push(entry); - } - - return entries; -} - -/** - * Lookup parent process info - * @param logon_id Current login ID - * @param parent Parent PID - * @param data Our `ProcTracker` object - * @param message Current process tree message - * @param tracker Array to prevent recursive lookups - * @param record EventLog record number associated with child process - * @returns Process tree - */ -function getParent(logon_id: number, parent: number, data: Record, message: string, tracker: string[], record: number): string { - const system = 4; - if (parent === system) { - return `SYSTEM->${message}`; - } - const key = `${logon_id}_${parent}`; - if (tracker.includes(key)) { - return message; - } - tracker.push(key); - let value = data[key]; - if (value === undefined) { - return message; - } - if (Array.isArray(value)) { - const real_value = duplicateIds(value, record); - if (real_value === undefined) { - return message; - } - value = real_value; - } - // The parent record cannot be newer/larget than the child process - if (value.record >= record) { - return message; - } - - let updated_message = message; - if (value.name !== "") { - updated_message = `${value.name}->${message}`; - } - - const next_parent = value.parent; - const next_logon_id = value.login_id; - return getParent(next_logon_id, next_parent, data, updated_message, tracker, value.record); -} - -/** - * When process IDs are reused we need to determine the closest one to our current child process based on the EventLog record number - * @param values Array of `ProcTracker` - * @param record Record number of child process - * @returns `ProcTracker` object or undefined - */ -function duplicateIds(values: ProcTracker[], record: number): ProcTracker | undefined { - const filter: ProcTracker[] = []; - for (const value of values) { - if (value.record >= record) { - continue; - } - filter.push(value); - } - if (filter.length === 0 || filter[0] === undefined) { - return undefined; - } - if (filter.length === 1) { - return filter[0]; - } - - - // The parent record number closest to us we treat as our parent - // Get the largest record in our filtered list - let key = filter[0].record; - for (const entry of filter) { - if (entry.record > key) { - key = entry.record; - } - } - - for (const entry of filter) { - if (key !== entry.record) { - continue; - } - return entry; - } - return undefined; +import { getEventlogs } from "../../../mod"; +import { EventLogProcessTree, ProcTracker, RawProcess } from "../../../types/windows/eventlogs/processtree"; +import { getEnvValue } from "../../environment/mod"; +import { WindowsError } from "../errors"; + +/** + * Attempt to create a process tree from Security.evtx file + * @param path Optional path to Security.evtx file + * @returns Array of `EventLogProcessTree` or `WindowsError` + */ +export function processTreeEventLogs(path?: string): EventLogProcessTree[] | WindowsError { + let log_path = path; + if (log_path === undefined) { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + log_path = `${drive}\\Windows\\System32\\winevt\\Logs\\Security.evtx`; + } + let offset = 0; + const limit = 5000; + const eid = 4688; + + const procs_map: Record = {}; + const raw_procs: RawProcess[] = []; + while (true) { + const logs = getEventlogs(log_path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "EVENTLOG_PROCESSTREE", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + + // If empty we are done. No more entries are in the log + if (logs[ 1 ].length === 0) { + break; + } + + // Increase to next chunk of entries + offset += limit; + const data = logs[ 1 ] as unknown as RawProcess[]; + for (const entry of data) { + // Skip non process 4688 events + if (entry.data.Event.System.EventID !== eid) { + continue; + } + const logon_id = Number(entry.data.Event.EventData.SubjectLogonId); + const parent = Number(entry.data.Event.EventData.ProcessId); + const pid = Number(entry.data.Event.EventData.NewProcessId); + const name = entry.data.Event.EventData.NewProcessName; + const parent_name = entry.data.Event.EventData.ParentProcessName ?? ""; + const track: ProcTracker = { + login_id: logon_id, + pid: pid, + parent: parent, + name, + parent_name, + record: entry.event_record_id, + }; + // Track new process + if (procs_map[ `${logon_id}_${pid}` ] !== undefined) { + const old = procs_map[ `${logon_id}_${pid}` ] as ProcTracker | ProcTracker[]; + + if (Array.isArray(old)) { + old.push(track); + procs_map[ `${logon_id}_${pid}` ] = old; + continue; + } + procs_map[ `${logon_id}_${pid}` ] = [ old, track ]; + continue; + } + procs_map[ `${logon_id}_${pid}` ] = track; + raw_procs.push(entry); + } + } + return createProcessTree(raw_procs, procs_map, log_path); +} + +/** + * Generated Process Trees from EventLog data + * @param data Array of `RawProcess` EventLogs + * @param proc_maps `ProcTracker` object + * @param evtx_path Path to the parsed evtx file + * @returns Array of `EventLogProcessTree` + */ +function createProcessTree(data: RawProcess[], proc_maps: Record, evtx_path: string): EventLogProcessTree[] { + const entries: EventLogProcessTree[] = []; + for (const value of data) { + const raw_event = value.data.Event.EventData; + + const logon_id = Number(raw_event.SubjectLogonId); + const parent_pid = Number(raw_event.ProcessId); + const pid = Number(raw_event.NewProcessId); + const message = `${raw_event.NewProcessName}`; + const tracker: string[] = []; + const tree_message = getParent(logon_id, parent_pid, proc_maps, message, tracker, value.event_record_id); + const entry: EventLogProcessTree = { + pid, + parent_pid, + process_name: raw_event.NewProcessName.split("\\").pop() ?? "", + process_path: raw_event.NewProcessName, + parent_name: (raw_event.ParentProcessName ?? "").split("\\").pop() ?? "", + parent_path: raw_event.ParentProcessName ?? "", + user: raw_event.SubjectUserName, + sid: raw_event.SubjectUserSid, + domain: raw_event.SubjectDomainName, + commandline: raw_event.CommandLine ?? "", + message: tree_message, + datetime: value.timestamp, + timestamp_desc: "EventLog Generated", + artifact: "EventLogs Process Tree", + data_type: "windows:eventlogs:proctree:entry", + record: value.event_record_id, + logon_id, + evidence: evtx_path, + }; + entries.push(entry); + } + + return entries; +} + +/** + * Lookup parent process info + * @param logon_id Current login ID + * @param parent Parent PID + * @param data Our `ProcTracker` object + * @param message Current process tree message + * @param tracker Array to prevent recursive lookups + * @param record EventLog record number associated with child process + * @returns Process tree + */ +function getParent(logon_id: number, parent: number, data: Record, message: string, tracker: string[], record: number): string { + const system = 4; + if (parent === system) { + return `SYSTEM->${message}`; + } + const key = `${logon_id}_${parent}`; + if (tracker.includes(key)) { + return message; + } + tracker.push(key); + let value = data[ key ]; + if (value === undefined) { + return message; + } + if (Array.isArray(value)) { + const real_value = duplicateIds(value, record); + if (real_value === undefined) { + return message; + } + value = real_value; + } + // The parent record cannot be newer/larger than the child process + if (value.record >= record) { + return message; + } + + let updated_message = message; + if (value.name !== "") { + updated_message = `${value.name}->${message}`; + } + + const next_parent = value.parent; + const next_logon_id = value.login_id; + return getParent(next_logon_id, next_parent, data, updated_message, tracker, value.record); +} + +/** + * When process IDs are reused we need to determine the closest one to our current child process based on the EventLog record number + * @param values Array of `ProcTracker` + * @param record Record number of child process + * @returns `ProcTracker` object or undefined + */ +function duplicateIds(values: ProcTracker[], record: number): ProcTracker | undefined { + const filter: ProcTracker[] = []; + for (const value of values) { + if (value.record >= record) { + continue; + } + filter.push(value); + } + if (filter.length === 0 || filter[ 0 ] === undefined) { + return undefined; + } + if (filter.length === 1) { + return filter[ 0 ]; + } + + + // The parent record number closest to us we treat as our parent + // Get the largest record in our filtered list + let key = filter[ 0 ].record; + for (const entry of filter) { + if (entry.record > key) { + key = entry.record; + } + } + + for (const entry of filter) { + if (key !== entry.record) { + continue; + } + return entry; + } + return undefined; } \ No newline at end of file diff --git a/src/windows/eventlogs/rdp.ts b/src/windows/eventlogs/rdp.ts index 67dae6b8..e9d81621 100644 --- a/src/windows/eventlogs/rdp.ts +++ b/src/windows/eventlogs/rdp.ts @@ -1,369 +1,373 @@ -import { Raw21Logons, Raw23Logoff, Raw25Reconnect, Raw24Disconnect, Raw41SessionStart, Raw42SessionEnd, RdpActivity } from "../../../types/windows/eventlogs/rdp"; -import { getEnvValue } from "../../environment/mod"; -import { WindowsError } from "../errors"; -import { getEventlogs } from "../eventlogs"; - -/** - * Function to parse RDP logons - * @param alt_file Optional alternative path to `Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx` - * @returns Array of `RdpActivity` or `WindowsError` - */ -export function rdpLogons(alt_file?: string): RdpActivity[] | WindowsError { - let path = alt_file; - // If no evtx file provided. Then try to find it on local system - if (path === undefined) { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx`; - } - - const logs = rdpLogs(path); - if (logs instanceof WindowsError) { - return new WindowsError(`RDPLOGONS`, `failed to parse RDP eventlogs: ${logs}`); - } - - return extractRdp(logs); -} - -interface RdpEvents { - logons: Raw21Logons[]; - reconnects: Raw25Reconnect[]; - disconnect: Raw24Disconnect[]; - logoffs: Raw23Logoff[]; - session_start: Raw41SessionStart[]; - session_end: Raw42SessionEnd[]; -} - -/** - * Parse the EventLog and extract specific RDP log entries - * @param path Path to the `Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx` file - * @returns Parsed `RdpEvents` or `WindowsError` - */ -function rdpLogs(path: string): RdpEvents | WindowsError { - const rdp_events: RdpEvents = { - logons: [], - reconnects: [], - disconnect: [], - logoffs: [], - session_start: [], - session_end: [], - }; - - let offset = 0; - const limit = 500; - while (true) { - // We do not need the EventLog provider strings. The raw RDP logs will be sufficient - const events = getEventlogs(path, offset, limit); - if (events instanceof WindowsError) { - return events; - } - offset += limit; - - // `getEventlogs` returns a tuple. The second value contains our raw eventlog data - // First value is empty because we did not enable provider strings - const records = events[1]; - if (records.length === 0) { - break; - } - - // Get the specific RDP event IDs we want - for (const entry of records) { - // Only keep RDP logs we can use to create sessions - if (isLogon(entry as Raw21Logons)) { - rdp_events.logons.push(entry as Raw21Logons); - } else if (isReconnect(entry as Raw25Reconnect)) { - rdp_events.reconnects.push(entry as Raw25Reconnect); - } else if (isLogoff(entry as Raw23Logoff)) { - rdp_events.logoffs.push(entry as Raw23Logoff); - } else if (isSessionStart(entry as Raw41SessionStart)) { - rdp_events.session_start.push(entry as Raw41SessionStart); - } else if (isSessionEnd(entry as Raw42SessionEnd)) { - rdp_events.session_end.push(entry as Raw42SessionEnd); - } else if (isDisconnect(entry as Raw24Disconnect)) { - rdp_events.disconnect.push(entry as Raw24Disconnect); - } - } - } - - return rdp_events; -} - -/** - * Check if this is a RDP logon event - * @param record `Raw21Logons` eventlog object - * @returns true if the eventlog record is 21 Logon event - */ -function isLogon(record: Raw21Logons): record is Raw21Logons { - if (record.data.Event.System.EventID === 21) { - return true; - } - - return false; -} - -/** - * Check if this is a RDP reconnect event - * @param record `Raw25Reconnect` eventlog object - * @returns true if the eventlog record is 25 reconnect event - */ -function isReconnect(record: Raw25Reconnect): record is Raw25Reconnect { - if (record.data.Event.System.EventID === 25) { - return true; - } - - return false; -} - -/** - * Check if this is a RDP disconnect event - * @param record `Raw24Disconnect` eventlog object - * @returns true if the eventlog record is 24 disconnect event - */ -function isDisconnect(record: Raw24Disconnect): record is Raw24Disconnect { - if (record.data.Event.System.EventID === 24) { - return true; - } - - return false; -} - -/** - * Check if this is a RDP session start event - * @param record `Raw41SessionStart` eventlog object - * @returns true if the eventlog record is 41 session start event - */ -function isSessionStart(record: Raw41SessionStart): record is Raw41SessionStart { - if (record.data.Event.System.EventID === 41) { - return true; - } - - return false; -} - -/** - * Check if this is a RDP session end event - * @param record `Raw42SessionEnd` eventlog object - * @returns true if the eventlog record is 42 session end event - */ -function isSessionEnd(record: Raw42SessionEnd): record is Raw42SessionEnd { - if (record.data.Event.System.EventID === 42) { - return true; - } - - return false; -} - -/** - * Check if this is a RDP logoff event - * @param record `Raw23Logoff` eventlog object - * @returns true if the eventlog record is 23 logoff event - */ -function isLogoff(record: Raw23Logoff): record is Raw23Logoff { - if (record.data.Event.System.EventID === 23) { - return true; - } - - return false; -} - -/** - * Extract RDP events into Timesketch compatible format - * @param events Object of `RdpEvents` - * @returns Array of `RdpActivity` - */ -function extractRdp(events: RdpEvents): RdpActivity[] { - const values: RdpActivity[] = []; - for (const entry of events.logons) { - const data = entry.data.Event.UserData.EventXML; - const value: RdpActivity = { - session_id: data.SessionID, - user: data.User, - domain: data.User.split("\\").at(0) ?? "Unknown", - account: data.User.split("\\").at(1) ?? "Unknown", - source_ip: data.Address, - hostname: entry.data.Event.System.Computer, - activity_id: entry.data.Event.System.Correlation?.["#attributes"].ActivityID ?? "None", - message: `RDP Logon by ${data.User} from ${data.Address}`, - datetime: entry.data.Event.System.TimeCreated["#attributes"].SystemTime, - timestamp_desc: "RDP Logon", - artifact: "RDP EventLog", - data_type: "windows:eventlogs:rdp:entry" - }; - values.push(value); - } - - for (const entry of events.logoffs) { - const data = entry.data.Event.UserData.EventXML; - const value: RdpActivity = { - session_id: data.SessionID, - user: data.User, - domain: data.User.split("\\").at(0) ?? "Unknown", - account: data.User.split("\\").at(1) ?? "Unknown", - source_ip: "None", - hostname: entry.data.Event.System.Computer, - activity_id: entry.data.Event.System.Correlation?.["#attributes"].ActivityID ?? "None", - message: `RDP Logoff by ${data.User}`, - datetime: entry.data.Event.System.TimeCreated["#attributes"].SystemTime, - timestamp_desc: "RDP Logoff", - artifact: "RDP EventLog", - data_type: "windows:eventlogs:rdp:entry" - }; - values.push(value); - } - - for (const entry of events.disconnect) { - const data = entry.data.Event.UserData.EventXML; - const value: RdpActivity = { - session_id: data.SessionID, - user: data.User, - domain: data.User.split("\\").at(0) ?? "Unknown", - account: data.User.split("\\").at(1) ?? "Unknown", - source_ip: data.Address, - hostname: entry.data.Event.System.Computer, - activity_id: entry.data.Event.System.Correlation?.["#attributes"].ActivityID ?? "None", - message: `RDP Disconnect by ${data.User} from ${data.Address}`, - datetime: entry.data.Event.System.TimeCreated["#attributes"].SystemTime, - timestamp_desc: "RDP Disconnect", - artifact: "RDP EventLog", - data_type: "windows:eventlogs:rdp:entry" - }; - values.push(value); - } - - for (const entry of events.reconnects) { - const data = entry.data.Event.UserData.EventXML; - const value: RdpActivity = { - session_id: data.SessionID, - user: data.User, - domain: data.User.split("\\").at(0) ?? "Unknown", - account: data.User.split("\\").at(1) ?? "Unknown", - source_ip: data.Address, - hostname: entry.data.Event.System.Computer, - activity_id: entry.data.Event.System.Correlation?.["#attributes"].ActivityID ?? "None", - message: `RDP Reconnect by ${data.User} from ${data.Address}`, - datetime: entry.data.Event.System.TimeCreated["#attributes"].SystemTime, - timestamp_desc: "RDP Reconnect", - artifact: "RDP EventLog", - data_type: "windows:eventlogs:rdp:entry" - }; - values.push(value); - } - - return values; -} - - -/** - * Function to test Windows RDP Logons parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows RDP Logons parsing - */ -export function testRdpLogons(): void { - const test = "../../tests/test_data/windows/eventlogs/Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx"; - const results = rdpLogons(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length != 59) { - throw `Got ${results.length} RDP events, expected 59.......rdpLogons ❌`; - } - if (results[1] === undefined) { - throw `Got undefined RDP event.......rdpLogons ❌`; - } - - if (results[1].datetime != "2025-07-10T00:36:56.762711Z") { - throw `Got ${results[1].datetime} for logon time, expected "2025-07-10T00:36:56.762711Z".......rdpLogons ❌`; - } - - console.info(` Function rdpLogons ✅`); - - const logoff = `{"event_record_id":84336,"timestamp":"2025-08-31T03:07:39.322481000Z","data":{"Event":{"#attributes":{"xmlns":"http://schemas.microsoft.com/win/2004/08/events/event"},"System":{"Provider":{"#attributes":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"54849625-5478-4994-A5BA-3E3B0328C30D"}},"EventID":4634,"Version":0,"Level":0,"Task":12545,"Opcode":0,"Keywords":"0x8020000000000000","TimeCreated":{"#attributes":{"SystemTime":"2025-08-31T03:07:39.322481Z"}},"EventRecordID":84336,"Correlation":null,"Execution":{"#attributes":{"ProcessID":876,"ThreadID":2132}},"Channel":"Security","Computer":"win","Security":null},"EventData":{"TargetUserSid":"S-1-5-96-0-1","TargetUserName":"UMFD-1","TargetDomainName":"Font Driver Host","TargetLogonId":"0x1381d","LogonType":2}}}}`; - if (isLogoff(JSON.parse(logoff))) { - throw `Got good logoff event with bad data.......isLogoff ❌`; - } - - console.info(` Function isLogoff ✅`); - - - if (isLogon(JSON.parse(logoff))) { - throw `Got good logon event with bad data.......isLogon ❌`; - } - - console.info(` Function isLogon ✅`); - - - if (isDisconnect(JSON.parse(logoff))) { - throw `Got good disconnect event with bad data.......isDisconnect ❌`; - } - - console.info(` Function isDisconnect ✅`); - - - if (isReconnect(JSON.parse(logoff))) { - throw `Got good reconnect event with bad data.......isReconnect ❌`; - } - - console.info(` Function isReconnect ✅`); - - const mock = extractRdp({ - logons: [{ - event_record_id: 0, - timestamp: "", - data: { - Event: { - "#attributes": { - xmlns: "" - }, - System: { - Provider: { - "#attributes": { - Name: "", - Guid: "" - } - }, - EventID: 21, - Version: 0, - Level: 0, - Task: 0, - Opcode: 0, - Keywords: "", - TimeCreated: { - "#attributes": { - SystemTime: "" - } - }, - EventRecordID: 0, - Correlation: null, - Channel: "", - Computer: "", - Security: undefined - }, - UserData: { - EventXML: { - User: "", - SessionID: 0, - Address: "", - "#attributes": { - xmlns: "" - } - } - } - } - } - }], - reconnects: [], - disconnect: [], - logoffs: [], - session_start: [], - session_end: [] - }); - - if (mock.length !== 1) { - throw `Got ${results.length} RDP events, expected 1.......extractRdp ❌`; - } - - console.info(` Function extractRdp ✅`); +import { Raw21Logons, Raw23Logoff, Raw25Reconnect, Raw24Disconnect, Raw41SessionStart, Raw42SessionEnd, RdpActivity } from "../../../types/windows/eventlogs/rdp"; +import { getEnvValue } from "../../environment/mod"; +import { WindowsError } from "../errors"; +import { getEventlogs } from "../eventlogs"; + +/** + * Function to parse RDP logons + * @param alt_file Optional alternative path to `Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx` + * @returns Array of `RdpActivity` or `WindowsError` + */ +export function rdpLogons(alt_file?: string): RdpActivity[] | WindowsError { + let path = alt_file; + // If no evtx file provided. Then try to find it on local system + if (path === undefined) { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx`; + } + + const logs = rdpLogs(path); + if (logs instanceof WindowsError) { + return new WindowsError(`RDPLOGONS`, `failed to parse RDP eventlogs: ${logs}`); + } + + return extractRdp(logs, path); +} + +interface RdpEvents { + logons: Raw21Logons[]; + reconnects: Raw25Reconnect[]; + disconnect: Raw24Disconnect[]; + logoffs: Raw23Logoff[]; + session_start: Raw41SessionStart[]; + session_end: Raw42SessionEnd[]; +} + +/** + * Parse the EventLog and extract specific RDP log entries + * @param path Path to the `Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx` file + * @returns Parsed `RdpEvents` or `WindowsError` + */ +function rdpLogs(path: string): RdpEvents | WindowsError { + const rdp_events: RdpEvents = { + logons: [], + reconnects: [], + disconnect: [], + logoffs: [], + session_start: [], + session_end: [], + }; + + let offset = 0; + const limit = 500; + while (true) { + // We do not need the EventLog provider strings. The raw RDP logs will be sufficient + const events = getEventlogs(path, offset, limit); + if (events instanceof WindowsError) { + return events; + } + offset += limit; + + // `getEventlogs` returns a tuple. The second value contains our raw eventlog data + // First value is empty because we did not enable provider strings + const records = events[ 1 ]; + if (records.length === 0) { + break; + } + + // Get the specific RDP event IDs we want + for (const entry of records) { + // Only keep RDP logs we can use to create sessions + if (isLogon(entry as unknown as Raw21Logons)) { + rdp_events.logons.push(entry as unknown as Raw21Logons); + } else if (isReconnect(entry as unknown as Raw25Reconnect)) { + rdp_events.reconnects.push(entry as unknown as Raw25Reconnect); + } else if (isLogoff(entry as unknown as Raw23Logoff)) { + rdp_events.logoffs.push(entry as unknown as Raw23Logoff); + } else if (isSessionStart(entry as unknown as Raw41SessionStart)) { + rdp_events.session_start.push(entry as unknown as Raw41SessionStart); + } else if (isSessionEnd(entry as unknown as Raw42SessionEnd)) { + rdp_events.session_end.push(entry as unknown as Raw42SessionEnd); + } else if (isDisconnect(entry as unknown as Raw24Disconnect)) { + rdp_events.disconnect.push(entry as unknown as Raw24Disconnect); + } + } + } + + return rdp_events; +} + +/** + * Check if this is a RDP logon event + * @param record `Raw21Logons` eventlog object + * @returns true if the eventlog record is 21 Logon event + */ +function isLogon(record: Raw21Logons): record is Raw21Logons { + if (record.data.Event.System.EventID === 21) { + return true; + } + + return false; +} + +/** + * Check if this is a RDP reconnect event + * @param record `Raw25Reconnect` eventlog object + * @returns true if the eventlog record is 25 reconnect event + */ +function isReconnect(record: Raw25Reconnect): record is Raw25Reconnect { + if (record.data.Event.System.EventID === 25) { + return true; + } + + return false; +} + +/** + * Check if this is a RDP disconnect event + * @param record `Raw24Disconnect` eventlog object + * @returns true if the eventlog record is 24 disconnect event + */ +function isDisconnect(record: Raw24Disconnect): record is Raw24Disconnect { + if (record.data.Event.System.EventID === 24) { + return true; + } + + return false; +} + +/** + * Check if this is a RDP session start event + * @param record `Raw41SessionStart` eventlog object + * @returns true if the eventlog record is 41 session start event + */ +function isSessionStart(record: Raw41SessionStart): record is Raw41SessionStart { + if (record.data.Event.System.EventID === 41) { + return true; + } + + return false; +} + +/** + * Check if this is a RDP session end event + * @param record `Raw42SessionEnd` eventlog object + * @returns true if the eventlog record is 42 session end event + */ +function isSessionEnd(record: Raw42SessionEnd): record is Raw42SessionEnd { + if (record.data.Event.System.EventID === 42) { + return true; + } + + return false; +} + +/** + * Check if this is a RDP logoff event + * @param record `Raw23Logoff` eventlog object + * @returns true if the eventlog record is 23 logoff event + */ +function isLogoff(record: Raw23Logoff): record is Raw23Logoff { + if (record.data.Event.System.EventID === 23) { + return true; + } + + return false; +} + +/** + * Extract RDP events into Timesketch compatible format + * @param events Object of `RdpEvents` + * @returns Array of `RdpActivity` + */ +function extractRdp(events: RdpEvents, evidence: string): RdpActivity[] { + const values: RdpActivity[] = []; + for (const entry of events.logons) { + const data = entry.data.Event.UserData.EventXML; + const value: RdpActivity = { + session_id: data.SessionID, + user: data.User, + domain: data.User.split("\\").at(0) ?? "Unknown", + account: data.User.split("\\").at(1) ?? "Unknown", + source_ip: data.Address, + hostname: entry.data.Event.System.Computer, + activity_id: entry.data.Event.System.Correlation?.[ "#attributes" ].ActivityID ?? "None", + message: `RDP Logon by ${data.User} from ${data.Address}`, + datetime: entry.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, + timestamp_desc: "RDP Logon", + artifact: "RDP EventLog", + data_type: "windows:eventlogs:rdp:entry", + evidence, + }; + values.push(value); + } + + for (const entry of events.logoffs) { + const data = entry.data.Event.UserData.EventXML; + const value: RdpActivity = { + session_id: data.SessionID, + user: data.User, + domain: data.User.split("\\").at(0) ?? "Unknown", + account: data.User.split("\\").at(1) ?? "Unknown", + source_ip: "None", + hostname: entry.data.Event.System.Computer, + activity_id: entry.data.Event.System.Correlation?.[ "#attributes" ].ActivityID ?? "None", + message: `RDP Logoff by ${data.User}`, + datetime: entry.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, + timestamp_desc: "RDP Logoff", + artifact: "RDP EventLog", + data_type: "windows:eventlogs:rdp:entry", + evidence, + }; + values.push(value); + } + + for (const entry of events.disconnect) { + const data = entry.data.Event.UserData.EventXML; + const value: RdpActivity = { + session_id: data.SessionID, + user: data.User, + domain: data.User.split("\\").at(0) ?? "Unknown", + account: data.User.split("\\").at(1) ?? "Unknown", + source_ip: data.Address, + hostname: entry.data.Event.System.Computer, + activity_id: entry.data.Event.System.Correlation?.[ "#attributes" ].ActivityID ?? "None", + message: `RDP Disconnect by ${data.User} from ${data.Address}`, + datetime: entry.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, + timestamp_desc: "RDP Disconnect", + artifact: "RDP EventLog", + data_type: "windows:eventlogs:rdp:entry", + evidence, + }; + values.push(value); + } + + for (const entry of events.reconnects) { + const data = entry.data.Event.UserData.EventXML; + const value: RdpActivity = { + session_id: data.SessionID, + user: data.User, + domain: data.User.split("\\").at(0) ?? "Unknown", + account: data.User.split("\\").at(1) ?? "Unknown", + source_ip: data.Address, + hostname: entry.data.Event.System.Computer, + activity_id: entry.data.Event.System.Correlation?.[ "#attributes" ].ActivityID ?? "None", + message: `RDP Reconnect by ${data.User} from ${data.Address}`, + datetime: entry.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, + timestamp_desc: "RDP Reconnect", + artifact: "RDP EventLog", + data_type: "windows:eventlogs:rdp:entry", + evidence, + }; + values.push(value); + } + + return values; +} + + +/** + * Function to test Windows RDP Logons parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows RDP Logons parsing + */ +export function testRdpLogons(): void { + const test = "../../tests/test_data/windows/eventlogs/Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx"; + const results = rdpLogons(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length != 59) { + throw `Got ${results.length} RDP events, expected 59.......rdpLogons ❌`; + } + if (results[ 1 ] === undefined) { + throw `Got undefined RDP event.......rdpLogons ❌`; + } + + if (results[ 1 ].datetime != "2025-07-10T00:36:56.762711Z") { + throw `Got ${results[ 1 ].datetime} for logon time, expected "2025-07-10T00:36:56.762711Z".......rdpLogons ❌`; + } + + console.info(` Function rdpLogons ✅`); + + const logoff = `{"event_record_id":84336,"timestamp":"2025-08-31T03:07:39.322481000Z","data":{"Event":{"#attributes":{"xmlns":"http://schemas.microsoft.com/win/2004/08/events/event"},"System":{"Provider":{"#attributes":{"Name":"Microsoft-Windows-Security-Auditing","Guid":"54849625-5478-4994-A5BA-3E3B0328C30D"}},"EventID":4634,"Version":0,"Level":0,"Task":12545,"Opcode":0,"Keywords":"0x8020000000000000","TimeCreated":{"#attributes":{"SystemTime":"2025-08-31T03:07:39.322481Z"}},"EventRecordID":84336,"Correlation":null,"Execution":{"#attributes":{"ProcessID":876,"ThreadID":2132}},"Channel":"Security","Computer":"win","Security":null},"EventData":{"TargetUserSid":"S-1-5-96-0-1","TargetUserName":"UMFD-1","TargetDomainName":"Font Driver Host","TargetLogonId":"0x1381d","LogonType":2}}}}`; + if (isLogoff(JSON.parse(logoff))) { + throw `Got good logoff event with bad data.......isLogoff ❌`; + } + + console.info(` Function isLogoff ✅`); + + + if (isLogon(JSON.parse(logoff))) { + throw `Got good logon event with bad data.......isLogon ❌`; + } + + console.info(` Function isLogon ✅`); + + + if (isDisconnect(JSON.parse(logoff))) { + throw `Got good disconnect event with bad data.......isDisconnect ❌`; + } + + console.info(` Function isDisconnect ✅`); + + + if (isReconnect(JSON.parse(logoff))) { + throw `Got good reconnect event with bad data.......isReconnect ❌`; + } + + console.info(` Function isReconnect ✅`); + + const mock = extractRdp({ + logons: [ { + event_record_id: 0, + timestamp: "", + data: { + Event: { + "#attributes": { + xmlns: "" + }, + System: { + Provider: { + "#attributes": { + Name: "", + Guid: "" + } + }, + EventID: 21, + Version: 0, + Level: 0, + Task: 0, + Opcode: 0, + Keywords: "", + TimeCreated: { + "#attributes": { + SystemTime: "" + } + }, + EventRecordID: 0, + Correlation: null, + Channel: "", + Computer: "", + Security: undefined + }, + UserData: { + EventXML: { + User: "", + SessionID: 0, + Address: "", + "#attributes": { + xmlns: "" + } + } + } + } + } + } ], + reconnects: [], + disconnect: [], + logoffs: [], + session_start: [], + session_end: [] + }, "test"); + + if (mock.length !== 1) { + throw `Got ${results.length} RDP events, expected 1.......extractRdp ❌`; + } + + console.info(` Function extractRdp ✅`); } \ No newline at end of file diff --git a/src/windows/eventlogs/scriptblocks.ts b/src/windows/eventlogs/scriptblocks.ts index 882ec7d6..c80d0775 100644 --- a/src/windows/eventlogs/scriptblocks.ts +++ b/src/windows/eventlogs/scriptblocks.ts @@ -1,158 +1,158 @@ -import { RawBlock, Scriptblock } from "../../../types/windows/eventlogs/scriptblocks"; -import { getEnvValue } from "../../environment/mod"; -import { WindowsError } from "../errors"; -import { getEventlogs } from "../eventlogs"; - -/** - * Function to assemble PowerShell scriptblocks - * @param path Optional path to PowerShell Operational evtx file - * @returns Array of `Scriptblock` or `WindowsError` - */ -export function assembleScriptblocks(path?: string): Scriptblock[] | WindowsError { - let log_path = path; - if (log_path === undefined) { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - log_path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-PowerShell%4Operational.evtx`; - } - - let offset = 0; - const limit = 500; - const eid = 4104; - - let entries: Scriptblock[] = []; - // Track scriptblocks - const blocks: Record = {}; - while (true) { - const logs = getEventlogs(log_path, offset, limit); - if (logs instanceof WindowsError) { - return new WindowsError( - "SCRIPTBLOCK", - `failed to parse eventlog ${path}: ${logs}`, - ); - } - - // If empty we are done. No more entries are in the log - if (logs[ 1 ].length === 0) { - break; - } - - // Increase to next chunk of entries - offset += limit; - const data = logs[ 1 ] as RawBlock[]; - for (const entry of data) { - if (entry.data.Event.System.EventID !== eid) { - continue; - } - - if (entry.data.Event.EventData.MessageTotal === 1) { - const record: Scriptblock = { - total_parts: entry.data.Event.EventData.MessageTotal, - message: entry.data.Event.EventData.ScriptBlockText, - datetime: entry.timestamp, - timestamp_desc: "EventLog Entry Created", - data_type: "windows:eventlogs:powershell:scriptblock:entry", - id: entry.data.Event.EventData.ScriptBlockId, - source_file: log_path, - path: entry.data.Event.EventData.Path, - script_length: entry.data.Event.EventData.ScriptBlockText.length, - has_signature_block: entry.data.Event.EventData.ScriptBlockText.includes("# SIG # End signature block"), - has_copyright_string: entry.data.Event.EventData.ScriptBlockText.includes("# Copyright "), - hostname: entry.data.Event.System.Computer, - version: entry.data.Event.System.Version, - activity_id: entry.data.Event.System.Correlation[ "#attributes" ].ActivityID, - channel: entry.data.Event.System.Channel, - user_id: entry.data.Event.System.Security[ "#attributes" ].UserID, - process_id: entry.data.Event.System.Execution[ "#attributes" ].ProcessID, - threat_id: entry.data.Event.System.Execution[ "#attributes" ].ThreadID, - system_time: entry.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, - created_time: entry.timestamp, - artifact: "Windows PowerShell Scriptblock" - }; - entries.push(record); - continue; - } - - if (blocks[ entry.data.Event.EventData.ScriptBlockId ] !== undefined) { - blocks[ entry.data.Event.EventData.ScriptBlockId ].push(entry); - continue; - } - blocks[ entry.data.Event.EventData.ScriptBlockId ] = [ entry ]; - } - } - - entries = entries.concat(constructBlocks(blocks, log_path)); - - return entries; -} - -/** - * Function to combine multiple Scriptblock entries - * @param data Array of `Record` that contains our blocks we want to reassemble - * @param source_file Path the evtx file - * @returns Array of `Scriptblock` - */ -function constructBlocks(data: Record, source_file: string): Scriptblock[] { - const results: Scriptblock[] = []; - - for (const entry in data) { - if(data[entry] === undefined) { - continue; - } - // Sort our blocks by block number. We do this to make sure we reassemble in correct order - data[ entry ].sort((x, y) => x.data.Event.EventData.MessageNumber - y.data.Event.EventData.MessageNumber); - const value: Scriptblock = { - total_parts: 0, - message: "", - datetime: "", - timestamp_desc: "EventLog Entry Created", - id: "", - source_file, - path: "", - script_length: 0, - has_signature_block: false, - has_copyright_string: false, - hostname: "", - version: 0, - activity_id: "", - channel: "", - user_id: "", - process_id: 0, - threat_id: 0, - system_time: "", - created_time: "", - artifact: "Windows PowerShell Scriptblock", - data_type: "windows:eventlogs:powershell:scriptblock:entry" - }; - let message = ""; - for (const script of data[ entry ]) { - // Only need this info once - if (message.length === 0) { - value.total_parts = script.data.Event.EventData.MessageTotal; - value.datetime = script.timestamp; - value.id = script.data.Event.EventData.ScriptBlockId; - value.path = script.data.Event.EventData.Path; - value.hostname = script.data.Event.System.Computer; - value.version = script.data.Event.System.Version; - value.activity_id = script.data.Event.System.Correlation[ "#attributes" ].ActivityID; - value.channel = script.data.Event.System.Channel; - value.user_id = script.data.Event.System.Security[ "#attributes" ].UserID; - value.process_id = script.data.Event.System.Execution[ "#attributes" ].ProcessID; - value.threat_id = script.data.Event.System.Execution[ "#attributes" ].ThreadID; - value.system_time = script.data.Event.System.TimeCreated[ "#attributes" ].SystemTime; - value.created_time = script.timestamp; - } - message = message.concat(script.data.Event.EventData.ScriptBlockText); - } - - value.message = message; - value.script_length = message.length; - value.has_signature_block = message.includes("# SIG # End signature block"); - value.has_copyright_string = message.includes("# Copyright "); - results.push(value); - } - - return results; +import { RawBlock, Scriptblock } from "../../../types/windows/eventlogs/scriptblocks"; +import { getEnvValue } from "../../environment/mod"; +import { WindowsError } from "../errors"; +import { getEventlogs } from "../eventlogs"; + +/** + * Function to assemble PowerShell scriptblocks + * @param path Optional path to PowerShell Operational evtx file + * @returns Array of `Scriptblock` or `WindowsError` + */ +export function assembleScriptblocks(path?: string): Scriptblock[] | WindowsError { + let log_path = path; + if (log_path === undefined) { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + log_path = `${drive}\\Windows\\System32\\winevt\\Logs\\Microsoft-Windows-PowerShell%4Operational.evtx`; + } + + let offset = 0; + const limit = 500; + const eid = 4104; + + let entries: Scriptblock[] = []; + // Track scriptblocks + const blocks: Record = {}; + while (true) { + const logs = getEventlogs(log_path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "SCRIPTBLOCK", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + + // If empty we are done. No more entries are in the log + if (logs[ 1 ].length === 0) { + break; + } + + // Increase to next chunk of entries + offset += limit; + const data = logs[ 1 ] as unknown as RawBlock[]; + for (const entry of data) { + if (entry.data.Event.System.EventID !== eid) { + continue; + } + + if (entry.data.Event.EventData.MessageTotal === 1) { + const record: Scriptblock = { + total_parts: entry.data.Event.EventData.MessageTotal, + message: entry.data.Event.EventData.ScriptBlockText, + datetime: entry.timestamp, + timestamp_desc: "EventLog Entry Created", + data_type: "windows:eventlogs:powershell:scriptblock:entry", + id: entry.data.Event.EventData.ScriptBlockId, + path: entry.data.Event.EventData.Path, + script_length: entry.data.Event.EventData.ScriptBlockText.length, + has_signature_block: entry.data.Event.EventData.ScriptBlockText.includes("# SIG # End signature block"), + has_copyright_string: entry.data.Event.EventData.ScriptBlockText.includes("# Copyright "), + hostname: entry.data.Event.System.Computer, + version: entry.data.Event.System.Version, + activity_id: entry.data.Event.System.Correlation[ "#attributes" ].ActivityID, + channel: entry.data.Event.System.Channel, + user_id: entry.data.Event.System.Security[ "#attributes" ].UserID, + process_id: entry.data.Event.System.Execution[ "#attributes" ].ProcessID, + threat_id: entry.data.Event.System.Execution[ "#attributes" ].ThreadID, + system_time: entry.data.Event.System.TimeCreated[ "#attributes" ].SystemTime, + created_time: entry.timestamp, + artifact: "Windows PowerShell Scriptblock", + evidence: log_path, + }; + entries.push(record); + continue; + } + + if (blocks[ entry.data.Event.EventData.ScriptBlockId ] !== undefined) { + blocks[ entry.data.Event.EventData.ScriptBlockId ].push(entry); + continue; + } + blocks[ entry.data.Event.EventData.ScriptBlockId ] = [ entry ]; + } + } + + entries = entries.concat(constructBlocks(blocks, log_path)); + + return entries; +} + +/** + * Function to combine multiple Scriptblock entries + * @param data Array of `Record` that contains our blocks we want to reassemble + * @param evidence Path the evtx file + * @returns Array of `Scriptblock` + */ +function constructBlocks(data: Record, evidence: string): Scriptblock[] { + const results: Scriptblock[] = []; + + for (const entry in data) { + if (data[ entry ] === undefined) { + continue; + } + // Sort our blocks by block number. We do this to make sure we reassemble in correct order + data[ entry ].sort((x, y) => x.data.Event.EventData.MessageNumber - y.data.Event.EventData.MessageNumber); + const value: Scriptblock = { + total_parts: 0, + message: "", + datetime: "", + timestamp_desc: "EventLog Entry Created", + id: "", + evidence, + path: "", + script_length: 0, + has_signature_block: false, + has_copyright_string: false, + hostname: "", + version: 0, + activity_id: "", + channel: "", + user_id: "", + process_id: 0, + threat_id: 0, + system_time: "", + created_time: "", + artifact: "Windows PowerShell Scriptblock", + data_type: "windows:eventlogs:powershell:scriptblock:entry" + }; + let message = ""; + for (const script of data[ entry ]) { + // Only need this info once + if (message.length === 0) { + value.total_parts = script.data.Event.EventData.MessageTotal; + value.datetime = script.timestamp; + value.id = script.data.Event.EventData.ScriptBlockId; + value.path = script.data.Event.EventData.Path; + value.hostname = script.data.Event.System.Computer; + value.version = script.data.Event.System.Version; + value.activity_id = script.data.Event.System.Correlation[ "#attributes" ].ActivityID; + value.channel = script.data.Event.System.Channel; + value.user_id = script.data.Event.System.Security[ "#attributes" ].UserID; + value.process_id = script.data.Event.System.Execution[ "#attributes" ].ProcessID; + value.threat_id = script.data.Event.System.Execution[ "#attributes" ].ThreadID; + value.system_time = script.data.Event.System.TimeCreated[ "#attributes" ].SystemTime; + value.created_time = script.timestamp; + } + message = message.concat(script.data.Event.EventData.ScriptBlockText); + } + + value.message = message; + value.script_length = message.length; + value.has_signature_block = message.includes("# SIG # End signature block"); + value.has_copyright_string = message.includes("# Copyright "); + results.push(value); + } + + return results; } \ No newline at end of file diff --git a/src/windows/eventlogs/services.ts b/src/windows/eventlogs/services.ts index 5ba1e26f..867706a6 100644 --- a/src/windows/eventlogs/services.ts +++ b/src/windows/eventlogs/services.ts @@ -1,182 +1,183 @@ -import { - RawService7045, - ServiceInstalls, -} from "../../../types/windows/eventlogs/services"; -import { WindowsError } from "../errors"; -import { getEventlogs } from "../eventlogs"; - -/** - * Function to extract Service install events from EventLog - * @param alt_path Optional alternative path to System.evtx. Default is path `C:\Windows\System32\winevt\Logs\System.evtx` - * @returns Array of `ServiceInstalls` or `WindowsError` - */ -export function serviceInstalls( - alt_path?: string, -): ServiceInstalls[] | WindowsError { - let path = "C:\\Windows\\System32\\winevt\\Logs\\System.evtx"; - if (alt_path !== undefined) { - path = alt_path - } - let offset = 0; - const limit = 10000; - const events = []; - - while (true) { - // Get records 10000 at a time - const logs = getEventlogs(path, offset, limit); - if (logs instanceof WindowsError) { - return new WindowsError( - "SERVICEINSTALL", - `failed to parse eventlog ${path}: ${logs}`, - ); - } - const recordsData = logs[1]; - if (recordsData.length === 0) { - break; - } - - offset += limit; - - const records = recordsData as RawService7045[]; - for (const entry of records) { - if (!isInstall(entry)) { - continue; - } - - const service: ServiceInstalls = { - name: entry.data.Event.EventData["ServiceName"], - image_path: entry.data.Event.EventData["ImagePath"], - service_type: entry.data.Event.EventData["ServiceType"], - account: entry.data.Event.EventData["AccountName"], - start_type: entry.data.Event.EventData["StartType"], - hostname: entry.data.Event.System.Computer, - timestamp: entry.timestamp, - process_id: entry.data.Event.System.Execution["#attributes"].ProcessID, - thread_id: entry.data.Event.System.Execution["#attributes"].ThreadID, - sid: entry.data.Event.System.Security["#attributes"].UserID, - message: `Service "${entry.data.Event.EventData["ServiceName"]}" installed`, - datetime: entry.timestamp, - timestamp_desc: "Windows Service Installed", - artifact: "EventLog Service 7045", - data_type: "windows:eventlog:system:service" - }; - events.push(service); - } - } - - return events; -} - -/** - * Function to confirm if eventlog record is a Service install event - * @param record `RawService7045` object - * @returns Boolean confirmation if the eventlog entry is a Service install event - */ -function isInstall(record: RawService7045): record is RawService7045 { - if ( - typeof record.data.Event.System.EventID === "number" && - record.data.Event.System.EventID === 7045 && - record.data.Event.System.Provider["#attributes"].Name === - "Service Control Manager" - ) { - return true; - } else if ( - typeof record.data.Event.System.EventID === "object" && - record.data.Event.System.EventID["#text"] === 7045 && - record.data.Event.System.Provider["#attributes"].Name === - "Service Control Manager" - ) { - return true; - } - - return false; -} - -/** - * Function to test Windows Service install parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows Service install parsing - */ -export function testServiceInstalls(): void { - const test = "../../tests/test_data/windows/eventlogs/System.evtx"; - const results = serviceInstalls(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length !== 16) { - throw `Got ${results.length} service install events, expected 16.......serviceInstalls ❌`; - } - if (results[1] === undefined) { - throw `Got undefined service install.......serviceInstalls ❌`; - } - if (results[1].message != 'Service "Intel(R) PRO/1000 NDIS 6 Adapter Driver" installed') { - throw `Got ${results[1].message}, expected 'Service "Intel(R) PRO/1000 NDIS 6 Adapter Driver" installed'.......serviceInstalls ❌`; - } - - console.info(` Function serviceInstalls ✅`); - - const dumb: RawService7045 = { - event_record_id: 0, - timestamp: "", - data: { - Event: { - "#attributes": { - xmlns: "" - }, - System: { - Provider: { - "#attributes": { - Name: "", - Guid: "", - EventSourceName: "" - } - }, - EventID: 11, - Version: 0, - Level: 0, - Task: 0, - Opcode: 0, - Keywords: "", - TimeCreated: { - "#attributes": { - SystemTime: "" - } - }, - EventRecordID: 0, - Correlation: { - "#attributes": { - ActivityID: "" - } - }, - Execution: { - "#attributes": { - ProcessID: 0, - ThreadID: 0 - } - }, - Channel: "", - Computer: "", - Security: { - "#attributes": { - UserID: "" - } - } - }, - EventData: { - ServiceName: "", - ImagePath: "", - ServiceType: "", - StartType: "", - AccountName: "" - } - } - } - }; - if (isInstall(dumb)) { - throw `Got install event with bad data.......isInstall ❌` - } - - console.info(` Function isInstall ✅`); - +import { + RawService7045, + ServiceInstalls, +} from "../../../types/windows/eventlogs/services"; +import { WindowsError } from "../errors"; +import { getEventlogs } from "../eventlogs"; + +/** + * Function to extract Service install events from EventLog + * @param alt_path Optional alternative path to System.evtx. Default is path `C:\Windows\System32\winevt\Logs\System.evtx` + * @returns Array of `ServiceInstalls` or `WindowsError` + */ +export function serviceInstalls( + alt_path?: string, +): ServiceInstalls[] | WindowsError { + let path = "C:\\Windows\\System32\\winevt\\Logs\\System.evtx"; + if (alt_path !== undefined) { + path = alt_path + } + let offset = 0; + const limit = 10000; + const events = []; + + while (true) { + // Get records 10000 at a time + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "SERVICEINSTALL", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + const recordsData = logs[1]; + if (recordsData.length === 0) { + break; + } + + offset += limit; + + const records = recordsData as unknown as RawService7045[]; + for (const entry of records) { + if (!isInstall(entry)) { + continue; + } + + const service: ServiceInstalls = { + name: entry.data.Event.EventData["ServiceName"], + image_path: entry.data.Event.EventData["ImagePath"], + service_type: entry.data.Event.EventData["ServiceType"], + account: entry.data.Event.EventData["AccountName"], + start_type: entry.data.Event.EventData["StartType"], + hostname: entry.data.Event.System.Computer, + timestamp: entry.timestamp, + process_id: entry.data.Event.System.Execution["#attributes"].ProcessID, + thread_id: entry.data.Event.System.Execution["#attributes"].ThreadID, + sid: entry.data.Event.System.Security["#attributes"].UserID, + message: `Service "${entry.data.Event.EventData["ServiceName"]}" installed`, + datetime: entry.timestamp, + timestamp_desc: "Windows Service Installed", + artifact: "EventLog Service 7045", + data_type: "windows:eventlog:system:service", + evidence: path, + }; + events.push(service); + } + } + + return events; +} + +/** + * Function to confirm if eventlog record is a Service install event + * @param record `RawService7045` object + * @returns Boolean confirmation if the eventlog entry is a Service install event + */ +function isInstall(record: RawService7045): record is RawService7045 { + if ( + typeof record.data.Event.System.EventID === "number" && + record.data.Event.System.EventID === 7045 && + record.data.Event.System.Provider["#attributes"].Name === + "Service Control Manager" + ) { + return true; + } else if ( + typeof record.data.Event.System.EventID === "object" && + record.data.Event.System.EventID["#text"] === 7045 && + record.data.Event.System.Provider["#attributes"].Name === + "Service Control Manager" + ) { + return true; + } + + return false; +} + +/** + * Function to test Windows Service install parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows Service install parsing + */ +export function testServiceInstalls(): void { + const test = "../../tests/test_data/windows/eventlogs/System.evtx"; + const results = serviceInstalls(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length !== 16) { + throw `Got ${results.length} service install events, expected 16.......serviceInstalls ❌`; + } + if (results[1] === undefined) { + throw `Got undefined service install.......serviceInstalls ❌`; + } + if (results[1].message != 'Service "Intel(R) PRO/1000 NDIS 6 Adapter Driver" installed') { + throw `Got ${results[1].message}, expected 'Service "Intel(R) PRO/1000 NDIS 6 Adapter Driver" installed'.......serviceInstalls ❌`; + } + + console.info(` Function serviceInstalls ✅`); + + const dumb: RawService7045 = { + event_record_id: 0, + timestamp: "", + data: { + Event: { + "#attributes": { + xmlns: "" + }, + System: { + Provider: { + "#attributes": { + Name: "", + Guid: "", + EventSourceName: "" + } + }, + EventID: 11, + Version: 0, + Level: 0, + Task: 0, + Opcode: 0, + Keywords: "", + TimeCreated: { + "#attributes": { + SystemTime: "" + } + }, + EventRecordID: 0, + Correlation: { + "#attributes": { + ActivityID: "" + } + }, + Execution: { + "#attributes": { + ProcessID: 0, + ThreadID: 0 + } + }, + Channel: "", + Computer: "", + Security: { + "#attributes": { + UserID: "" + } + } + }, + EventData: { + ServiceName: "", + ImagePath: "", + ServiceType: "", + StartType: "", + AccountName: "" + } + } + } + }; + if (isInstall(dumb)) { + throw `Got install event with bad data.......isInstall ❌` + } + + console.info(` Function isInstall ✅`); + } \ No newline at end of file diff --git a/src/windows/eventlogs/velociraptor.ts b/src/windows/eventlogs/velociraptor.ts new file mode 100644 index 00000000..78e88bc4 --- /dev/null +++ b/src/windows/eventlogs/velociraptor.ts @@ -0,0 +1,81 @@ +import { getEventlogs } from "../eventlogs"; +import { VeloExecution, VeloRaw } from "../../../types/windows/eventlogs/velociraptor"; +import { getSystemDrive } from "../../environment/env"; +import { WindowsError } from "../errors"; + +/** + * Function to extract executed Velociraptor commands from the EventLog + * @param alt_path Optional alternative path to Application.evtx file + * @param limit Optional limit to use when iterating through the EventLog + * @returns Array of `VeloExecution` or `WindowsError` + */ +export function veloCommands(alt_path?: string, limit = 10000): VeloExecution[] | WindowsError { + const drive = getSystemDrive(); + let path = `${drive}\\Windows\\System32\\winevt\\Logs\\Application.evtx`; + if (alt_path !== undefined) { + path = alt_path; + } + + let offset = 0; + const values: VeloExecution[] = []; + while (true) { + const logs = getEventlogs(path, offset, limit); + if (logs instanceof WindowsError) { + return new WindowsError( + "EVENTLOG_VELOCIRAPTOR", + `failed to parse eventlog ${path}: ${logs}`, + ); + } + + const recordsData = logs[1]; + if (recordsData.length === 0) { + break; + } + + const records = recordsData as unknown as VeloRaw[]; + for (const record of records) { + if (!isVelo(record)) { + continue; + } + const event_data = record.data.Event.EventData.Data["#text"]; + const command: string[] = JSON.parse(event_data.replace("Velociraptor startup ARGV: ", "")); + const entry: VeloExecution = { + evidence: path, + pid: record.data.Event.System.Execution["#attributes"].ProcessID, + message: `Velociraptor executed '${command.join(" ")}'`, + datetime: record.data.Event.System.TimeCreated["#attributes"].SystemTime, + provider: record.data.Event.System.Provider["#attributes"].Name, + event_id: record.data.Event.System.EventID["#text"], + thread_id: record.data.Event.System.Execution["#attributes"].ThreadID, + event: event_data, + path: command.at(0) ?? "Unknown path", + arguments: [], + timestamp_desc: "Velociraptor Executed", + artifact: "Velociraptor EventLog", + data_type: "windows:eventlogs:velociraptor:entry" + }; + + if (command.length > 1) { + entry.arguments = command.slice(1); + } + + values.push(entry); + } + offset += limit; + + } + + return values; +} + +/** + * Function to verify the EventLog entry is a Velociraptor event + * @param record `VeloRaw` object + * @returns Verification that the EventLog entry is a valid entry associated with Velociraptor + */ +function isVelo(record: VeloRaw): record is VeloRaw { + if (record.data.Event.System.EventID["#text"] === 1000 && JSON.stringify(record.data.Event.EventData).includes("Velociraptor startup ")) { + return true; + } + return false; +} \ No newline at end of file diff --git a/src/windows/jumplists.ts b/src/windows/jumplists.ts index e58568ae..45ae2d3d 100644 --- a/src/windows/jumplists.ts +++ b/src/windows/jumplists.ts @@ -1,17 +1,17 @@ -import { Jumplists } from "../../types/windows/jumplists"; -import { WindowsError } from "./errors"; - -/** - * Function to parse all Jumplists for all users using the `Systemdrive` (typically C) - * @param path Optional path to a Jumplist file - * @returns Array of `Jumplists` or `WindowsError` - */ -export function getJumplists(path?: string): Jumplists[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_jumplists(path); - return data; - } catch (err) { - return new WindowsError("JUMPLIST", `failed to parse jumplists: ${err}`); - } -} +import { Jumplists } from "../../types/windows/jumplists"; +import { WindowsError } from "./errors"; + +/** + * Function to parse all Jumplists for all users using the system drive (typically C) + * @param path Optional path to a Jumplist file + * @returns Array of `Jumplists` or `WindowsError` + */ +export function getJumplists(path?: string): Jumplists[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_jumplists(path); + return data; + } catch (err) { + return new WindowsError("JUMPLIST", `failed to parse jumplists: ${err}`); + } +} diff --git a/src/windows/pca.ts b/src/windows/pca.ts index 38eb2061..2094ba35 100644 --- a/src/windows/pca.ts +++ b/src/windows/pca.ts @@ -1,189 +1,189 @@ -import { PcaType, ProgramCompatibilityAssist } from "../../types/windows/pca"; -import { extractUtf16String } from "../encoding/strings"; -import { getEnvValue } from "../environment/mod"; -import { FileError } from "../filesystem/errors"; -import { glob, readFile, readLines } from "../filesystem/files"; -import { WindowsError } from "./errors"; - -/** - * Extract Program Compatibility Assist entries - * @param alt_dir Optional alternative glob to folder that contains PCA files - * @returns Array of `ProgramCompatibilityAssist` or `WindowsError` - */ -export function parsePca(alt_dir?: string): ProgramCompatibilityAssist[] | WindowsError { - let path = alt_dir; - if (path === undefined) { - const volume = getEnvValue("SystemDrive"); - if (volume === "") { - return new WindowsError(`PCA`, `no SystemDrive found`); - } - - path = `${volume}\\Windows\\appcompat\\pca\\*`; - } - - const globs = glob(path); - if (globs instanceof FileError) { - return new WindowsError(`PCA`, `failed to glob ${path}: ${globs}`); - } - - let values: ProgramCompatibilityAssist[] = []; - for (const entry of globs) { - if (entry.filename === "PcaAppLaunchDic.txt") { - const value = parseDict(entry.full_path); - if (value instanceof WindowsError) { - continue; - } - values = values.concat(value); - } else if (entry.filename.includes("General")) { - const value = parseGeneral(entry.full_path); - if (value instanceof WindowsError) { - continue; - } - values = values.concat(value); - - } - } - - return values; -} - -/** - * Parse Application Launch PCA file - * @param path Path to PcaAppLaunchDic.txt - * @returns Array of `ProgramCompatibilityAssist` or `WindowsError` - */ -function parseDict(path: string): ProgramCompatibilityAssist[] | WindowsError { - const limit = 1000; - let offset = 0; - - const values: ProgramCompatibilityAssist[] = []; - while (true) { - const lines = readLines(path, offset, limit); - if (lines instanceof FileError) { - break; - } - - offset += limit; - for (const line of lines) { - const entries = line.split("|"); - const timestamp = `${entries.at(1)?.replace(" ", "T") ?? "1970-01-01T00:00:00.000"}Z`; - - const value: ProgramCompatibilityAssist = { - last_run: timestamp, - path: entries.at(0) ?? "Unknown path", - run_status: 0, - file_description: "", - vendor: "", - version: "", - program_id: "", - exit_message: "", - pca_type: PcaType.AppLaunch, - message: `PCA app launch: ${entries.at(0) ?? "Unknown path"}`, - datetime: timestamp, - source: path, - timestamp_desc: "Last Run", - artifact: "Windows Program Compatibility Assist", - data_type: "windows:pca:entry" - }; - values.push(value); - } - if (lines.length < limit) { - break; - } - } - - return values; -} - -/** - * Parse Application Launch PCA file - * @param path Path to PcaGeneralDb0.txt or PcaGeneralDb1.txt - * @returns Array of `ProgramCompatibilityAssist` or `WindowsError` - */ -function parseGeneral(path: string): ProgramCompatibilityAssist[] | WindowsError { - const bytes = readFile(path); - if (bytes instanceof FileError) { - return new WindowsError(`PCA`, `failed to read file ${path}: ${bytes}`); - } - const lines = extractUtf16String(bytes).split("\n"); - - const values: ProgramCompatibilityAssist[] = []; - for (const line of lines) { - const entries = line.split("|"); - const timestamp = `${entries.at(0)?.replace(" ", "T") ?? "1970-01-01T00:00:00.000"}Z`; - - const value: ProgramCompatibilityAssist = { - last_run: timestamp, - path: entries.at(2) ?? "Unknown path", - run_status: Number(entries.at(1) ?? 0), - file_description: entries.at(3) ?? "Unknown description", - vendor: entries.at(4) ?? "Unknown vendor", - version: entries.at(5) ?? "Unknown version", - program_id: entries.at(6) ?? "Unknown program ID", - exit_message: entries.at(7) ?? "Unknown exit message", - pca_type: PcaType.General, - message: `PCA general launch: ${entries.at(2) ?? "Unknown path"}`, - datetime: timestamp, - source: path, - timestamp_desc: "Last Run", - artifact: "Windows Program Compatibility Assist", - data_type: "windows:pca:entry" - }; - values.push(value); - } - - return values; -} - -/** - * Function to test Windows PCA parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows PCA parsing - */ -export function testParsePca(): void { - const test = "../../tests/test_data/windows/pca/*"; - const results = parsePca(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length !== 567) { - throw `Got ${results.length} expected 567.......parsePca ❌`; - } - - if (results[ 4 ]?.message !== "PCA app launch: C:\\Program Files\\WindowsApps\\Microsoft.OutlookForWindows_1.0.0.0_neutral__8wekyb3d8bbwe\\olk.exe") { - throw `Got ${results[ 4 ]?.message} expected "PCA app launch: C:\\Program Files\\WindowsApps\\Microsoft.OutlookForWindows_1.0.0.0_neutral__8wekyb3d8bbwe\\olk.exe".......parsePca ❌`; - } - console.info(` Function parsePca ✅`); - - const apps = parseDict("../../tests/test_data/windows/pca/PcaAppLaunchDic.txt"); - if (apps instanceof WindowsError) { - throw apps; - } - - if (apps.length !== 192) { - throw `Got ${apps.length} expected 192.......parseDict ❌`; - } - - if (apps[ 8 ]?.message !== "PCA app launch: C:\\Windows\\System32\\msiexec.exe") { - throw `Got ${apps[ 8 ]?.message} expected "PCA app launch: C:\\Windows\\System32\\msiexec.exe".......parseDict ❌`; - } - console.info(` Function parseDict ✅`); - - - const general = parseGeneral("../../tests/test_data/windows/pca/PcaGeneralDb0.txt"); - if (general instanceof WindowsError) { - throw general; - } - - if (general.length !== 375) { - throw `Got ${general.length} expected 375.......parseGeneral ❌`; - } - - if (general[ 8 ]?.message !== "PCA general launch: %USERPROFILE%\\Downloads\\LLVM-18.1.8-woa64.exe") { - throw `Got ${general[ 8 ]?.message} expected "PCA general launch: %USERPROFILE%\\Downloads\\LLVM-18.1.8-woa64.exe".......parseGeneral ❌`; - } - - console.info(` Function parseGeneral ✅`); - +import { PcaType, ProgramCompatibilityAssist } from "../../types/windows/pca"; +import { extractUtf16String } from "../encoding/strings"; +import { getEnvValue } from "../environment/mod"; +import { FileError } from "../filesystem/errors"; +import { glob, readFile, readLines } from "../filesystem/files"; +import { WindowsError } from "./errors"; + +/** + * Extract Program Compatibility Assist entries + * @param alt_dir Optional alternative glob to folder that contains PCA files + * @returns Array of `ProgramCompatibilityAssist` or `WindowsError` + */ +export function parsePca(alt_dir?: string): ProgramCompatibilityAssist[] | WindowsError { + let path = alt_dir; + if (path === undefined) { + const volume = getEnvValue("SystemDrive"); + if (volume === "") { + return new WindowsError(`PCA`, `no SystemDrive found`); + } + + path = `${volume}\\Windows\\appcompat\\pca\\*`; + } + + const globs = glob(path); + if (globs instanceof FileError) { + return new WindowsError(`PCA`, `failed to glob ${path}: ${globs}`); + } + + let values: ProgramCompatibilityAssist[] = []; + for (const entry of globs) { + if (entry.filename === "PcaAppLaunchDic.txt") { + const value = parseDict(entry.full_path); + if (value instanceof WindowsError) { + continue; + } + values = values.concat(value); + } else if (entry.filename.includes("General")) { + const value = parseGeneral(entry.full_path); + if (value instanceof WindowsError) { + continue; + } + values = values.concat(value); + + } + } + + return values; +} + +/** + * Parse Application Launch PCA file + * @param path Path to PcaAppLaunchDic.txt + * @returns Array of `ProgramCompatibilityAssist` or `WindowsError` + */ +function parseDict(path: string): ProgramCompatibilityAssist[] | WindowsError { + const limit = 1000; + let offset = 0; + + const values: ProgramCompatibilityAssist[] = []; + while (true) { + const lines = readLines(path, offset, limit); + if (lines instanceof FileError) { + break; + } + + offset += limit; + for (const line of lines) { + const entries = line.split("|"); + const timestamp = `${entries.at(1)?.replace(" ", "T") ?? "1970-01-01T00:00:00.000"}Z`; + + const value: ProgramCompatibilityAssist = { + last_run: timestamp, + path: entries.at(0) ?? "Unknown path", + run_status: 0, + file_description: "", + vendor: "", + version: "", + program_id: "", + exit_message: "", + pca_type: PcaType.AppLaunch, + message: `PCA app launch: ${entries.at(0) ?? "Unknown path"}`, + datetime: timestamp, + timestamp_desc: "Last Run", + artifact: "Windows Program Compatibility Assist", + data_type: "windows:pca:entry", + evidence: path, + }; + values.push(value); + } + if (lines.length < limit) { + break; + } + } + + return values; +} + +/** + * Parse Application Launch PCA file + * @param path Path to PcaGeneralDb0.txt or PcaGeneralDb1.txt + * @returns Array of `ProgramCompatibilityAssist` or `WindowsError` + */ +function parseGeneral(path: string): ProgramCompatibilityAssist[] | WindowsError { + const bytes = readFile(path); + if (bytes instanceof FileError) { + return new WindowsError(`PCA`, `failed to read file ${path}: ${bytes}`); + } + const lines = extractUtf16String(bytes).split("\n"); + + const values: ProgramCompatibilityAssist[] = []; + for (const line of lines) { + const entries = line.split("|"); + const timestamp = `${entries.at(0)?.replace(" ", "T") ?? "1970-01-01T00:00:00.000"}Z`; + + const value: ProgramCompatibilityAssist = { + last_run: timestamp, + path: entries.at(2) ?? "Unknown path", + run_status: Number(entries.at(1) ?? 0), + file_description: entries.at(3) ?? "Unknown description", + vendor: entries.at(4) ?? "Unknown vendor", + version: entries.at(5) ?? "Unknown version", + program_id: entries.at(6) ?? "Unknown program ID", + exit_message: entries.at(7) ?? "Unknown exit message", + pca_type: PcaType.General, + message: `PCA general launch: ${entries.at(2) ?? "Unknown path"}`, + datetime: timestamp, + evidence: path, + timestamp_desc: "Last Run", + artifact: "Windows Program Compatibility Assist", + data_type: "windows:pca:entry" + }; + values.push(value); + } + + return values; +} + +/** + * Function to test Windows PCA parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows PCA parsing + */ +export function testParsePca(): void { + const test = "../../tests/test_data/windows/pca/*"; + const results = parsePca(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length !== 567) { + throw `Got ${results.length} expected 567.......parsePca ❌`; + } + + if (results[ 4 ]?.message !== "PCA app launch: C:\\Program Files\\WindowsApps\\Microsoft.OutlookForWindows_1.0.0.0_neutral__8wekyb3d8bbwe\\olk.exe") { + throw `Got ${results[ 4 ]?.message} expected "PCA app launch: C:\\Program Files\\WindowsApps\\Microsoft.OutlookForWindows_1.0.0.0_neutral__8wekyb3d8bbwe\\olk.exe".......parsePca ❌`; + } + console.info(` Function parsePca ✅`); + + const apps = parseDict("../../tests/test_data/windows/pca/PcaAppLaunchDic.txt"); + if (apps instanceof WindowsError) { + throw apps; + } + + if (apps.length !== 192) { + throw `Got ${apps.length} expected 192.......parseDict ❌`; + } + + if (apps[ 8 ]?.message !== "PCA app launch: C:\\Windows\\System32\\msiexec.exe") { + throw `Got ${apps[ 8 ]?.message} expected "PCA app launch: C:\\Windows\\System32\\msiexec.exe".......parseDict ❌`; + } + console.info(` Function parseDict ✅`); + + + const general = parseGeneral("../../tests/test_data/windows/pca/PcaGeneralDb0.txt"); + if (general instanceof WindowsError) { + throw general; + } + + if (general.length !== 375) { + throw `Got ${general.length} expected 375.......parseGeneral ❌`; + } + + if (general[ 8 ]?.message !== "PCA general launch: %USERPROFILE%\\Downloads\\LLVM-18.1.8-woa64.exe") { + throw `Got ${general[ 8 ]?.message} expected "PCA general launch: %USERPROFILE%\\Downloads\\LLVM-18.1.8-woa64.exe".......parseGeneral ❌`; + } + + console.info(` Function parseGeneral ✅`); + } \ No newline at end of file diff --git a/src/windows/powershell.ts b/src/windows/powershell.ts index 228be855..3d149c1d 100644 --- a/src/windows/powershell.ts +++ b/src/windows/powershell.ts @@ -1,145 +1,144 @@ -import { PlatformType } from "../../mod"; -import { History } from "../../types/windows/powershell"; -import { getEnvValue } from "../environment/env"; -import { FileError } from "../filesystem/errors"; -import { readTextFile, stat } from "../filesystem/files"; -import { glob } from "../filesystem/files"; -import { WindowsError } from "./errors"; - -/** - * Attempts to parse PowerShell history for all users using the default system drive - * @param [platform=PlatformType.Windows] `PlatformType` to parse. Windows is the default - * @param alt_path Alternative Path to PowerShell history file - * @returns Array of PowerShell `History` entries or single `History` or `WindowsError` - */ -export function powershellHistory( - platform = PlatformType.Windows, - alt_path?: string, -): History[] | WindowsError { - if (alt_path !== undefined) { - return parsePowershellHistory(alt_path, platform); - } - - let glob_pattern = ""; - if (platform === PlatformType.Windows) { - const drive = getEnvValue("SystemDrive"); - if (drive === "") { - return new WindowsError("POWERSHELL", `failed get drive`); - } - glob_pattern = - `${drive}\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt`; - } - - if (platform === PlatformType.Darwin) { - glob_pattern = "/Users/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt"; - } else if (platform === PlatformType.Linux) { - glob_pattern = "/home/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt"; - } - const paths = glob(glob_pattern); - if (paths instanceof FileError) { - return new WindowsError("POWERSHELL", `failed glob paths`); - } - - let history: History[] = []; - for (const path of paths) { - const entries = parsePowershellHistory(path.full_path, platform); - if (entries instanceof WindowsError) { - continue; - } - - history = history.concat(entries); - } - - return history; -} - -/** - * Parse a single PowerShell history file - * @param path Path to PowerShell history file - * @returns PowerShell `History` data or `WindowsError` - */ -function parsePowershellHistory(path: string, platform: PlatformType): History[] | WindowsError { - const data = readTextFile(path); - if (data instanceof FileError) { - console.warn(`could not read file ${path}: ${data}`); - return new WindowsError( - "POWERSHELL", - `failed to read PowerShell history file ${path}`, - ); - } - - let entries: string[] = []; - - if (platform === PlatformType.Windows) { - entries = data.split("\r\n"); - if (entries.length === 0) { - entries = data.split("\n"); - } - } else { - entries = data.split("\n"); - } - const meta = stat(path); - const values: History[] = []; - for (const line of entries) { - const ps_history: History = { - line, - path, - created: "1970-01-01T00:00:00.000Z", - modified: "1970-01-01T00:00:00.000Z", - accessed: "1970-01-01T00:00:00.000Z", - changed: "1970-01-01T00:00:00.000Z", - message: `PowerShell console command '${line}'`, - datetime: "1970-01-01T00:00:00.000Z", - timestamp_desc: "PowerShell History Modified", - artifact: "PowerShell History", - data_type: "application:powershell:entry" - }; - - if (!(meta instanceof FileError)) { - ps_history.changed = meta.changed; - ps_history.modified = meta.modified; - ps_history.created = meta.created; - ps_history.accessed = meta.accessed; - ps_history.datetime = meta.modified; - } - - values.push(ps_history); - } - - return values; -} - -/** - * Function to test PowerShell History parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the PowerShell History parsing - */ -export function testPowerShellHistory(): void { - const test = "../test_data/windows/powershell/history.txt"; - const hits = powershellHistory(PlatformType.Windows, test); - if (hits instanceof WindowsError) { - throw hits; - } - - if (hits[0] === undefined || !Array.isArray(hits)) { - throw `Got ${hits[0]} expected an array.......powershellHistory ❌`; - } - - if (hits[0].line !== 'whoami') { - throw `Got ${hits[0].line} expected 'whoami'.......powershellHistory ❌`; - } - - console.info(` Function powershellHistory ✅`); - - - const values = parsePowershellHistory(test, PlatformType.Windows); - if (values instanceof WindowsError) { - throw values; - } - if (values[0]?.line !== 'whoami') { - throw `Got ${values[0]?.line} expected 'whoami'.......parsePowershellHistory ❌`; - } - - console.info(` Function parsePowershellHistory ✅`); - +import { PlatformType } from "../../mod"; +import { History } from "../../types/windows/powershell"; +import { getEnvValue } from "../environment/env"; +import { FileError } from "../filesystem/errors"; +import { readTextFile, stat } from "../filesystem/files"; +import { glob } from "../filesystem/files"; +import { WindowsError } from "./errors"; + +/** + * Attempts to parse PowerShell history for all users using the default system drive + * @param [platform=PlatformType.Windows] `PlatformType` to parse. Windows is the default + * @param alt_path Alternative Path to PowerShell history file + * @returns Array of PowerShell `History` entries or single `History` or `WindowsError` + */ +export function powershellHistory( + platform = PlatformType.Windows, + alt_path?: string, +): History[] | WindowsError { + if (alt_path !== undefined) { + return parsePowershellHistory(alt_path, platform); + } + + let glob_pattern = ""; + if (platform === PlatformType.Windows) { + const drive = getEnvValue("SystemDrive"); + if (drive === "") { + return new WindowsError("POWERSHELL", `failed get drive`); + } + glob_pattern = + `${drive}\\Users\\*\\AppData\\Roaming\\Microsoft\\Windows\\PowerShell\\PSReadLine\\ConsoleHost_history.txt`; + } + + if (platform === PlatformType.Darwin) { + glob_pattern = "/Users/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt"; + } else if (platform === PlatformType.Linux) { + glob_pattern = "/home/*/.local/share/PowerShell/PSReadLine/ConsoleHost_history.txt"; + } + const paths = glob(glob_pattern); + if (paths instanceof FileError) { + return new WindowsError("POWERSHELL", `failed glob paths`); + } + + let history: History[] = []; + for (const path of paths) { + const entries = parsePowershellHistory(path.full_path, platform); + if (entries instanceof WindowsError) { + continue; + } + + history = history.concat(entries); + } + + return history; +} + +/** + * Parse a single PowerShell history file + * @param path Path to PowerShell history file + * @returns PowerShell `History` data or `WindowsError` + */ +function parsePowershellHistory(path: string, platform: PlatformType): History[] | WindowsError { + const data = readTextFile(path); + if (data instanceof FileError) { + console.warn(`could not read file ${path}: ${data}`); + return new WindowsError( + "POWERSHELL", + `failed to read PowerShell history file ${path}`, + ); + } + + let entries; + if (platform === PlatformType.Windows) { + entries = data.split("\r\n"); + if (entries.length === 0) { + entries = data.split("\n"); + } + } else { + entries = data.split("\n"); + } + const meta = stat(path); + const values: History[] = []; + for (const line of entries) { + const ps_history: History = { + line, + created: "1970-01-01T00:00:00.000Z", + modified: "1970-01-01T00:00:00.000Z", + accessed: "1970-01-01T00:00:00.000Z", + changed: "1970-01-01T00:00:00.000Z", + message: `PowerShell console command '${line}'`, + datetime: "1970-01-01T00:00:00.000Z", + timestamp_desc: "PowerShell History Modified", + artifact: "PowerShell History", + data_type: "application:powershell:entry", + evidence: path, + }; + + if (!(meta instanceof FileError)) { + ps_history.changed = meta.changed; + ps_history.modified = meta.modified; + ps_history.created = meta.created; + ps_history.accessed = meta.accessed; + ps_history.datetime = meta.modified; + } + + values.push(ps_history); + } + + return values; +} + +/** + * Function to test PowerShell History parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the PowerShell History parsing + */ +export function testPowerShellHistory(): void { + const test = "../test_data/windows/powershell/history.txt"; + const hits = powershellHistory(PlatformType.Windows, test); + if (hits instanceof WindowsError) { + throw hits; + } + + if (hits[0] === undefined || !Array.isArray(hits)) { + throw `Got ${hits[0]} expected an array.......powershellHistory ❌`; + } + + if (hits[0].line !== 'whoami') { + throw `Got ${hits[0].line} expected 'whoami'.......powershellHistory ❌`; + } + + console.info(` Function powershellHistory ✅`); + + + const values = parsePowershellHistory(test, PlatformType.Windows); + if (values instanceof WindowsError) { + throw values; + } + if (values[0]?.line !== 'whoami') { + throw `Got ${values[0]?.line} expected 'whoami'.......parsePowershellHistory ❌`; + } + + console.info(` Function parsePowershellHistory ✅`); + } \ No newline at end of file diff --git a/src/windows/registry/bam.ts b/src/windows/registry/bam.ts index 2ba23d77..eea72b36 100644 --- a/src/windows/registry/bam.ts +++ b/src/windows/registry/bam.ts @@ -1,65 +1,66 @@ -import { Bam } from "../../../types/windows/registry/bam"; -import { decode } from "../../encoding/base64"; -import { EncodingError } from "../../encoding/errors"; -import { getEnvValue } from "../../environment/env"; -import { NomError } from "../../nom/error"; -import { Endian, nomUnsignedEightBytes } from "../../nom/helpers"; -import { filetimeToUnixEpoch, unixEpochToISO } from "../../time/conversion"; -import { WindowsError } from "../errors"; -import { getRegistry } from "../registry"; - -/** - * Function to extract Background Activities Manager entries from Registry - * @param alt_path Optional path to the SYSTEM Registry file - * @returns Array of `Bam` or `WindowsError` - */ -export function backgroundActivitiesManager(alt_path?: string): Bam[] | WindowsError { - let path = `${getEnvValue("SystemDrive")}\\Windows\\System32\\config\\SYSTEM`; - if (alt_path !== undefined) { - path = alt_path; - } - const reg_data = getRegistry(path); - if (reg_data instanceof WindowsError) { - return new WindowsError(`BAM`, `failed to parse ${path}: ${reg_data.message}`); - } - - const values: Bam[] = []; - for (const entry of reg_data) { - if (!entry.path.includes("Services\\bam\\State\\UserSettings\\")) { - continue; - } - - if (entry.name === "Version" || entry.name === "SequenceNumber") { - continue; - } - - for (const value of entry.values) { - const data = decode(value.data); - if (data instanceof EncodingError) { - continue; - } - - const timestamp = nomUnsignedEightBytes(data, Endian.Le); - if (timestamp instanceof NomError) { - continue; - } - - const last_execution = unixEpochToISO(filetimeToUnixEpoch(timestamp.value)); - const bam_entry: Bam = { - key_path: entry.path, - reg_path: entry.registry_path, - sid: entry.name, - path: value.value, - last_execution, - message: `BAM: ${value.value}`, - datetime:last_execution, - timestamp_desc: "Last Execution", - artifact: "Windows Background Activity Monitor", - data_type: "windows:registry:bam:entry" - }; - - values.push(bam_entry); - } - } - return values; +import { Bam } from "../../../types/windows/registry/bam"; +import { decode } from "../../encoding/base64"; +import { EncodingError } from "../../encoding/errors"; +import { getEnvValue } from "../../environment/env"; +import { NomError } from "../../nom/error"; +import { Endian, nomUnsignedEightBytes } from "../../nom/helpers"; +import { filetimeToUnixEpoch, unixEpochToISO } from "../../time/conversion"; +import { WindowsError } from "../errors"; +import { getRegistry } from "../registry"; + +/** + * Function to extract Background Activities Manager entries from Registry + * @param alt_path Optional path to the SYSTEM Registry file + * @returns Array of `Bam` or `WindowsError` + */ +export function backgroundActivitiesManager(alt_path?: string): Bam[] | WindowsError { + let path = `${getEnvValue("SystemDrive")}\\Windows\\System32\\config\\SYSTEM`; + if (alt_path !== undefined) { + path = alt_path; + } + const reg_data = getRegistry(path); + if (reg_data instanceof WindowsError) { + return new WindowsError(`BAM`, `failed to parse ${path}: ${reg_data.message}`); + } + + const values: Bam[] = []; + for (const entry of reg_data) { + if (!entry.path.includes("Services\\bam\\State\\UserSettings\\")) { + continue; + } + + if (entry.name === "Version" || entry.name === "SequenceNumber") { + continue; + } + + for (const value of entry.values) { + const data = decode(value.data); + if (data instanceof EncodingError) { + continue; + } + + const timestamp = nomUnsignedEightBytes(data, Endian.Le); + if (timestamp instanceof NomError) { + continue; + } + + const last_execution = unixEpochToISO(filetimeToUnixEpoch(timestamp.value)); + const bam_entry: Bam = { + key_path: entry.path, + reg_path: entry.evidence, + sid: entry.name, + path: value.value, + last_execution, + message: `BAM: ${value.value}`, + datetime:last_execution, + timestamp_desc: "Last Execution", + artifact: "Windows Background Activity Monitor", + data_type: "windows:registry:bam:entry", + evidence: path, + }; + + values.push(bam_entry); + } + } + return values; } \ No newline at end of file diff --git a/src/windows/registry/eventlog_providers.ts b/src/windows/registry/eventlog_providers.ts index e5b46ef6..410d8df6 100644 --- a/src/windows/registry/eventlog_providers.ts +++ b/src/windows/registry/eventlog_providers.ts @@ -1,228 +1,228 @@ -import { Registry } from "../../../types/windows/registry"; -import { ChannelType, RegistryEventlogProviders } from "../../../types/windows/registry/eventlog_providers"; -import { WindowsError } from "../errors"; -import { getRegistry } from "../registry"; - -/** - * Function to list registered Windows EventLog providers - * @param alt_path Optional alternative path to the Windows Registry file - * @returns Array of `RegistryEventlogProviders` - */ -export function getEventlogProviders(alt_path?: string): RegistryEventlogProviders[] { - let default_paths = ["C:\\Windows\\System32\\config\\SYSTEM", "C:\\Windows\\System32\\config\\SOFTWARE"]; - if (alt_path !== undefined) { - default_paths = [alt_path]; - } - - const providers: RegistryEventlogProviders[] = []; - const pub: Record = {}; - for (const entry of default_paths) { - if (entry.endsWith("SYSTEM")) { - const system_filter = ".*ControlSet.*\\\\Services\\\\EventLog\\\\.*"; - const results = getRegistry(entry, system_filter); - if (results instanceof WindowsError) { - continue; - } - for (const value of results) { - if (value.values.length === 0) { - continue; - } - const info = extractProviderInfo(value); - if (info.message_file === "" && info.parameter_file === "" && info.guid === "") { - continue; - } - providers.push(info); - } - } else if (entry.endsWith("SOFTWARE")) { - const system_filter = ".*CurrentVersion.*\\\\WINEVT\\\\Channels|Publishers\\\\.*"; - const results = getRegistry(entry, system_filter); - if (results instanceof WindowsError) { - continue; - } - for (const value of results) { - if (value.values.length === 0) { - continue; - } - const info = extractPublisherInfo(value); - if (info.guid === "") { - continue; - } - const pub_value = pub[info.guid]; - if (pub_value !== undefined) { - pub_value.names = pub_value.names.concat(info.names); - pub_value.channel_types = pub_value.channel_types.concat(info.channel_types); - if (pub_value.message_file === "") { - pub_value.message_file = info.message_file; - } else if (pub_value.parameter_file === "") { - pub_value.parameter_file = info.parameter_file; - } - if(info.enabled) { - pub_value.enabled = info.enabled; - } - continue; - } - - pub[info.guid] = info; - } - } - } - - for (let i = 0; i < providers.length; i++) { - const entry = providers[i]; - if (entry === undefined) { - continue; - } - if (entry.guid === "") { - const value = pub[entry.name.toLowerCase()]; - if (value === undefined) { - continue; - } - entry.guid = value.guid; - entry.enabled = value.enabled; - entry.channel_types = value.channel_types; - entry.channel_names = value.names; - } else { - const value = pub[entry.guid.toLowerCase()]; - if (value === undefined) { - continue; - } - entry.guid = value.guid; - entry.enabled = value.enabled; - entry.channel_types = value.channel_types; - entry.channel_names = value.names; - } - } - - Object.keys(pub).forEach(key => { - const value = pub[key]; - if (value !== undefined && (value.message_file !== "" || value.parameter_file !== "")) { - const prov_value: RegistryEventlogProviders = { - registry_file: value.registry_file, - key_path: value.key_path.split("/").at(0) ?? "", - name: value.names.at(0) ?? "Unknown", - channel_names: value.names, - message_file: value.message_file, - last_modified: value.last_modified, - parameter_file: value.parameter_file, - guid: value.guid, - enabled: value.enabled, - channel_types: value.channel_types, - message: `EventLog Provider: ${value.names.at(0) ?? "Unknown"}`, - datetime: value.last_modified, - timestamp_desc: "Registry Last Modified", - artifact: "Windows EventLog Provider", - data_type: "windows:registry:eventlogprovider:entry" - }; - providers.push(prov_value); - } - }) - - return providers; -} - -function extractProviderInfo(value: Registry): RegistryEventlogProviders { - const values: RegistryEventlogProviders = { - registry_file: value.registry_path, - key_path: value.path, - name: value.name, - channel_names: [], - message_file: "", - last_modified: value.last_modified, - parameter_file: "", - guid: "", - enabled: false, - channel_types: [], - message: "", - datetime: "", - timestamp_desc: "Registry Last Modified", - artifact: "Windows EventLog Provider", - data_type: "windows:registry:eventlogprovider:entry" - }; - for (const entry of value.values) { - if (entry.value.toLowerCase() === "providerguid") { - values.guid = entry.data; - } else if (entry.value.toLowerCase() === "eventmessagefile") { - values.message_file = entry.data; - } else if (entry.value.toLowerCase() === "parametermessagefile") { - values.parameter_file = entry.data; - } - } - return values; -} - -interface Publisher { - guid: string; - names: string[]; - enabled: boolean; - channel_number: number; - channel_types: ChannelType[]; - message_file: string; - parameter_file: string; - registry_file: string; - key_path: string; - last_modified: string; -} - -function extractPublisherInfo(values: Registry): Publisher { - const value: Publisher = { - guid: "", - names: [], - enabled: false, - channel_number: 0, - channel_types: [], - message_file: "", - parameter_file: "", - registry_file: values.registry_path, - key_path: values.path, - last_modified: values.last_modified, - }; - for (const entry of values.values) { - if (values.path.includes("Publishers") && values.name.startsWith("{")) { - if (entry.value === "(default)") { - value.guid = values.name; - value.names.push(entry.data); - } else if (entry.value === "MessageFileName") { - value.message_file = entry.data; - } else if (entry.value === "ParameterFileName") { - value.parameter_file = entry.data; - } - } else if (values.path.includes("Publishers") && values.path.includes("ChannelReferences")) { - if (entry.value === "(default)") { - value.guid = `{${(values.path.split("{").at(1) ?? "").split("}").at(0)}}` - if (entry.data === "Application" || entry.data === "System") { - continue; - } - value.names.push(entry.data); - } - } else if (values.path.includes("Channels\\")) { - if (entry.value.toLowerCase() === "enabled") { - value.enabled = Boolean(Number(entry.data)); - } else if (entry.value.toLowerCase() === "owningpublisher") { - value.guid = entry.data; - } else if (entry.value.toLowerCase() === "type") { - value.channel_number = Number(entry.data); - switch (value.channel_number) { - case 0: { - value.channel_types.push(ChannelType.Admin); - break; - } - case 1: { - value.channel_types.push(ChannelType.Operational); - break; - } - case 2: { - value.channel_types.push(ChannelType.Analytic); - break; - } - case 3: { - value.channel_types.push(ChannelType.Debug); - break; - } - default: break; - } - } - } - } - - return value; +import { Registry } from "../../../types/windows/registry"; +import { ChannelType, RegistryEventlogProviders } from "../../../types/windows/registry/eventlog_providers"; +import { WindowsError } from "../errors"; +import { getRegistry } from "../registry"; + +/** + * Function to list registered Windows EventLog providers + * @param alt_path Optional alternative path to the Windows Registry file + * @returns Array of `RegistryEventlogProviders` + */ +export function getEventlogProviders(alt_path?: string): RegistryEventlogProviders[] { + let default_paths = ["C:\\Windows\\System32\\config\\SYSTEM", "C:\\Windows\\System32\\config\\SOFTWARE"]; + if (alt_path !== undefined) { + default_paths = [alt_path]; + } + + const providers: RegistryEventlogProviders[] = []; + const pub: Record = {}; + for (const entry of default_paths) { + if (entry.endsWith("SYSTEM")) { + const system_filter = ".*ControlSet.*\\\\Services\\\\EventLog\\\\.*"; + const results = getRegistry(entry, system_filter); + if (results instanceof WindowsError) { + continue; + } + for (const value of results) { + if (value.values.length === 0) { + continue; + } + const info = extractProviderInfo(value); + if (info.message_file === "" && info.parameter_file === "" && info.guid === "") { + continue; + } + providers.push(info); + } + } else if (entry.endsWith("SOFTWARE")) { + const system_filter = ".*CurrentVersion.*\\\\WINEVT\\\\Channels|Publishers\\\\.*"; + const results = getRegistry(entry, system_filter); + if (results instanceof WindowsError) { + continue; + } + for (const value of results) { + if (value.values.length === 0) { + continue; + } + const info = extractPublisherInfo(value); + if (info.guid === "") { + continue; + } + const pub_value = pub[info.guid]; + if (pub_value !== undefined) { + pub_value.names = pub_value.names.concat(info.names); + pub_value.channel_types = pub_value.channel_types.concat(info.channel_types); + if (pub_value.message_file === "") { + pub_value.message_file = info.message_file; + } else if (pub_value.parameter_file === "") { + pub_value.parameter_file = info.parameter_file; + } + if(info.enabled) { + pub_value.enabled = info.enabled; + } + continue; + } + + pub[info.guid] = info; + } + } + } + + for (let i = 0; i < providers.length; i++) { + const entry = providers[i]; + if (entry === undefined) { + continue; + } + if (entry.guid === "") { + const value = pub[entry.name.toLowerCase()]; + if (value === undefined) { + continue; + } + entry.guid = value.guid; + entry.enabled = value.enabled; + entry.channel_types = value.channel_types; + entry.channel_names = value.names; + } else { + const value = pub[entry.guid.toLowerCase()]; + if (value === undefined) { + continue; + } + entry.guid = value.guid; + entry.enabled = value.enabled; + entry.channel_types = value.channel_types; + entry.channel_names = value.names; + } + } + + Object.keys(pub).forEach(key => { + const value = pub[key]; + if (value !== undefined && (value.message_file !== "" || value.parameter_file !== "")) { + const prov_value: RegistryEventlogProviders = { + evidence: value.evidence, + key_path: value.key_path.split("/").at(0) ?? "", + name: value.names.at(0) ?? "Unknown", + channel_names: value.names, + message_file: value.message_file, + last_modified: value.last_modified, + parameter_file: value.parameter_file, + guid: value.guid, + enabled: value.enabled, + channel_types: value.channel_types, + message: `EventLog Provider: ${value.names.at(0) ?? "Unknown"}`, + datetime: value.last_modified, + timestamp_desc: "Registry Last Modified", + artifact: "Windows EventLog Provider", + data_type: "windows:registry:eventlogprovider:entry" + }; + providers.push(prov_value); + } + }) + + return providers; +} + +function extractProviderInfo(value: Registry): RegistryEventlogProviders { + const values: RegistryEventlogProviders = { + evidence: value.evidence, + key_path: value.path, + name: value.name, + channel_names: [], + message_file: "", + last_modified: value.last_modified, + parameter_file: "", + guid: "", + enabled: false, + channel_types: [], + message: "", + datetime: "", + timestamp_desc: "Registry Last Modified", + artifact: "Windows EventLog Provider", + data_type: "windows:registry:eventlogprovider:entry" + }; + for (const entry of value.values) { + if (entry.value.toLowerCase() === "providerguid") { + values.guid = entry.data; + } else if (entry.value.toLowerCase() === "eventmessagefile") { + values.message_file = entry.data; + } else if (entry.value.toLowerCase() === "parametermessagefile") { + values.parameter_file = entry.data; + } + } + return values; +} + +interface Publisher { + guid: string; + names: string[]; + enabled: boolean; + channel_number: number; + channel_types: ChannelType[]; + message_file: string; + parameter_file: string; + evidence: string; + key_path: string; + last_modified: string; +} + +function extractPublisherInfo(values: Registry): Publisher { + const value: Publisher = { + guid: "", + names: [], + enabled: false, + channel_number: 0, + channel_types: [], + message_file: "", + parameter_file: "", + evidence: values.evidence, + key_path: values.path, + last_modified: values.last_modified, + }; + for (const entry of values.values) { + if (values.path.includes("Publishers") && values.name.startsWith("{")) { + if (entry.value === "(default)") { + value.guid = values.name; + value.names.push(entry.data); + } else if (entry.value === "MessageFileName") { + value.message_file = entry.data; + } else if (entry.value === "ParameterFileName") { + value.parameter_file = entry.data; + } + } else if (values.path.includes("Publishers") && values.path.includes("ChannelReferences")) { + if (entry.value === "(default)") { + value.guid = `{${(values.path.split("{").at(1) ?? "").split("}").at(0)}}` + if (entry.data === "Application" || entry.data === "System") { + continue; + } + value.names.push(entry.data); + } + } else if (values.path.includes("Channels\\")) { + if (entry.value.toLowerCase() === "enabled") { + value.enabled = Boolean(Number(entry.data)); + } else if (entry.value.toLowerCase() === "owningpublisher") { + value.guid = entry.data; + } else if (entry.value.toLowerCase() === "type") { + value.channel_number = Number(entry.data); + switch (value.channel_number) { + case 0: { + value.channel_types.push(ChannelType.Admin); + break; + } + case 1: { + value.channel_types.push(ChannelType.Operational); + break; + } + case 2: { + value.channel_types.push(ChannelType.Analytic); + break; + } + case 3: { + value.channel_types.push(ChannelType.Debug); + break; + } + default: break; + } + } + } + } + + return value; } \ No newline at end of file diff --git a/src/windows/registry/firewall_rules.ts b/src/windows/registry/firewall_rules.ts index 18fa656b..7d274e34 100644 --- a/src/windows/registry/firewall_rules.ts +++ b/src/windows/registry/firewall_rules.ts @@ -1,169 +1,167 @@ -import { Direction, FirewallRules, Protocol } from "../../../types/windows/registry/firewall_rules"; -import { getEnvValue } from "../../environment/mod"; -import { WindowsError } from "../errors"; -import { getRegistry } from "../registry"; - -/** - * Function to extract Windows Firewall rules in the Registry - * @param alt_file Alternative path to `SYSTEM` Registry file - * @returns Array of `FirewallRules` or `WindowsError` - */ -export function firewallRules(alt_file?: string): FirewallRules[] | WindowsError { - let path = ""; - if (alt_file !== undefined) { - path = alt_file; - } else { - path = `${getEnvValue("SystemDrive")}\\Windows\\System32\\config\\SYSTEM`; - } - - const reg_data = getRegistry(path); - if (reg_data instanceof WindowsError) { - return new WindowsError(`FIREWALL_RULES`, `failed to parse ${path}: ${reg_data}`); - } - - const rules: FirewallRules[] = []; - for (const entry of reg_data) { - if (!entry.path.endsWith("\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\FirewallRules")) { - continue; - } - - for (const value of entry.values) { - if (value.data_type !== "REG_SZ") { - continue; - } - const entries = value.data.split("|"); - const rule: FirewallRules = { - action: "", - active: false, - direction: Direction.Unknown, - protocol: Protocol.Unkonwn, - protocol_number: 0, - local_port: 0, - remote_port: 0, - name: "", - description: "", - application: "", - registry_file: entry.registry_path, - key_path: entry.path, - last_modified: entry.last_modified, - rule_version: "", - profile: "", - service: "", - remote_address: [], - registry_key_name: value.value, - local_address: [], - message: "", - datetime: entry.last_modified, - timestamp_desc: "Registry Last Modified", - artifact: "Windows Firewall Rule", - data_type: "windows:registry:firewallrule:entry" - }; - - for (const rule_values of entries) { - if (rule_values.startsWith("v") && !rule_values.includes("=")) { - rule.rule_version = rule_values; - continue; - } - - const key_value = rule_values.split("="); - const key = key_value.at(0); - if (key === "") { - continue; - } - - switch (key) { - case "Action": - rule.action = key_value.at(1) ?? ""; - break; - case "Active": - rule.active = JSON.parse(key_value.at(1)?.toLowerCase() ?? "false"); - break; - case "Dir": - rule.direction = getDirection(key_value.at(1) ?? ""); - break; - case "Protocol": - rule.protocol = getProtocol(key_value.at(1) ?? ""); - rule.protocol_number = Number(key_value.at(1)); - break; - case "Profile": - rule.profile = key_value.at(1) ?? ""; - break; - case "RPort": - rule.remote_port = Number(key_value.at(1)); - break; - case "LPort": - rule.local_port = Number(key_value.at(1)); - break; - case "App": - rule.application = key_value.at(1) ?? ""; - break; - case "Svc": - rule.service = key_value.at(1) ?? ""; - break; - case "Name": - rule.name = key_value.at(1) ?? ""; - rule.message = `Firewall Rule: ${rule.name}`; - break; - case "Desc": - rule.description = key_value.at(1) ?? ""; - break; - case "RA4": - rule.remote_address.push(key_value.at(1) ?? ""); - break; - case "RA6": - rule.remote_address.push(key_value.at(1) ?? ""); - break; - case "LA4": - rule.local_address.push(key_value.at(1) ?? ""); - break; - case "LA6": - rule.local_address.push(key_value.at(1) ?? ""); - break; - case undefined: - break; - default: - rule[ key ] = key_value.at(1) ?? ""; - break; - } - } - - rules.push(rule); - } - } - - return rules; -} - -/** - * Determines Firewall direction - * @param dir Direction of the rule as string - * @returns `Direction` enum value - */ -function getDirection(dir: string): Direction { - if (dir.toLowerCase() === "in") { - return Direction.Inbound; - } else if (dir.toLowerCase() === "out") { - return Direction.Outbound; - } - - return Direction.Unknown; -} - -/** - * Determines Firewall protocol - * @param proto Protocol number as string - * @returns `Protocol` enum value - */ -function getProtocol(proto: string): Protocol { - // From https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml - switch (proto) { - case "6": return Protocol.TCP; - case "1": return Protocol.ICMP; - case "17": return Protocol.UDP; - case "58": return Protocol.ICMP_v6; - case "41": return Protocol.IPV6; - case "47": return Protocol.GRE; - case "2": return Protocol.IGMP; - default: return Protocol.Unkonwn; - } +import { Direction, FirewallRules, Protocol } from "../../../types/windows/registry/firewall_rules"; +import { getSystemDrive } from "../../environment/env"; +import { WindowsError } from "../errors"; +import { getRegistry } from "../registry"; + +/** + * Function to extract Windows Firewall rules in the Registry + * @param alt_file Alternative path to `SYSTEM` Registry file + * @returns Array of `FirewallRules` or `WindowsError` + */ +export function firewallRules(alt_file?: string): FirewallRules[] | WindowsError { + let path = `${getSystemDrive()}\\Windows\\System32\\config\\SYSTEM`; + if (alt_file !== undefined) { + path = alt_file; + } + + const reg_data = getRegistry(path); + if (reg_data instanceof WindowsError) { + return new WindowsError(`FIREWALL_RULES`, `failed to parse ${path}: ${reg_data}`); + } + + const rules: FirewallRules[] = []; + for (const entry of reg_data) { + if (!entry.path.endsWith("\\Services\\SharedAccess\\Parameters\\FirewallPolicy\\FirewallRules")) { + continue; + } + + for (const value of entry.values) { + if (value.data_type !== "REG_SZ") { + continue; + } + const entries = value.data.split("|"); + const rule: FirewallRules = { + action: "", + active: false, + direction: Direction.Unknown, + protocol: Protocol.Unknown, + protocol_number: 0, + local_port: 0, + remote_port: 0, + name: "", + description: "", + application: "", + evidence: entry.evidence, + key_path: entry.path, + last_modified: entry.last_modified, + rule_version: "", + profile: "", + service: "", + remote_address: [], + registry_key_name: value.value, + local_address: [], + message: "", + datetime: entry.last_modified, + timestamp_desc: "Registry Last Modified", + artifact: "Windows Firewall Rule", + data_type: "windows:registry:firewallrule:entry" + }; + + for (const rule_values of entries) { + if (rule_values.startsWith("v") && !rule_values.includes("=")) { + rule.rule_version = rule_values; + continue; + } + + const key_value = rule_values.split("="); + const key = key_value.at(0); + if (key === "") { + continue; + } + + switch (key) { + case "Action": + rule.action = key_value.at(1) ?? ""; + break; + case "Active": + rule.active = JSON.parse(key_value.at(1)?.toLowerCase() ?? "false"); + break; + case "Dir": + rule.direction = getDirection(key_value.at(1) ?? ""); + break; + case "Protocol": + rule.protocol = getProtocol(key_value.at(1) ?? ""); + rule.protocol_number = Number(key_value.at(1)); + break; + case "Profile": + rule.profile = key_value.at(1) ?? ""; + break; + case "RPort": + rule.remote_port = Number(key_value.at(1)); + break; + case "LPort": + rule.local_port = Number(key_value.at(1)); + break; + case "App": + rule.application = key_value.at(1) ?? ""; + break; + case "Svc": + rule.service = key_value.at(1) ?? ""; + break; + case "Name": + rule.name = key_value.at(1) ?? ""; + rule.message = `Firewall Rule: ${rule.name}`; + break; + case "Desc": + rule.description = key_value.at(1) ?? ""; + break; + case "RA4": + rule.remote_address.push(key_value.at(1) ?? ""); + break; + case "RA6": + rule.remote_address.push(key_value.at(1) ?? ""); + break; + case "LA4": + rule.local_address.push(key_value.at(1) ?? ""); + break; + case "LA6": + rule.local_address.push(key_value.at(1) ?? ""); + break; + case undefined: + break; + default: + rule[ key ] = key_value.at(1) ?? ""; + break; + } + } + + rules.push(rule); + } + } + + return rules; +} + +/** + * Determines Firewall direction + * @param dir Direction of the rule as string + * @returns `Direction` enum value + */ +function getDirection(dir: string): Direction { + if (dir.toLowerCase() === "in") { + return Direction.Inbound; + } else if (dir.toLowerCase() === "out") { + return Direction.Outbound; + } + + return Direction.Unknown; +} + +/** + * Determines Firewall protocol + * @param proto Protocol number as string + * @returns `Protocol` enum value + */ +function getProtocol(proto: string): Protocol { + // From https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml + switch (proto) { + case "6": return Protocol.TCP; + case "1": return Protocol.ICMP; + case "17": return Protocol.UDP; + case "58": return Protocol.ICMP_v6; + case "41": return Protocol.IPV6; + case "47": return Protocol.GRE; + case "2": return Protocol.IGMP; + default: return Protocol.Unknown; + } } \ No newline at end of file diff --git a/src/windows/registry/recently_used.ts b/src/windows/registry/recently_used.ts index 7d32d03f..d74a9cb4 100644 --- a/src/windows/registry/recently_used.ts +++ b/src/windows/registry/recently_used.ts @@ -1,229 +1,229 @@ -import { Registry } from "../../../types/windows/registry"; -import { - Mru, - MruType, - MruValues, -} from "../../../types/windows/registry/recently_used"; -import { ShellItems } from "../../../types/windows/shellitems"; -import { WindowsError } from "../errors"; -import { getRegistry } from "../registry"; -import { lastVisitMru, openSaveMru } from "./mru/common"; -import { recentDocs } from "./mru/recent_docs"; - -/** - * Parse common Most Recently Used (MRU) Registry keys - * @param ntuser_path Path to NTUSER.DAT file - * @returns Array of common `Mru` entries or `WindowsError` - */ -export function parseMru(ntuser_path: string): Mru[] | WindowsError { - const reg_data = getRegistry(ntuser_path); - if (reg_data instanceof WindowsError) { - return new WindowsError( - "MRU", - `Could not parse Registry ${ntuser_path}: ${reg_data}`, - ); - } - - const common = openSaveMru(reg_data); - if (common instanceof WindowsError) { - return new WindowsError( - "MRU", - `Could not get OpenSave MRU entries: ${common}`, - ); - } - - const mrus: Mru[] = []; - - for (const entry of common) { - const open_save_mru: Mru = { - ntuser_path, - kind: MruType.OPENSAVE, - filename: entry.filename, - path: entry.path, - created: entry.created, - modified: entry.modified, - accessed: entry.accessed, - items: entry.items, - message: `MRU value: ${entry.path}`, - datetime: entry.created, - timestamp_desc: "MRU Entry Created", - artifact: "MRU Open Save", - data_type: "windows:registry:mru:entry" - }; - mrus.push(open_save_mru); - } - - const last_visit = lastVisitMru(reg_data); - if (last_visit instanceof WindowsError) { - return new WindowsError( - "MRU", - `Could not get LastVisited MRU entries: ${last_visit}`, - ); - } - - for (const entry of last_visit) { - const last_visit_mru: Mru = { - ntuser_path, - kind: MruType.OPENSAVE, - filename: entry.filename, - path: entry.path, - created: entry.created, - modified: entry.modified, - accessed: entry.accessed, - items: entry.items, - message: `MRU value: ${entry.path}`, - datetime: entry.created, - timestamp_desc: "MRU Entry Created", - artifact: "MRU Last Visit", - data_type: "windows:registry:mru:entry" - }; - mrus.push(last_visit_mru); - } - - const recent_docs = recentDocs(reg_data); - if (recent_docs instanceof WindowsError) { - return new WindowsError( - "MRU", - `Could not get RecentDocs MRU entries: ${recent_docs}`, - ); - } - - for (const entry of recent_docs) { - const recent_docs_mru: Mru = { - ntuser_path, - kind: MruType.OPENSAVE, - filename: entry.filename, - path: entry.path, - created: entry.created, - modified: entry.modified, - accessed: entry.accessed, - items: entry.items, - message: `MRU value: ${entry.path}`, - datetime: entry.created, - timestamp_desc: "MRU Entry Created", - artifact: "MRU Recent Docs", - data_type: "windows:registry:mru:entry" - }; - mrus.push(recent_docs_mru); - } - - return mrus; -} - -/** - * Assemble `ShellItems` into a MRU formatted entry - * @param items Array of `Shellitems` - * @returns Generic `MruValues` - */ -export function assembleMru(items: ShellItems[]): MruValues { - const paths: string[] = []; - - if (items.length === 0) { - return { - filename: "", - path: "", - modified: "1970-01-01T00:00:00.000Z", - created: "1970-01-01T00:00:00.000Z", - accessed: "1970-01-01T00:00:00.000Z", - items: [], - }; - } - - for (const item of items) { - paths.push(item.value.replaceAll("\\\\", "")); - } - // Get last entry - const item = items[items.length - 1]; - if (item === undefined) { - return { - filename: "", - path: "", - modified: "1970-01-01T00:00:00.000Z", - created: "1970-01-01T00:00:00.000Z", - accessed: "1970-01-01T00:00:00.000Z", - items: [], - }; - } - const entry: MruValues = { - filename: item.value, - path: paths.join("\\"), - modified: item.modified, - created: item.created, - accessed: item.accessed, - items, - }; - - return entry; -} - -/** - * Function to test Windows MRU parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows MRU parsing - */ -export function testParseMru(): void { - const test = "../../tests/test_data/windows/registry/NTUSER.DAT"; - const results = parseMru(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length != 0) { - throw `Got ${results.length} entries, expected 0.......parseMru ❌`; - } - - console.info(` Function parseMru ✅`); - - const open_save: Registry[] = [{ "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32", "name": "OpenSavePidlMRU", "values": [], "last_modified": "2025-08-30T22:59:19.000Z", "depth": 7, "security_offset": 69216, "registry_path": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }, { "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\*", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU", "name": "*", "values": [{ "value": "0", "data": "OgAfSUcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAACoIgXqOfHbAVOF/R9I8dsBIGUiJEjx2wEUAIYAdAAeAENGU0YYADEAAAAAAOpaGhIQAFByb2plY3RzAAAAAHQaWV6W39NIjWcXM7zuKLrFzfrfn2dWQYlHxcdrwLZ/QgAJAAQA777qWhQS6lrFGC4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAA8aNxAFAAcgBvAGoAZQBjAHQAcwAAAEQAVgAxAAAAAADqWvMUMABhcnRlbWlzAEAACQAEAO++6loaEupaxRguAAAA478AAAAAAwAAAAAAuAAAAAAAAAAAAILw7ABhAHIAdABlAG0AaQBzAAAAFgAAAA==", "data_type": "REG_BINARY" }, { "value": "MRUListEx", "data": "CQAAAAQAAAAOAAAADQAAAAwAAAALAAAACgAAAAAAAAAIAAAABwAAAAYAAAAFAAAAAwAAAAIAAAABAAAA/////w==", "data_type": "REG_BINARY" }, { "value": "1", "data": "OgAfSDrMv7Qs20xCsCl/6ZqHxkEmAAEAJgDvvhEAAADMXgjqOfHbAeFhw+xO8dsBWw5x8k7x2wEUAGAAMgAAMFwY6lpwHCAAV2luZG93cy5kYgAARgAJAAQA777qWlUf6lpWHy4AAAAUBgcAAAAVAAAAAAAAAAAAAAAAAAAA9oCZAFcAaQBuAGQAbwB3AHMALgBkAGIAAAAaAAAA", "data_type": "REG_BINARY" }, { "value": "2", "data": "OgAfSUcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAACoIgXqOfHbAVOF/R9I8dsBIGUiJEjx2wEUAIYAdAAeAENGU0YYADEAAAAAAOxaFKwQAFByb2plY3RzAAAAAHQaWV6W39NIjWcXM7zuKLrFzfrfn2dWQYlHxcdrwLZ/QgAJAAQA777qWhQS7FoUrC4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAAErkXAVAAcgBvAGoAZQBjAHQAcwAAAEQAbAAxAAAAAADsWhSsEABNQUNPUy1+MQAAVAAJAAQA777sWhSs7FoUrC4AAADVlwAAAAAGAAAAAAAAAAAAAAAAAAAASeiLAG0AYQBjAG8AcwAtAHUAbgBpAGYAaQBlAGQAbABvAGcAcwAAABgAAAA=", "data_type": "REG_BINARY" }], "last_modified": "2025-07-15T03:39:32.000Z", "depth": 8, "security_offset": 69216, "registry_path": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }, { "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\ts", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU", "name": "ts", "values": [{ "value": "0", "data": "OgAfAAU5jggjAwJLmCZdmUKOEV8mAAEAJgDvvjEAAADTNgjqOfHbAY1eTb0OCNwBH5rlEP0Z3AEUAFYAMgAAAAAAAAAAAIAAbWFpbi50cwBAAAkABADvvgAAAAAAAAAALgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbQBhAGkAbgAuAHQAcwAAABYAAAA=", "data_type": "REG_BINARY" }, { "value": "MRUListEx", "data": "AAAAAP////8=", "data_type": "REG_BINARY" }], "last_modified": "2025-08-30T22:59:19.000Z", "depth": 8, "security_offset": 69216, "registry_path": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }]; - const open_mru = openSaveMru(open_save); - if (open_mru instanceof WindowsError) { - throw open_mru; - } - - if (open_mru.length != 4 || open_mru[0] === undefined) { - throw `Got ${open_mru.length} entries, expected 4.......openSaveMru ❌`; - } - - if (open_mru[0].filename != "artemis") { - throw `Got ${open_mru[0].filename}, expected 'artemis'.......openSaveMru ❌`; - } - - console.info(` Function openSaveMru ✅`); - - const list_visit: Registry[] = [{ "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\LastVisitedPidlMRU", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32", "name": "LastVisitedPidlMRU", "values": [{ "value": "MRUListEx", "data": "AgAAAAAAAAAEAAAAAwAAAAEAAAD/////", "data_type": "REG_BINARY" }, { "value": "1", "data": "aQBtAGgAZQB4AC0AZwB1AGkALgBlAHgAZQAAABQAH1DgT9Ag6jppEKLYCAArMDCdGQAvQzpcAAAAAAAAAAAAAAAAAAAAAAAAAHgAMQAAAAAA6loKDhEAVXNlcnMAZAAJAAQA776BWEFF71p5Gi4AAADaEQAAAAABAAAAAAAAAAAAOgAAAAAAzMkTAFUAcwBlAHIAcwAAAEAAcwBoAGUAbABsADMAMgAuAGQAbABsACwALQAyADEAOAAxADMAAAAUAFAAMQAAAAAA61qBABAAYXp1cjMAPAAJAAQA777qWokL71p5Gi4AAACnCwIAAAACAAAAAAAAAAAAAAAAAAAA3UCjAGEAegB1AHIAMwAAABQAWgAxAAAAAADsWhSsEABQcm9qZWN0cwAAQgAJAAQA777qWhQS71p5Gi4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAAErkXAVAAcgBvAGoAZQBjAHQAcwAAABgAVgAxAAAAAADqWvMUMABhcnRlbWlzAEAACQAEAO++6loaEu9aeRouAAAA478AAAAAAwAAAAAAuAAAAAAAAAAAAILw7ABhAHIAdABlAG0AaQBzAAAAFgBcADEAAAAAAO9aNhkQAEZPUkVOU34xAABEAAkABADvvupaIhLvWnkaLgAAAII7BQAAAAEAAAAAAAAAAAAAAAAAAAC64qEAZgBvAHIAZQBuAHMAaQBjAHMAAAAYAFAAMQAAAAAA6lojEhAAdGVzdHMAPAAJAAQA777qWiIS71p5Gi4AAABlPgUAAAABAAAAAAAAAAAAAAAAAAAAO83cAHQAZQBzAHQAcwAAABQAXAAxAAAAAADqWiISEABURVNUX0R+MQAARAAJAAQA777qWiIS71p5Gi4AAACKPgUAAAABAAAAAAAAAAAAAAAAAAAAiic1AHQAZQBzAHQAXwBkAGEAdABhAAAAGABWADEAAAAAAOpaIxIQAHdpbmRvd3MAQAAJAAQA777qWiIS71p5Gi4AAAA4PwUAAAABAAAAAAAAAAAAAAAAAAAATjjdAHcAaQBuAGQAbwB3AHMAAAAWAFoAMQAAAAAA6lojEhAAcmVnaXN0cnkAAEIACQAEAO++6lojEu9adBouAAAAZkIFAAAAAQAAAAAAAAAAAAAAAAAAAOPeAgFyAGUAZwBpAHMAdAByAHkAAAAYAFAAMQAAAAAA6lojEhAAd2luMTAAPAAJAAQA777qWiMS71p0Gi4AAABnQgUAAAABAAAAAAAAAAAAAAAAAAAA9SzsAHcAaQBuADEAMAAAABQAAAA=", "data_type": "REG_BINARY" }, { "value": "3", "data": "UABpAGMAawBlAHIASABvAHMAdAAuAGUAeABlAAAAFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAAeAAxAAAAAADqWgoOEQBVc2VycwBkAAkABADvvoFYQUXwWoA2LgAAANoRAAAAAAEAAAAAAAAAAAA6AAAAAADMyRMAVQBzAGUAcgBzAAAAQABzAGgAZQBsAGwAMwAyAC4AZABsAGwALAAtADIAMQA4ADEAMwAAABQAUAAxAAAAAADwWiY2EABhenVyMwA8AAkABADvvupaiQvwWoI2LgAAAKcLAgAAAAIAAAAAAAAAAAAAAAAAAACtlOkAYQB6AHUAcgAzAAAAFABaADEAAAAAAOxaFKwQAFByb2plY3RzAABCAAkABADvvupaFBLwWoM2LgAAAOK/AAAAAAMAAAAAAAAAAAAAAAAAAAASuRcBUAByAG8AagBlAGMAdABzAAAAGABWADEAAAAAAPBagS4wAGFydGVtaXMAQAAJAAQA777qWhoS8FqFNi4AAADjvwAAAAADAAAAAAC4AAAAAAAAAAAAu79hAGEAcgB0AGUAbQBpAHMAAAAWAFQAMQAAAAAA8FqVNRAgdGFyZ2V0AAA+AAkABADvvvBagS7wWoc2LgAAAGQMAAAAAAwAAAAAAAAAAAAAAAAAAAAuTigAdABhAHIAZwBlAHQAAAAWAAAA", "data_type": "REG_BINARY" }, { "value": "4", "data": "TQBzAGkAeABQAGEAYwBrAGEAZwBlAFQAbwBvAGwALgBlAHgAZQAAABQAH1DgT9Ag6jppEKLYCAArMDCdGQAvQzpcAAAAAAAAAAAAAAAAAAAAAAAAAHgAMQAAAAAA6loKDhEAVXNlcnMAZAAJAAQA776BWEFF81qmti4AAADaEQAAAAABAAAAAAAAAAAAOgAAAAAAzMkTAFUAcwBlAHIAcwAAAEAAcwBoAGUAbABsADMAMgAuAGQAbABsACwALQAyADEAOAAxADMAAAAUAFAAMQAAAAAA81rDshAAYXp1cjMAPAAJAAQA777qWokL81rRui4AAACnCwIAAAACAAAAAAAAAAAAAAAAAAAAR2ogAGEAegB1AHIAMwAAABQAWgAxAAAAAADzWsmuEABQcm9qZWN0cwAAQgAJAAQA777qWhQS81prui4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAAVCnLAFAAcgBvAGoAZQBjAHQAcwAAABgAVgAxAAAAAADzWnKuMABhcnRlbWlzAEAACQAEAO++6loaEvNacq4uAAAA478AAAAAAwAAAAAAuAAAAAAAAAAAADGFvwBhAHIAdABlAG0AaQBzAAAAFgBUADEAAAAAAPBalTUQIHRhcmdldAAAPgAJAAQA777wWoEu8FpNOC4AAABkDAAAAAAMAAAAAAAAAAAAAAAAAAAALk4oAHQAYQByAGcAZQB0AAAAFgBWADEAAAAAAPBaxjUwIHJlbGVhc2UAQAAJAAQA777wWoEu8FpNOC4AAAAskAAAAAD1AAAAAAC4AAAAAAAAAAAAIZ3AAHIAZQBsAGUAYQBzAGUAAAAWAAAA", "data_type": "REG_BINARY" }, { "value": "0", "data": "VgBTAEMAbwBkAGkAdQBtAC4AZQB4AGUAAAA6AB8ABTmOCCMDAkuYJl2ZQo4RXyYAAQAmAO++MQAAANM2COo58dsBjV5NvQ4I3AEfmuUQ/RncARQAAAA=", "data_type": "REG_BINARY" }, { "value": "2", "data": "UgBlAGcAaQBzAHQAcgB5AEUAeABwAGwAbwByAGUAcgAuAGUAeABlAAAAFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAAeAAxAAAAAADqWgoOEQBVc2VycwBkAAkABADvvoFYQUU0WySmLgAAANoRAAAAAAEAAAAAAAAAAAA6AAAAAADMyRMAVQBzAGUAcgBzAAAAQABzAGgAZQBsAGwAMwAyAC4AZABsAGwALAAtADIAMQA4ADEAMwAAABQAUAAxAAAAAAA0W519EABhenVyMwA8AAkABADvvupaiQs0WySmLgAAAKcLAgAAAAIAAAAAAAAAAAAAAAAAAADtvQMBYQB6AHUAcgAzAAAAFABaADEAAAAAAB5b7bMQAFByb2plY3RzAABCAAkABADvvupaFBI0W8SkLgAAAOK/AAAAAAMAAAAAAAAAAAAAAAAAAAAfnpsAUAByAG8AagBlAGMAdABzAAAAGABWADEAAAAAADRbFXkwAGFydGVtaXMAQAAJAAQA777qWhoSNFvJpC4AAADjvwAAAAADAAAAAACsAAAAAAAAAAAA0M9zAGEAcgB0AGUAbQBpAHMAAAAWAFwAMQAAAAAANFtnpBAARk9SRU5TfjEAAEQACQAEAO++6loiEjRb1qQuAAAAgjsFAAAAAQAAAAAAAAAAAAAAAAAAAFK37QBmAG8AcgBlAG4AcwBpAGMAcwAAABgAUAAxAAAAAADzWm6uEAB0ZXN0cwA8AAkABADvvupaIhI0W9akLgAAAGU+BQAAAAEAAAAAAAAAAAAAAAAAAAAkHnUAdABlAHMAdABzAAAAFABcADEAAAAAAB5b07MQAFRFU1RfRH4xAABEAAkABADvvupaIhI0W9akLgAAAIo+BQAAAAEAAAAAAAAAAAAAAAAAAACIlQgAdABlAHMAdABfAGQAYQB0AGEAAAAYAFYAMQAAAAAA6lojEhAAd2luZG93cwBAAAkABADvvupaIhI0WzagLgAAADg/BQAAAAEAAAAAAAAAAAAAAAAAAABOON0AdwBpAG4AZABvAHcAcwAAABYAWgAxAAAAAADqWiMSEAByZWdpc3RyeQAAQgAJAAQA777qWiMSNFvWpC4AAABmQgUAAAABAAAAAAAAAAAAAAAAAAAA494CAXIAZQBnAGkAcwB0AHIAeQAAABgAUAAxAAAAAADwWmYuEAB3aW4xMAA8AAkABADvvupaIxI0W9akLgAAAGdCBQAAAAEAAAAAAAAAAAAAAAAAAAB3QK0AdwBpAG4AMQAwAAAAFAAAAA==", "data_type": "REG_BINARY" }], "last_modified": "2025-09-20T21:00:12.000Z", "depth": 7, "security_offset": 69216, "registry_path": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }]; - const visit_mru = lastVisitMru(list_visit); - if (visit_mru instanceof WindowsError) { - throw visit_mru; - } - - if (visit_mru.length != 5 || visit_mru[2] === undefined) { - throw `Got ${visit_mru.length} entries, expected 5.......lastVisitMru ❌`; - } - - if (visit_mru[2].filename != "MsixPackageTool.exe") { - throw `Got ${visit_mru[2].filename}, expected 'MsixPackageTool.exe'.......lastVisitMru ❌`; - } - - console.info(` Function lastVisitMru ✅`); - - const recent_mru: Registry[] = [{ "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", "name": "RecentDocs", "values": [], "last_modified": "2025-07-19T21:56:37.000Z", "depth": 6, "security_offset": 69216, "registry_path": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }]; - const recent_values = recentDocs(recent_mru); - if (recent_values instanceof WindowsError) { - throw recent_values; - } - - if (recent_values.length != 0) { - throw `Got ${recent_values.length} entries, expected 0.......recentDocs ❌`; - } - - console.info(` Function recentDocs ✅`); - - const item: ShellItems[] = [{ "value": "59031a47-3f72-44a7-89c5-5595fe6b30ee", "shell_type": "RootFolder", "created": "1970-01-01T00:00:00.000Z", "modified": "1970-01-01T00:00:00.000Z", "accessed": "1970-01-01T00:00:00.000Z", "mft_entry": 0, "mft_sequence": 0, "stores": {} }, { "value": "Projects", "shell_type": "Delegate", "created": "2025-07-10T02:16:40.000Z", "modified": "2025-07-10T02:16:52.000Z", "accessed": "2025-07-10T03:06:10.000Z", "mft_entry": 49122, "mft_sequence": 3, "stores": {} }, { "value": "artemis", "shell_type": "Directory", "created": "2025-07-10T02:16:52.000Z", "modified": "2025-07-10T02:39:38.000Z", "accessed": "2025-07-10T03:06:10.000Z", "mft_entry": 49123, "mft_sequence": 3, "stores": {} }]; - const items = assembleMru(item); - if (items.created != "2025-07-10T02:16:52.000Z") { - throw `Got ${items.created}, expected 2025-07-10T02:16:52.000Z.......assembleMru ❌`; - - } - - console.info(` Function assembleMru ✅`); +import { Registry } from "../../../types/windows/registry"; +import { + Mru, + MruType, + MruValues, +} from "../../../types/windows/registry/recently_used"; +import { ShellItems } from "../../../types/windows/shellitems"; +import { WindowsError } from "../errors"; +import { getRegistry } from "../registry"; +import { lastVisitMru, openSaveMru } from "./mru/common"; +import { recentDocs } from "./mru/recent_docs"; + +/** + * Parse common Most Recently Used (MRU) Registry keys + * @param ntuser_path Path to NTUSER.DAT file + * @returns Array of common `Mru` entries or `WindowsError` + */ +export function parseMru(ntuser_path: string): Mru[] | WindowsError { + const reg_data = getRegistry(ntuser_path); + if (reg_data instanceof WindowsError) { + return new WindowsError( + "MRU", + `Could not parse Registry ${ntuser_path}: ${reg_data}`, + ); + } + + const common = openSaveMru(reg_data); + if (common instanceof WindowsError) { + return new WindowsError( + "MRU", + `Could not get OpenSave MRU entries: ${common}`, + ); + } + + const mrus: Mru[] = []; + + for (const entry of common) { + const open_save_mru: Mru = { + kind: MruType.OPENSAVE, + filename: entry.filename, + path: entry.path, + created: entry.created, + modified: entry.modified, + accessed: entry.accessed, + items: entry.items, + message: `MRU value: ${entry.path}`, + datetime: entry.created, + timestamp_desc: "MRU Entry Created", + artifact: "MRU Open Save", + data_type: "windows:registry:mru:entry", + evidence: ntuser_path, + }; + mrus.push(open_save_mru); + } + + const last_visit = lastVisitMru(reg_data); + if (last_visit instanceof WindowsError) { + return new WindowsError( + "MRU", + `Could not get LastVisited MRU entries: ${last_visit}`, + ); + } + + for (const entry of last_visit) { + const last_visit_mru: Mru = { + kind: MruType.OPENSAVE, + filename: entry.filename, + path: entry.path, + created: entry.created, + modified: entry.modified, + accessed: entry.accessed, + items: entry.items, + message: `MRU value: ${entry.path}`, + datetime: entry.created, + timestamp_desc: "MRU Entry Created", + artifact: "MRU Last Visit", + data_type: "windows:registry:mru:entry", + evidence: ntuser_path, + }; + mrus.push(last_visit_mru); + } + + const recent_docs = recentDocs(reg_data); + if (recent_docs instanceof WindowsError) { + return new WindowsError( + "MRU", + `Could not get RecentDocs MRU entries: ${recent_docs}`, + ); + } + + for (const entry of recent_docs) { + const recent_docs_mru: Mru = { + evidence: ntuser_path, + kind: MruType.OPENSAVE, + filename: entry.filename, + path: entry.path, + created: entry.created, + modified: entry.modified, + accessed: entry.accessed, + items: entry.items, + message: `MRU value: ${entry.path}`, + datetime: entry.created, + timestamp_desc: "MRU Entry Created", + artifact: "MRU Recent Docs", + data_type: "windows:registry:mru:entry" + }; + mrus.push(recent_docs_mru); + } + + return mrus; +} + +/** + * Assemble `ShellItems` into a MRU formatted entry + * @param items Array of `Shellitems` + * @returns Generic `MruValues` + */ +export function assembleMru(items: ShellItems[]): MruValues { + const paths: string[] = []; + + if (items.length === 0) { + return { + filename: "", + path: "", + modified: "1970-01-01T00:00:00.000Z", + created: "1970-01-01T00:00:00.000Z", + accessed: "1970-01-01T00:00:00.000Z", + items: [], + }; + } + + for (const item of items) { + paths.push(item.value.replaceAll("\\\\", "")); + } + // Get last entry + const item = items[items.length - 1]; + if (item === undefined) { + return { + filename: "", + path: "", + modified: "1970-01-01T00:00:00.000Z", + created: "1970-01-01T00:00:00.000Z", + accessed: "1970-01-01T00:00:00.000Z", + items: [], + }; + } + const entry: MruValues = { + filename: item.value, + path: paths.join("\\"), + modified: item.modified, + created: item.created, + accessed: item.accessed, + items, + }; + + return entry; +} + +/** + * Function to test Windows MRU parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows MRU parsing + */ +export function testParseMru(): void { + const test = "../../tests/test_data/windows/registry/NTUSER.DAT"; + const results = parseMru(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length != 0) { + throw `Got ${results.length} entries, expected 0.......parseMru ❌`; + } + + console.info(` Function parseMru ✅`); + + const open_save: Registry[] = [{ "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32", "name": "OpenSavePidlMRU", "values": [], "last_modified": "2025-08-30T22:59:19.000Z", "depth": 7, "security_offset": 69216, "evidence": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }, { "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\*", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU", "name": "*", "values": [{ "value": "0", "data": "OgAfSUcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAACoIgXqOfHbAVOF/R9I8dsBIGUiJEjx2wEUAIYAdAAeAENGU0YYADEAAAAAAOpaGhIQAFByb2plY3RzAAAAAHQaWV6W39NIjWcXM7zuKLrFzfrfn2dWQYlHxcdrwLZ/QgAJAAQA777qWhQS6lrFGC4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAA8aNxAFAAcgBvAGoAZQBjAHQAcwAAAEQAVgAxAAAAAADqWvMUMABhcnRlbWlzAEAACQAEAO++6loaEupaxRguAAAA478AAAAAAwAAAAAAuAAAAAAAAAAAAILw7ABhAHIAdABlAG0AaQBzAAAAFgAAAA==", "data_type": "REG_BINARY" }, { "value": "MRUListEx", "data": "CQAAAAQAAAAOAAAADQAAAAwAAAALAAAACgAAAAAAAAAIAAAABwAAAAYAAAAFAAAAAwAAAAIAAAABAAAA/////w==", "data_type": "REG_BINARY" }, { "value": "1", "data": "OgAfSDrMv7Qs20xCsCl/6ZqHxkEmAAEAJgDvvhEAAADMXgjqOfHbAeFhw+xO8dsBWw5x8k7x2wEUAGAAMgAAMFwY6lpwHCAAV2luZG93cy5kYgAARgAJAAQA777qWlUf6lpWHy4AAAAUBgcAAAAVAAAAAAAAAAAAAAAAAAAA9oCZAFcAaQBuAGQAbwB3AHMALgBkAGIAAAAaAAAA", "data_type": "REG_BINARY" }, { "value": "2", "data": "OgAfSUcaA1lyP6dEicVVlf5rMO4mAAEAJgDvvhAAAACoIgXqOfHbAVOF/R9I8dsBIGUiJEjx2wEUAIYAdAAeAENGU0YYADEAAAAAAOxaFKwQAFByb2plY3RzAAAAAHQaWV6W39NIjWcXM7zuKLrFzfrfn2dWQYlHxcdrwLZ/QgAJAAQA777qWhQS7FoUrC4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAAErkXAVAAcgBvAGoAZQBjAHQAcwAAAEQAbAAxAAAAAADsWhSsEABNQUNPUy1+MQAAVAAJAAQA777sWhSs7FoUrC4AAADVlwAAAAAGAAAAAAAAAAAAAAAAAAAASeiLAG0AYQBjAG8AcwAtAHUAbgBpAGYAaQBlAGQAbABvAGcAcwAAABgAAAA=", "data_type": "REG_BINARY" }], "last_modified": "2025-07-15T03:39:32.000Z", "depth": 8, "security_offset": 69216, "evidence": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }, { "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\ts", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU", "name": "ts", "values": [{ "value": "0", "data": "OgAfAAU5jggjAwJLmCZdmUKOEV8mAAEAJgDvvjEAAADTNgjqOfHbAY1eTb0OCNwBH5rlEP0Z3AEUAFYAMgAAAAAAAAAAAIAAbWFpbi50cwBAAAkABADvvgAAAAAAAAAALgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbQBhAGkAbgAuAHQAcwAAABYAAAA=", "data_type": "REG_BINARY" }, { "value": "MRUListEx", "data": "AAAAAP////8=", "data_type": "REG_BINARY" }], "last_modified": "2025-08-30T22:59:19.000Z", "depth": 8, "security_offset": 69216, "evidence": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }]; + const open_mru = openSaveMru(open_save); + if (open_mru instanceof WindowsError) { + throw open_mru; + } + + if (open_mru.length != 4 || open_mru[0] === undefined) { + throw `Got ${open_mru.length} entries, expected 4.......openSaveMru ❌`; + } + + if (open_mru[0].filename != "artemis") { + throw `Got ${open_mru[0].filename}, expected 'artemis'.......openSaveMru ❌`; + } + + console.info(` Function openSaveMru ✅`); + + const list_visit: Registry[] = [{ "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\LastVisitedPidlMRU", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32", "name": "LastVisitedPidlMRU", "values": [{ "value": "MRUListEx", "data": "AgAAAAAAAAAEAAAAAwAAAAEAAAD/////", "data_type": "REG_BINARY" }, { "value": "1", "data": "aQBtAGgAZQB4AC0AZwB1AGkALgBlAHgAZQAAABQAH1DgT9Ag6jppEKLYCAArMDCdGQAvQzpcAAAAAAAAAAAAAAAAAAAAAAAAAHgAMQAAAAAA6loKDhEAVXNlcnMAZAAJAAQA776BWEFF71p5Gi4AAADaEQAAAAABAAAAAAAAAAAAOgAAAAAAzMkTAFUAcwBlAHIAcwAAAEAAcwBoAGUAbABsADMAMgAuAGQAbABsACwALQAyADEAOAAxADMAAAAUAFAAMQAAAAAA61qBABAAYXp1cjMAPAAJAAQA777qWokL71p5Gi4AAACnCwIAAAACAAAAAAAAAAAAAAAAAAAA3UCjAGEAegB1AHIAMwAAABQAWgAxAAAAAADsWhSsEABQcm9qZWN0cwAAQgAJAAQA777qWhQS71p5Gi4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAAErkXAVAAcgBvAGoAZQBjAHQAcwAAABgAVgAxAAAAAADqWvMUMABhcnRlbWlzAEAACQAEAO++6loaEu9aeRouAAAA478AAAAAAwAAAAAAuAAAAAAAAAAAAILw7ABhAHIAdABlAG0AaQBzAAAAFgBcADEAAAAAAO9aNhkQAEZPUkVOU34xAABEAAkABADvvupaIhLvWnkaLgAAAII7BQAAAAEAAAAAAAAAAAAAAAAAAAC64qEAZgBvAHIAZQBuAHMAaQBjAHMAAAAYAFAAMQAAAAAA6lojEhAAdGVzdHMAPAAJAAQA777qWiIS71p5Gi4AAABlPgUAAAABAAAAAAAAAAAAAAAAAAAAO83cAHQAZQBzAHQAcwAAABQAXAAxAAAAAADqWiISEABURVNUX0R+MQAARAAJAAQA777qWiIS71p5Gi4AAACKPgUAAAABAAAAAAAAAAAAAAAAAAAAiic1AHQAZQBzAHQAXwBkAGEAdABhAAAAGABWADEAAAAAAOpaIxIQAHdpbmRvd3MAQAAJAAQA777qWiIS71p5Gi4AAAA4PwUAAAABAAAAAAAAAAAAAAAAAAAATjjdAHcAaQBuAGQAbwB3AHMAAAAWAFoAMQAAAAAA6lojEhAAcmVnaXN0cnkAAEIACQAEAO++6lojEu9adBouAAAAZkIFAAAAAQAAAAAAAAAAAAAAAAAAAOPeAgFyAGUAZwBpAHMAdAByAHkAAAAYAFAAMQAAAAAA6lojEhAAd2luMTAAPAAJAAQA777qWiMS71p0Gi4AAABnQgUAAAABAAAAAAAAAAAAAAAAAAAA9SzsAHcAaQBuADEAMAAAABQAAAA=", "data_type": "REG_BINARY" }, { "value": "3", "data": "UABpAGMAawBlAHIASABvAHMAdAAuAGUAeABlAAAAFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAAeAAxAAAAAADqWgoOEQBVc2VycwBkAAkABADvvoFYQUXwWoA2LgAAANoRAAAAAAEAAAAAAAAAAAA6AAAAAADMyRMAVQBzAGUAcgBzAAAAQABzAGgAZQBsAGwAMwAyAC4AZABsAGwALAAtADIAMQA4ADEAMwAAABQAUAAxAAAAAADwWiY2EABhenVyMwA8AAkABADvvupaiQvwWoI2LgAAAKcLAgAAAAIAAAAAAAAAAAAAAAAAAACtlOkAYQB6AHUAcgAzAAAAFABaADEAAAAAAOxaFKwQAFByb2plY3RzAABCAAkABADvvupaFBLwWoM2LgAAAOK/AAAAAAMAAAAAAAAAAAAAAAAAAAASuRcBUAByAG8AagBlAGMAdABzAAAAGABWADEAAAAAAPBagS4wAGFydGVtaXMAQAAJAAQA777qWhoS8FqFNi4AAADjvwAAAAADAAAAAAC4AAAAAAAAAAAAu79hAGEAcgB0AGUAbQBpAHMAAAAWAFQAMQAAAAAA8FqVNRAgdGFyZ2V0AAA+AAkABADvvvBagS7wWoc2LgAAAGQMAAAAAAwAAAAAAAAAAAAAAAAAAAAuTigAdABhAHIAZwBlAHQAAAAWAAAA", "data_type": "REG_BINARY" }, { "value": "4", "data": "TQBzAGkAeABQAGEAYwBrAGEAZwBlAFQAbwBvAGwALgBlAHgAZQAAABQAH1DgT9Ag6jppEKLYCAArMDCdGQAvQzpcAAAAAAAAAAAAAAAAAAAAAAAAAHgAMQAAAAAA6loKDhEAVXNlcnMAZAAJAAQA776BWEFF81qmti4AAADaEQAAAAABAAAAAAAAAAAAOgAAAAAAzMkTAFUAcwBlAHIAcwAAAEAAcwBoAGUAbABsADMAMgAuAGQAbABsACwALQAyADEAOAAxADMAAAAUAFAAMQAAAAAA81rDshAAYXp1cjMAPAAJAAQA777qWokL81rRui4AAACnCwIAAAACAAAAAAAAAAAAAAAAAAAAR2ogAGEAegB1AHIAMwAAABQAWgAxAAAAAADzWsmuEABQcm9qZWN0cwAAQgAJAAQA777qWhQS81prui4AAADivwAAAAADAAAAAAAAAAAAAAAAAAAAVCnLAFAAcgBvAGoAZQBjAHQAcwAAABgAVgAxAAAAAADzWnKuMABhcnRlbWlzAEAACQAEAO++6loaEvNacq4uAAAA478AAAAAAwAAAAAAuAAAAAAAAAAAADGFvwBhAHIAdABlAG0AaQBzAAAAFgBUADEAAAAAAPBalTUQIHRhcmdldAAAPgAJAAQA777wWoEu8FpNOC4AAABkDAAAAAAMAAAAAAAAAAAAAAAAAAAALk4oAHQAYQByAGcAZQB0AAAAFgBWADEAAAAAAPBaxjUwIHJlbGVhc2UAQAAJAAQA777wWoEu8FpNOC4AAAAskAAAAAD1AAAAAAC4AAAAAAAAAAAAIZ3AAHIAZQBsAGUAYQBzAGUAAAAWAAAA", "data_type": "REG_BINARY" }, { "value": "0", "data": "VgBTAEMAbwBkAGkAdQBtAC4AZQB4AGUAAAA6AB8ABTmOCCMDAkuYJl2ZQo4RXyYAAQAmAO++MQAAANM2COo58dsBjV5NvQ4I3AEfmuUQ/RncARQAAAA=", "data_type": "REG_BINARY" }, { "value": "2", "data": "UgBlAGcAaQBzAHQAcgB5AEUAeABwAGwAbwByAGUAcgAuAGUAeABlAAAAFAAfUOBP0CDqOmkQotgIACswMJ0ZAC9DOlwAAAAAAAAAAAAAAAAAAAAAAAAAeAAxAAAAAADqWgoOEQBVc2VycwBkAAkABADvvoFYQUU0WySmLgAAANoRAAAAAAEAAAAAAAAAAAA6AAAAAADMyRMAVQBzAGUAcgBzAAAAQABzAGgAZQBsAGwAMwAyAC4AZABsAGwALAAtADIAMQA4ADEAMwAAABQAUAAxAAAAAAA0W519EABhenVyMwA8AAkABADvvupaiQs0WySmLgAAAKcLAgAAAAIAAAAAAAAAAAAAAAAAAADtvQMBYQB6AHUAcgAzAAAAFABaADEAAAAAAB5b7bMQAFByb2plY3RzAABCAAkABADvvupaFBI0W8SkLgAAAOK/AAAAAAMAAAAAAAAAAAAAAAAAAAAfnpsAUAByAG8AagBlAGMAdABzAAAAGABWADEAAAAAADRbFXkwAGFydGVtaXMAQAAJAAQA777qWhoSNFvJpC4AAADjvwAAAAADAAAAAACsAAAAAAAAAAAA0M9zAGEAcgB0AGUAbQBpAHMAAAAWAFwAMQAAAAAANFtnpBAARk9SRU5TfjEAAEQACQAEAO++6loiEjRb1qQuAAAAgjsFAAAAAQAAAAAAAAAAAAAAAAAAAFK37QBmAG8AcgBlAG4AcwBpAGMAcwAAABgAUAAxAAAAAADzWm6uEAB0ZXN0cwA8AAkABADvvupaIhI0W9akLgAAAGU+BQAAAAEAAAAAAAAAAAAAAAAAAAAkHnUAdABlAHMAdABzAAAAFABcADEAAAAAAB5b07MQAFRFU1RfRH4xAABEAAkABADvvupaIhI0W9akLgAAAIo+BQAAAAEAAAAAAAAAAAAAAAAAAACIlQgAdABlAHMAdABfAGQAYQB0AGEAAAAYAFYAMQAAAAAA6lojEhAAd2luZG93cwBAAAkABADvvupaIhI0WzagLgAAADg/BQAAAAEAAAAAAAAAAAAAAAAAAABOON0AdwBpAG4AZABvAHcAcwAAABYAWgAxAAAAAADqWiMSEAByZWdpc3RyeQAAQgAJAAQA777qWiMSNFvWpC4AAABmQgUAAAABAAAAAAAAAAAAAAAAAAAA494CAXIAZQBnAGkAcwB0AHIAeQAAABgAUAAxAAAAAADwWmYuEAB3aW4xMAA8AAkABADvvupaIxI0W9akLgAAAGdCBQAAAAEAAAAAAAAAAAAAAAAAAAB3QK0AdwBpAG4AMQAwAAAAFAAAAA==", "data_type": "REG_BINARY" }], "last_modified": "2025-09-20T21:00:12.000Z", "depth": 7, "security_offset": 69216, "evidence": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }]; + const visit_mru = lastVisitMru(list_visit); + if (visit_mru instanceof WindowsError) { + throw visit_mru; + } + + if (visit_mru.length != 5 || visit_mru[2] === undefined) { + throw `Got ${visit_mru.length} entries, expected 5.......lastVisitMru ❌`; + } + + if (visit_mru[2].filename != "MsixPackageTool.exe") { + throw `Got ${visit_mru[2].filename}, expected 'MsixPackageTool.exe'.......lastVisitMru ❌`; + } + + console.info(` Function lastVisitMru ✅`); + + const recent_mru: Registry[] = [{ "path": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs", "key": "ROOT\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer", "name": "RecentDocs", "values": [], "last_modified": "2025-07-19T21:56:37.000Z", "depth": 6, "security_offset": 69216, "evidence": "C:\\Users\\azur3\\NTUSER.dat", "registry_file": "NTUSER.dat" }]; + const recent_values = recentDocs(recent_mru); + if (recent_values instanceof WindowsError) { + throw recent_values; + } + + if (recent_values.length != 0) { + throw `Got ${recent_values.length} entries, expected 0.......recentDocs ❌`; + } + + console.info(` Function recentDocs ✅`); + + const item: ShellItems[] = [{ "value": "59031a47-3f72-44a7-89c5-5595fe6b30ee", "shell_type": "RootFolder", "created": "1970-01-01T00:00:00.000Z", "modified": "1970-01-01T00:00:00.000Z", "accessed": "1970-01-01T00:00:00.000Z", "mft_entry": 0, "mft_sequence": 0, "stores": {} }, { "value": "Projects", "shell_type": "Delegate", "created": "2025-07-10T02:16:40.000Z", "modified": "2025-07-10T02:16:52.000Z", "accessed": "2025-07-10T03:06:10.000Z", "mft_entry": 49122, "mft_sequence": 3, "stores": {} }, { "value": "artemis", "shell_type": "Directory", "created": "2025-07-10T02:16:52.000Z", "modified": "2025-07-10T02:39:38.000Z", "accessed": "2025-07-10T03:06:10.000Z", "mft_entry": 49123, "mft_sequence": 3, "stores": {} }]; + const items = assembleMru(item); + if (items.created != "2025-07-10T02:16:52.000Z") { + throw `Got ${items.created}, expected 2025-07-10T02:16:52.000Z.......assembleMru ❌`; + + } + + console.info(` Function assembleMru ✅`); } \ No newline at end of file diff --git a/src/windows/registry/run.ts b/src/windows/registry/run.ts index abb648ed..122608b9 100644 --- a/src/windows/registry/run.ts +++ b/src/windows/registry/run.ts @@ -1,241 +1,248 @@ -import { Hashes } from "../../../types/filesystem/files"; -import { Registry } from "../../../types/windows/registry"; -import { RegistryRunKey } from "../../../types/windows/registry/run"; -import { getEnvValue, getSystemDrive } from "../../environment/env"; -import { FileError } from "../../filesystem/errors"; -import { glob, hash, stat } from "../../filesystem/files"; -import { WindowsError } from "../errors"; -import { getPe } from "../pe"; -import { getRegistry } from "../registry"; - -/** - * Function to extract Registry Run key information - * @param alt_path Optional alternative path to a Registry file - * @returns Array of `RegistryRunKey` - */ -export function getRunKeys(alt_path?: string): RegistryRunKey[] { - let drive = getSystemDrive(); - if (drive === "") { - drive = "C:"; - } - let paths = [`${drive}\\Users\\*\\NTUSER.DAT`, `${drive}\\Users\\*\\NTUSER.dat`, `${drive}\\Users\\*\\ntuser.dat`, `${drive}\\Windows\\System32\\config\\SOFTWARE`]; - if (alt_path !== undefined) { - paths = [alt_path]; - } - - const glob_paths: string[] = []; - // Try to get all the Registry files that Run keys for persistence - for (const path of paths) { - const results = glob(path); - if (results instanceof FileError) { - continue; - } - for (const entry of results) { - if (!entry.is_file) { - continue; - } - glob_paths.push(entry.full_path); - } - } - - let run_values: RegistryRunKey[] = []; - // Now parse each Registry file - for (const entry of glob_paths) { - const results = getRegistry(entry, ".*CurrentVersion\\\\Run.*"); - if (results instanceof WindowsError) { - continue; - } - - for (const reg of results) { - const values = parseKeys(reg); - run_values = run_values.concat(values); - } - } - - return run_values; -} - -/** - * Function to extract Run key information - * @param value `Registry` object associated with a Run - * @returns Array of `RegistryRunKey` - */ -function parseKeys(value: Registry): RegistryRunKey[] { - const values: RegistryRunKey[] = []; - for (const entry of value.values) { - const run_value: RegistryRunKey = { - key_modified: value.last_modified, - key_path: value.path, - registry_path: value.registry_path, - registry_file: value.registry_file, - path: "", - created: "", - has_signature: false, - md5: "", - value: entry.data, - name: entry.value, - sha1: "", - sha256: "", - message: `Run key: ${value.name}`, - datetime: value.last_modified, - timestamp_desc: "Registry Last Modified", - artifact: "Windows Registry Run Key", - data_type: "windows:registry:run:entry" - }; - - // Try to get some metadata about the Run key values - if (entry.data.startsWith("%")) { - // Environment values - const env = /%.*?%/g; - for (const entry_match of entry.data.match(env) ?? []) { - const real_value = getEnvValue(entry_match.replaceAll("%", "")); - let path = entry.data.replace(entry_match, real_value); - if(path.includes("/")) { - path = path.split("/").at(0)?.trimEnd() ?? path; - } - const created = getCreation(path); - run_value.created = created; - - const hashes = getHash(path); - run_value.md5 = hashes.md5; - run_value.sha1 = hashes.sha1; - run_value.sha256 = hashes.sha256; - - run_value.has_signature = getPeInfo(path); - run_value.path = path; - - values.push(run_value); - break; - } - } else if (entry.data.startsWith("\"")) { - // Files with cli args - const cmd = /".*?"/; - for (const entry_match of entry.data.match(cmd) ?? []) { - const path = entry_match.replaceAll("\"", ""); - const created = getCreation(path); - run_value.created = created; - - const hashes = getHash(path); - run_value.md5 = hashes.md5; - run_value.sha1 = hashes.sha1; - run_value.sha256 = hashes.sha256; - - run_value.has_signature = getPeInfo(path); - run_value.path = path; - - values.push(run_value); - break; - } - } else if (entry.data.includes("/")) { - // Files with clie args with forward slashes - const file = entry.data.split("/").at(0); - if (file === undefined) { - values.push(run_value); - continue; - } - const path = file.replaceAll("\"", "").trimEnd(); - const created = getCreation(path); - run_value.created = created; - - const hashes = getHash(path); - run_value.md5 = hashes.md5; - run_value.sha1 = hashes.sha1; - run_value.sha256 = hashes.sha256; - - run_value.has_signature = getPeInfo(path); - run_value.path = path; - - values.push(run_value); - } - } - - return values; -} - -/** - * Function to pull the created timestamp for a file - * @param path Path to a file - * @returns Standard created timestamp - */ -function getCreation(path: string): string { - const meta = stat(path); - if (meta instanceof FileError) { - return "1970-01-01T00:00:00.00Z"; - } - return meta.created; -} - -/** - * Function to hash a file associated with the Registry Run key - * @param path Path to a file to hash - * @returns `Hashes` object - */ -function getHash(path: string): Hashes { - const hashes = hash(path, true, true, true); - if (hashes instanceof FileError) { - return { - md5: "", - sha1: "", - sha256: "" - }; - } - return hashes; -} - -/** - * Function to check if PE file has certificate info - * @param path Path to a PE file - * @returns `true` if signed. `false` if not signed - */ -function getPeInfo(path: string): boolean { - const info = getPe(path); - if (info instanceof WindowsError) { - return false; - } - - return info.cert.length !== 0; -} - -/** - * Function to test Windows Registry Run key parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows Registry Run key parsing - */ -export function testGetRunKeys(): void { - const test = "../../tests/test_data/windows/registry/NTUSER.DAT"; - const results = getRunKeys(test); - if (results instanceof WindowsError) { - throw results; - } - - if (results.length !== 2) { - throw `Got ${results.length} expected 2.......getRunKeys ❌` - } - - if (results[1]?.value !== "%ProgramFiles%\\Windows Mail\\wab.exe /Upgrade") { - throw `Got ${results[1]?.value} expected "%ProgramFiles%\\\\Windows Mail\\\\wab.exe /Upgrade".......getRunKeys ❌` - } - - console.info(` Function getRunKeys ✅`); - - - const created = getCreation("C:\\Windows\\notepad.exe"); - if (created === "1970-01-01T00:00:00.00Z") { - throw 'Got bad time.......getCreation ❌' - } - console.info(` Function getCreation ✅`); - - - const hash = getHash("C:\\Windows\\notepad.exe"); - if (hash.md5 === "") { - throw 'Got empty hash.......getHash ❌' - } - console.info(` Function getHash ✅`); - - const info = getPeInfo("C:\\Windows\\notepad.exe"); - if (info === true) { - throw 'Got signature for notepad? Should be in Catalog?.......getPeInfo ❌' - } - console.info(` Function getPeInfo ✅`); - +import { Hashes } from "../../../types/filesystem/files"; +import { Registry } from "../../../types/windows/registry"; +import { RegistryRunKey } from "../../../types/windows/registry/run"; +import { getEnvValue, getSystemDrive } from "../../environment/env"; +import { FileError } from "../../filesystem/errors"; +import { glob, hash, stat } from "../../filesystem/files"; +import { WindowsError } from "../errors"; +import { getPe } from "../pe"; +import { getRegistry } from "../registry"; + +/** + * Function to extract Registry Run key information + * @param alt_path Optional alternative path to a Registry file + * @returns Array of `RegistryRunKey` + */ +export function getRunKeys(alt_path?: string): RegistryRunKey[] { + let drive = getSystemDrive(); + if (drive === "") { + drive = "C:"; + } + let paths = [`${drive}\\Users\\*\\NTUSER.*`, `${drive}\\Windows\\System32\\config\\SOFTWARE`]; + if (alt_path !== undefined) { + paths = [alt_path]; + } + + const glob_paths: string[] = []; + // Try to get all the Registry files that Run keys for persistence + for (const path of paths) { + const results = glob(path); + if (results instanceof FileError) { + continue; + } + for (const entry of results) { + if (!entry.is_file) { + continue; + } + glob_paths.push(entry.full_path); + } + } + + let run_values: RegistryRunKey[] = []; + // Now parse each Registry file + for (const entry of glob_paths) { + if (entry.toLowerCase().includes("ntuser") && !entry.toLowerCase().endsWith(".dat")) { + continue; + } + const results = getRegistry(entry, ".*CurrentVersion\\\\Run.*"); + if (results instanceof WindowsError) { + continue; + } + + for (const reg of results) { + const values = parseKeys(reg); + run_values = run_values.concat(values); + } + } + + return run_values; +} + +/** + * Function to extract Run key information + * @param value `Registry` object associated with a Run + * @returns Array of `RegistryRunKey` + */ +function parseKeys(value: Registry): RegistryRunKey[] { + const values: RegistryRunKey[] = []; + for (const entry of value.values) { + const run_value: RegistryRunKey = { + key_modified: value.last_modified, + key_path: value.path, + evidence: value.evidence, + registry_file: value.registry_file, + path: "", + created: "", + has_signature: false, + md5: "", + value: entry.data, + name: entry.value, + sha1: "", + sha256: "", + message: `Run key: '${entry.value}'`, + datetime: value.last_modified, + timestamp_desc: "Registry Last Modified", + artifact: "Windows Registry Run Key", + data_type: "windows:registry:run:entry" + }; + + // Try to get some metadata about the Run key values + if (entry.data.startsWith("%")) { + // Environment values + const env = /%.*?%/g; + for (const entry_match of entry.data.match(env) ?? []) { + const real_value = getEnvValue(entry_match.replaceAll("%", "")); + let path = entry.data.replace(entry_match, real_value); + if (path.includes("/")) { + path = path.split("/").at(0)?.trimEnd() ?? path; + } + const created = getCreation(path); + run_value.created = created; + + const hashes = getHash(path); + run_value.md5 = hashes.md5; + run_value.sha1 = hashes.sha1; + run_value.sha256 = hashes.sha256; + + run_value.has_signature = getPeInfo(path); + run_value.path = path; + + values.push(run_value); + break; + } + } else if (entry.data.startsWith("\"")) { + // Files with cli args + const cmd = /".*?"/; + for (const entry_match of entry.data.match(cmd) ?? []) { + const path = entry_match.replaceAll("\"", ""); + const created = getCreation(path); + run_value.created = created; + + const hashes = getHash(path); + run_value.md5 = hashes.md5; + run_value.sha1 = hashes.sha1; + run_value.sha256 = hashes.sha256; + + run_value.has_signature = getPeInfo(path); + run_value.path = path; + + values.push(run_value); + break; + } + } else if (entry.data.includes("/")) { + // Files with cli args with forward slashes + const file = entry.data.split("/").at(0); + if (file === undefined) { + values.push(run_value); + continue; + } + const path = file.replaceAll("\"", "").trimEnd(); + const created = getCreation(path); + run_value.created = created; + + const hashes = getHash(path); + run_value.md5 = hashes.md5; + run_value.sha1 = hashes.sha1; + run_value.sha256 = hashes.sha256; + + run_value.has_signature = getPeInfo(path); + run_value.path = path; + + values.push(run_value); + } + } + + return values; +} + +/** + * Function to pull the created timestamp for a file + * @param path Path to a file + * @returns Standard created timestamp + */ +function getCreation(path: string): string { + const meta = stat(path); + if (meta instanceof FileError) { + return "1970-01-01T00:00:00.00Z"; + } + return meta.created; +} + +/** + * Function to hash a file associated with the Registry Run key + * @param path Path to a file to hash + * @returns `Hashes` object + */ +function getHash(path: string): Hashes { + const hashes = hash(path, true, true, true); + if (hashes instanceof FileError) { + return { + md5: "", + sha1: "", + sha256: "" + }; + } + return hashes; +} + +/** + * Function to check if PE file has certificate info + * @param path Path to a PE file + * @returns `true` if signed. `false` if not signed + */ +function getPeInfo(path: string): boolean { + const info = getPe(path); + if (info instanceof WindowsError) { + return false; + } + + return info.cert.length !== 0; +} + +/** + * Function to test Windows Registry Run key parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows Registry Run key parsing + */ +export function testGetRunKeys(): void { + const test = "../../tests/test_data/windows/registry/NTUSER.DAT"; + const results = getRunKeys(test); + if (results instanceof WindowsError) { + throw results; + } + + if (results.length !== 2) { + throw `Got ${results.length} expected 2.......getRunKeys ❌` + } + + if (results[1]?.value !== "%ProgramFiles%\\Windows Mail\\wab.exe /Upgrade") { + throw `Got ${results[1]?.value} expected "%ProgramFiles%\\\\Windows Mail\\\\wab.exe /Upgrade".......getRunKeys ❌` + } + + if (results[1]?.message !== "Run key: 'WAB Migrate'") { + throw `Got ${results[1]?.message} expected "Run key: 'WAB Migrate'".......getRunKeys ❌` + } + + console.info(` Function getRunKeys ✅`); + + + const created = getCreation("C:\\Windows\\notepad.exe"); + if (created === "1970-01-01T00:00:00.00Z") { + throw 'Got bad time.......getCreation ❌' + } + console.info(` Function getCreation ✅`); + + + const hash = getHash("C:\\Windows\\notepad.exe"); + if (hash.md5 === "") { + throw 'Got empty hash.......getHash ❌' + } + console.info(` Function getHash ✅`); + + const info = getPeInfo("C:\\Windows\\notepad.exe"); + if (info === true) { + throw 'Got signature for notepad? Should be in Catalog?.......getPeInfo ❌' + } + console.info(` Function getPeInfo ✅`); + } \ No newline at end of file diff --git a/src/windows/registry/usb.ts b/src/windows/registry/usb.ts index a1b47847..1f4b67d8 100644 --- a/src/windows/registry/usb.ts +++ b/src/windows/registry/usb.ts @@ -1,219 +1,238 @@ -import { getRegistry } from "../../../mod"; -import { Registry } from "../../../types/windows/registry"; -import { UsbDevices } from "../../../types/windows/registry/usb"; -import { EncodingError } from "../../encoding/errors"; -import { decode } from "../../encoding/mod"; -import { extractUtf16String } from "../../encoding/strings"; -import { getEnvValue } from "../../environment/mod"; -import { NomError } from "../../nom/error"; -import { Endian } from "../../nom/helpers"; -import { nomUnsignedEightBytes } from "../../nom/mod"; -import { filetimeToUnixEpoch, unixEpochToISO } from "../../time/conversion"; -import { WindowsError } from "../errors"; - -/** - * Function to parse Windows Registry files to get list of USB devices that have been connected - * @param alt_file Alternative path to a SYSTEM Registry file - * @returns Array of `UsbDevices` or `WindowsError` - */ -export function listUsbDevices(alt_file?: string): UsbDevices[] | WindowsError { - if (alt_file !== undefined) { - return usbSystem(alt_file); - } - - const volume = getEnvValue("SystemDrive"); - if (volume === "") { - return new WindowsError(`USB`, `no SystemDrive found`); - } - - const system = `${volume}\\Windows\\System32\\config\\SYSTEM`; - return usbSystem(system); -} - -/** - * Function to get USB information from the SYSTEM Registry file - * @param path Full path to the SYSTEM Registry file - * @returns Array of `UsbDevices` or `WindowsError` - */ -function usbSystem(path: string): UsbDevices[] | WindowsError { - const usbstor = "Control\\DeviceMigration\\Devices\\USBSTOR\\"; - const usbstor_legacy = "\\Enum\\USBSTOR\\"; - const mounts = "MountedDevices"; - - const reg_data = getRegistry(path); - if (reg_data instanceof WindowsError) { - return new WindowsError(`USB`, `failed to parse ${path}: ${reg_data}`); - } - - const usbs: UsbDevices[] = []; - for (const reg of reg_data) { - if ( - (reg.path.includes(usbstor) || reg.path.includes(usbstor_legacy)) && - reg.values.length > 0 && - reg.name.includes("&") && - reg.path.includes("ControlSet00") - ) { - const values = usbStor(reg); - usbs.push(values); - } - } - - for (let i = 0; i < usbs.length; i++) { - const usb_info = usbs[i]; - if (usb_info === undefined) { - continue; - } - for (const reg of reg_data) { - if (reg.path.includes(usb_info.tracking_id) && "Partmg") { - for (const value of reg.values) { - if (value.value !== "DiskId" || usb_info.disk_id !== "") { - continue; - } - usb_info.disk_id = value.data.replace("{", "").replace("}", ""); - } - } else if (reg.path.includes(mounts) && reg.values.length !== 0) { - for (const value of reg.values) { - const data = decode(value.data); - if (data instanceof EncodingError) { - continue; - } - const value_string = extractUtf16String(data); - if ( - value_string.includes(usb_info.tracking_id) && - value.value.includes(":") - ) { - usb_info.drive_letter = value.value.split("\\").pop() as string; - } - } - } - if ( - reg.path.includes(usb_info.tracking_id) && - reg.path.includes("\\USBSTOR\\") && - reg.name === "0064" - ) { - for (const value of reg.values) { - usb_info.first_install = unixEpochToISO( - filetimeToUnixEpoch(BigInt(value.data)), - ); - } - } else if ( - reg.path.includes(usb_info.tracking_id) && - reg.path.includes("\\USBSTOR\\") && - reg.name === "0065" - ) { - for (const value of reg.values) { - usb_info.install = unixEpochToISO( - filetimeToUnixEpoch(BigInt(value.data)), - ); - } - } else if ( - reg.path.includes(usb_info.tracking_id) && - reg.path.includes("\\USBSTOR\\") && - reg.name === "0066" - ) { - for (const value of reg.values) { - usb_info.last_connected = unixEpochToISO( - filetimeToUnixEpoch(BigInt(value.data)), - ); - usb_info.datetime = usb_info.last_connected; - } - } else if ( - reg.path.includes(usb_info.tracking_id) && - reg.path.includes("\\USBSTOR\\") && - reg.name === "0067" - ) { - for (const value of reg.values) { - usb_info.last_removal = unixEpochToISO( - filetimeToUnixEpoch(BigInt(value.data)), - ); - } - } - } - } - - return usbs; -} - -/** - * Extract USB info from the Registry - * @param data `Registry` object - * @returns UsbDevices object - */ -function usbStor(data: Registry): UsbDevices { - const entry: UsbDevices = { - friendly_name: "", - last_connected: "", - last_removal: "", - usb_type: "", - vendor: "", - product: "", - revision: "", - tracking_id: "", - disk_id: "", - device_class_id: "", - drive_letter: "", - last_insertion: "", - install: "", - first_install: "", - message: "", - datetime: "1970-01-01T00:00:00.000Z", - timestamp_desc: "USB Last Connected", - artifact: "Windows USB Device", - data_type: "windows:registry:usb:entry" - }; - - const info = (data.key.split("\\").pop() as string).split("&"); - for (let i = 0; i < info.length; i++) { - const value = info[i]; - if (value === undefined) { - continue; - } - if (i === 0) { - entry.usb_type = value; - } - - if (value.includes("Ven_")) { - entry.vendor = value.slice(4); - } - - if (value.includes("Prod_")) { - entry.product = value.slice(5); - } - - if (value.includes("Rev_")) { - entry.revision = value.slice(4); - } - } - - if (data.name.at(1) !== undefined && data.name.at(1) !== "&") { - entry.tracking_id = data.name.split("&")[0] ?? ""; - } - - for (const value of data.values) { - if (value.value === "BusDeviceDesc") { - entry.friendly_name = value.data; - entry.message = `USB value ${entry.friendly_name}`; - } else if (value.value === "LastPresentDate") { - const time_bytes = decode(value.data); - if (time_bytes instanceof EncodingError) { - console.warn(`Could not base64 decode last present date ${time_bytes}`); - break; - } - - const time_data = nomUnsignedEightBytes(time_bytes, Endian.Le); - if (time_data instanceof NomError) { - console.warn(`Could not nom last present date ${time_data}`); - break; - } - - const time_value = unixEpochToISO( - filetimeToUnixEpoch(BigInt(time_data.value)), - ); - entry.last_insertion = time_value; - } else if (value.value === "ClassGUID") { - entry.device_class_id = value.data.replace("{", "").replace("}", ""); - } - } - - return entry; -} +import { getRegistry } from "../../../mod"; +import { Registry } from "../../../types/windows/registry"; +import { UsbDevices } from "../../../types/windows/registry/usb"; +import { EncodingError } from "../../encoding/errors"; +import { decode } from "../../encoding/mod"; +import { extractUtf16String } from "../../encoding/strings"; +import { getEnvValue } from "../../environment/mod"; +import { NomError } from "../../nom/error"; +import { Endian } from "../../nom/helpers"; +import { nomUnsignedEightBytes } from "../../nom/mod"; +import { filetimeToUnixEpoch, unixEpochToISO } from "../../time/conversion"; +import { WindowsError } from "../errors"; + +/** + * Function to parse Windows Registry files to get list of USB devices that have been connected + * @param alt_file Alternative path to a SYSTEM Registry file + * @returns Array of `UsbDevices` or `WindowsError` + */ +export function listUsbDevices(alt_file?: string): UsbDevices[] | WindowsError { + if (alt_file !== undefined) { + return usbSystem(alt_file); + } + + const volume = getEnvValue("SystemDrive"); + if (volume === "") { + return new WindowsError(`USB`, `no SystemDrive found`); + } + + const system = `${volume}\\Windows\\System32\\config\\SYSTEM`; + return usbSystem(system); +} + +/** + * Function to get USB information from the SYSTEM Registry file + * @param path Full path to the SYSTEM Registry file + * @returns Array of `UsbDevices` or `WindowsError` + */ +function usbSystem(path: string): UsbDevices[] | WindowsError { + const usbstor = "Control\\DeviceMigration\\Devices\\USBSTOR\\"; + const usbstor_legacy = "\\Enum\\USBSTOR\\"; + const mounts = "MountedDevices"; + + const reg_data = getRegistry(path); + if (reg_data instanceof WindowsError) { + return new WindowsError(`USB`, `failed to parse ${path}: ${reg_data}`); + } + + const usbs: UsbDevices[] = []; + for (const reg of reg_data) { + if ( + (reg.path.includes(usbstor) || reg.path.includes(usbstor_legacy)) && + reg.values.length > 0 && + reg.name.includes("&") && + reg.path.includes("ControlSet00") + ) { + const values = usbStor(reg); + usbs.push(values); + } + } + + for (let i = 0; i < usbs.length; i++) { + const usb_info = usbs[i]; + if (usb_info === undefined) { + continue; + } + for (const reg of reg_data) { + if (reg.path.includes(usb_info.tracking_id) && "Partmg") { + for (const value of reg.values) { + if (value.value !== "DiskId" || usb_info.disk_id !== "") { + continue; + } + usb_info.disk_id = value.data.replace("{", "").replace("}", ""); + } + } else if (reg.path.includes(mounts) && reg.values.length !== 0) { + for (const value of reg.values) { + const data = decode(value.data); + if (data instanceof EncodingError) { + continue; + } + const value_string = extractUtf16String(data); + if ( + value_string.includes(usb_info.tracking_id) && + value.value.includes(":") + ) { + usb_info.drive_letter = value.value.split("\\").pop() as string; + } + } + } + + if ( + reg.path.includes(usb_info.tracking_id) && + reg.path.includes("\\USBSTOR\\") && + reg.name === "0064" + ) { + for (const value of reg.values) { + if (typeof value.data === 'string') { + usb_info.first_install = value.data; + break; + } + usb_info.first_install = unixEpochToISO( + filetimeToUnixEpoch(BigInt(value.data)), + ); + } + } else if ( + reg.path.includes(usb_info.tracking_id) && + reg.path.includes("\\USBSTOR\\") && + reg.name === "0065" + ) { + for (const value of reg.values) { + if (typeof value.data === 'string') { + usb_info.install = value.data; + break; + } + usb_info.install = unixEpochToISO( + filetimeToUnixEpoch(BigInt(value.data)), + ); + } + } else if ( + reg.path.includes(usb_info.tracking_id) && + reg.path.includes("\\USBSTOR\\") && + reg.name === "0066" + ) { + for (const value of reg.values) { + if (typeof value.data === 'string') { + usb_info.last_connected = value.data; + usb_info.datetime = usb_info.last_connected; + break; + } + usb_info.last_connected = unixEpochToISO( + filetimeToUnixEpoch(BigInt(value.data)), + ); + usb_info.datetime = usb_info.last_connected; + } + } else if ( + reg.path.includes(usb_info.tracking_id) && + reg.path.includes("\\USBSTOR\\") && + reg.name === "0067" + ) { + for (const value of reg.values) { + if (typeof value.data === 'string') { + usb_info.last_removal = value.data; + break; + } + usb_info.last_removal = unixEpochToISO( + filetimeToUnixEpoch(BigInt(value.data)), + ); + } + } + } + } + + return usbs; +} + +/** + * Extract USB info from the Registry + * @param data `Registry` object + * @returns UsbDevices object + */ +function usbStor(data: Registry): UsbDevices { + const entry: UsbDevices = { + friendly_name: "", + last_connected: "", + last_removal: "", + usb_type: "", + vendor: "", + product: "", + revision: "", + tracking_id: "", + disk_id: "", + device_class_id: "", + drive_letter: "", + last_insertion: "", + install: "", + first_install: "", + message: "USB no friendly name", + datetime: "1970-01-01T00:00:00.000Z", + timestamp_desc: "USB Last Connected", + artifact: "Windows USB Device", + data_type: "windows:registry:usb:entry", + evidence: data.evidence, + }; + + const info = (data.key.split("\\").pop() as string).split("&"); + for (let i = 0; i < info.length; i++) { + const value = info[i]; + if (value === undefined) { + continue; + } + if (i === 0) { + entry.usb_type = value; + } + + if (value.includes("Ven_")) { + entry.vendor = value.slice(4); + } + + if (value.includes("Prod_")) { + entry.product = value.slice(5); + } + + if (value.includes("Rev_")) { + entry.revision = value.slice(4); + } + } + + if (data.name.at(1) !== undefined && data.name.at(1) !== "&") { + entry.tracking_id = data.name.split("&").at(0) ?? ""; + } + + for (const value of data.values) { + if (value.value === "BusDeviceDesc") { + entry.friendly_name = value.data; + entry.message = `USB value ${entry.friendly_name}`; + } else if (value.value === "LastPresentDate") { + const time_bytes = decode(value.data); + if (time_bytes instanceof EncodingError) { + console.warn(`Could not base64 decode last present date ${time_bytes}`); + break; + } + + const time_data = nomUnsignedEightBytes(time_bytes, Endian.Le); + if (time_data instanceof NomError) { + console.warn(`Could not nom last present date ${time_data}`); + break; + } + + const time_value = unixEpochToISO( + filetimeToUnixEpoch(BigInt(time_data.value)), + ); + entry.last_insertion = time_value; + } else if (value.value === "ClassGUID") { + entry.device_class_id = value.data.replace("{", "").replace("}", ""); + } + } + + return entry; +} diff --git a/src/windows/registry/wifi.ts b/src/windows/registry/wifi.ts index 107ac290..6012e209 100644 --- a/src/windows/registry/wifi.ts +++ b/src/windows/registry/wifi.ts @@ -1,210 +1,210 @@ -import { Registry } from "../../../types/windows/registry"; -import { NameType, Wifi, WifiCategory } from "../../../types/windows/registry/wifi"; -import { decode } from "../../encoding/base64"; -import { EncodingError } from "../../encoding/errors"; -import { getEnvValue } from "../../environment/mod"; -import { NomError } from "../../nom/error"; -import { Endian, nomUnsignedTwoBytes } from "../../nom/helpers"; -import { WindowsError } from "../errors"; -import { getRegistry } from "../registry"; - -/** - * Function to list connected Windows WiFi networks - * @param alt_path Optional alternative path to the `SOFTWARE` Registry file - * @returns Array of `Wifi` entries or `WindowsError` - */ -export function wifiNetworksWindows(alt_path?: string): Wifi[] | WindowsError { - let path = "\\Windows\\System32\\config\\SOFTWARE"; - if (alt_path !== undefined) { - path = alt_path; - } else { - let drive = getEnvValue("SystemDrive"); - if (drive === "") { - drive = "C:"; - } - path = `${drive}${path}`; - } - - const reg_entries = getRegistry(path, "\\\\currentversion\\\\networklist\\\\profiles\\\\\\{"); - if (reg_entries instanceof WindowsError) { - return new WindowsError(`WIFI`, `could not parse the SOFTWARE Registry file ${reg_entries}`); - } - - const profiles: Registry[] = []; - const networks: Wifi[] = []; - for (const entry of profiles) { - const net: Wifi = { - name: "", - description: "", - managed: false, - category: WifiCategory.Unknown, - created_local_time: "", - name_type: NameType.Unknown, - id: entry.name.replace("{", "").replace("}", ""), - last_connected_local_time: "", - registry_path: entry.path, - registry_file: path, - message: "", - datetime: entry.last_modified, - timestamp_desc: "Registry Key Modified", - artifact: "WiFi Network", - data_type: "windows:registry:wifi:entry" - }; - - for (const value of entry.values) { - if (value.value.toLowerCase() === "profilename") { - net.name = value.data; - } else if (value.value.toLowerCase() === "description") { - net.description = value.data; - net.message = `WiFi network '${value.data}'`; - } else if (value.value.toLowerCase() === "managed") { - net.managed = Boolean(Number(value.data)); - } else if (value.value.toLowerCase() === "category") { - switch (value.data) { - case "0": { - net.category = WifiCategory.Public; - break; - } - case "1": { - net.category = WifiCategory.Private; - break; - } - case "2": { - net.category = WifiCategory.Domain; - break; - } - default: net.category = WifiCategory.Unknown; - } - } else if (value.value.toLowerCase() === "datecreated") { - const bytes = decode(value.data); - if (bytes instanceof EncodingError) { - continue; - } - const time_value = decodeTime(bytes); - if (time_value instanceof NomError) { - continue; - } - net.created_local_time = time_value; - } else if (value.value.toLowerCase() === "nametype") { - switch (value.data) { - case "6": { - net.name_type = NameType.Wired; - break; - } - case "23": { - net.name_type = NameType.Vpn; - break; - } - case "71": { - net.name_type = NameType.Wireless; - break; - } - case "243": { - net.name_type = NameType.Mobile; - break; - } - default: net.name_type = NameType.Unknown; - } - } else if (value.value.toLowerCase() === "datelastconnected") { - const bytes = decode(value.data); - if (bytes instanceof EncodingError) { - continue; - } - const time_value = decodeTime(bytes); - if (time_value instanceof NomError) { - continue; - } - net.last_connected_local_time = time_value; - } - } - networks.push(net); - } - - return networks; -} - -/** - * Function to convert Network time bytes to proper timestamp. - * It uses a weird format: https://superuser.com/questions/1572038/ms-regedit-hex-to-date-conversion - * @param bytes Timestamp bytes - * @returns Timestamp or `NomError` - */ -function decodeTime(bytes: Uint8Array): string | NomError { - let input = nomUnsignedTwoBytes(bytes, Endian.Le); - if (input instanceof NomError) { - return input; - } - - const year = input.value; - input = nomUnsignedTwoBytes(input.remaining, Endian.Le); - if (input instanceof NomError) { - return input; - } - let month: number | string = input.value; - if (month < 10) { - month = `0${month}`; - } - - // Day of week (ex: Thursday) - input = nomUnsignedTwoBytes(input.remaining, Endian.Le); - if (input instanceof NomError) { - return input; - } - - input = nomUnsignedTwoBytes(input.remaining, Endian.Le); - if (input instanceof NomError) { - return input; - } - let day: number | string = input.value; - if (day < 10) { - day = `0${day}`; - } - - input = nomUnsignedTwoBytes(input.remaining, Endian.Le); - if (input instanceof NomError) { - return input; - } - let hour: number | string = input.value; - if (hour < 10) { - hour = `0${hour}`; - } - - input = nomUnsignedTwoBytes(input.remaining, Endian.Le); - if (input instanceof NomError) { - return input; - } - let mins: number | string = input.value; - if (mins < 10) { - mins = `0${mins}`; - } - - input = nomUnsignedTwoBytes(input.remaining, Endian.Le); - if (input instanceof NomError) { - return input; - } - let seconds: number | string = input.value; - if (seconds < 10) { - seconds = `0${seconds}`; - } - - return `${year}-${month}-${day}T${hour}:${mins}:${seconds}`; -} - -/** - * Function to test Windows WiFi parsing - * This function should not be called unless you are developing the artemis-api - * Or want to validate the Windows WiFi parsing - */ -export function testWindowsWifiNetworks(): void { - const test = new Uint8Array([233, 7, 7, 0, 4, 0, 10, 0, 1, 0, 10, 0, 43, 0, 94, 3]); - const results = decodeTime(test); - if (results instanceof NomError) { - throw results; - } - - if (results !== "2025-07-10T01:10:43") { - throw `Got ${results} expected "2025-07-10T01:10:43".......decodeTime ❌` - } - console.info(` Function decodeTime ✅`); - +import { Registry } from "../../../types/windows/registry"; +import { NameType, Wifi, WifiCategory } from "../../../types/windows/registry/wifi"; +import { decode } from "../../encoding/base64"; +import { EncodingError } from "../../encoding/errors"; +import { getEnvValue } from "../../environment/mod"; +import { NomError } from "../../nom/error"; +import { Endian, nomUnsignedTwoBytes } from "../../nom/helpers"; +import { WindowsError } from "../errors"; +import { getRegistry } from "../registry"; + +/** + * Function to list connected Windows WiFi networks + * @param alt_path Optional alternative path to the `SOFTWARE` Registry file + * @returns Array of `Wifi` entries or `WindowsError` + */ +export function wifiNetworksWindows(alt_path?: string): Wifi[] | WindowsError { + let path = "\\Windows\\System32\\config\\SOFTWARE"; + if (alt_path !== undefined) { + path = alt_path; + } else { + let drive = getEnvValue("SystemDrive"); + if (drive === "") { + drive = "C:"; + } + path = `${drive}${path}`; + } + + const reg_entries = getRegistry(path, "\\\\currentversion\\\\networklist\\\\profiles\\\\\\{"); + if (reg_entries instanceof WindowsError) { + return new WindowsError(`WIFI`, `could not parse the SOFTWARE Registry file ${reg_entries}`); + } + + const profiles: Registry[] = []; + const networks: Wifi[] = []; + for (const entry of profiles) { + const net: Wifi = { + name: "", + description: "", + managed: false, + category: WifiCategory.Unknown, + created_local_time: "", + name_type: NameType.Unknown, + id: entry.name.replace("{", "").replace("}", ""), + last_connected_local_time: "", + registry_path: entry.path, + message: "", + datetime: entry.last_modified, + timestamp_desc: "Registry Key Modified", + artifact: "WiFi Network", + data_type: "windows:registry:wifi:entry", + evidence: path, + }; + + for (const value of entry.values) { + if (value.value.toLowerCase() === "profilename") { + net.name = value.data; + } else if (value.value.toLowerCase() === "description") { + net.description = value.data; + net.message = `WiFi network '${value.data}'`; + } else if (value.value.toLowerCase() === "managed") { + net.managed = Boolean(Number(value.data)); + } else if (value.value.toLowerCase() === "category") { + switch (value.data) { + case "0": { + net.category = WifiCategory.Public; + break; + } + case "1": { + net.category = WifiCategory.Private; + break; + } + case "2": { + net.category = WifiCategory.Domain; + break; + } + default: net.category = WifiCategory.Unknown; + } + } else if (value.value.toLowerCase() === "datecreated") { + const bytes = decode(value.data); + if (bytes instanceof EncodingError) { + continue; + } + const time_value = decodeTime(bytes); + if (time_value instanceof NomError) { + continue; + } + net.created_local_time = time_value; + } else if (value.value.toLowerCase() === "nametype") { + switch (value.data) { + case "6": { + net.name_type = NameType.Wired; + break; + } + case "23": { + net.name_type = NameType.Vpn; + break; + } + case "71": { + net.name_type = NameType.Wireless; + break; + } + case "243": { + net.name_type = NameType.Mobile; + break; + } + default: net.name_type = NameType.Unknown; + } + } else if (value.value.toLowerCase() === "datelastconnected") { + const bytes = decode(value.data); + if (bytes instanceof EncodingError) { + continue; + } + const time_value = decodeTime(bytes); + if (time_value instanceof NomError) { + continue; + } + net.last_connected_local_time = time_value; + } + } + networks.push(net); + } + + return networks; +} + +/** + * Function to convert Network time bytes to proper timestamp. + * It uses a weird format: https://superuser.com/questions/1572038/ms-regedit-hex-to-date-conversion + * @param bytes Timestamp bytes + * @returns Timestamp or `NomError` + */ +function decodeTime(bytes: Uint8Array): string | NomError { + let input = nomUnsignedTwoBytes(bytes, Endian.Le); + if (input instanceof NomError) { + return input; + } + + const year = input.value; + input = nomUnsignedTwoBytes(input.remaining, Endian.Le); + if (input instanceof NomError) { + return input; + } + let month: number | string = input.value; + if (month < 10) { + month = `0${month}`; + } + + // Day of week (ex: Thursday) + input = nomUnsignedTwoBytes(input.remaining, Endian.Le); + if (input instanceof NomError) { + return input; + } + + input = nomUnsignedTwoBytes(input.remaining, Endian.Le); + if (input instanceof NomError) { + return input; + } + let day: number | string = input.value; + if (day < 10) { + day = `0${day}`; + } + + input = nomUnsignedTwoBytes(input.remaining, Endian.Le); + if (input instanceof NomError) { + return input; + } + let hour: number | string = input.value; + if (hour < 10) { + hour = `0${hour}`; + } + + input = nomUnsignedTwoBytes(input.remaining, Endian.Le); + if (input instanceof NomError) { + return input; + } + let mins: number | string = input.value; + if (mins < 10) { + mins = `0${mins}`; + } + + input = nomUnsignedTwoBytes(input.remaining, Endian.Le); + if (input instanceof NomError) { + return input; + } + let seconds: number | string = input.value; + if (seconds < 10) { + seconds = `0${seconds}`; + } + + return `${year}-${month}-${day}T${hour}:${mins}:${seconds}`; +} + +/** + * Function to test Windows WiFi parsing + * This function should not be called unless you are developing the artemis-api + * Or want to validate the Windows WiFi parsing + */ +export function testWindowsWifiNetworks(): void { + const test = new Uint8Array([233, 7, 7, 0, 4, 0, 10, 0, 1, 0, 10, 0, 43, 0, 94, 3]); + const results = decodeTime(test); + if (results instanceof NomError) { + throw results; + } + + if (results !== "2025-07-10T01:10:43") { + throw `Got ${results} expected "2025-07-10T01:10:43".......decodeTime ❌` + } + console.info(` Function decodeTime ✅`); + } \ No newline at end of file diff --git a/src/windows/registry/wordwheel.ts b/src/windows/registry/wordwheel.ts index 50108459..0b597d35 100644 --- a/src/windows/registry/wordwheel.ts +++ b/src/windows/registry/wordwheel.ts @@ -1,78 +1,77 @@ -import { getRegistry } from "../../../mod"; -import { Registry } from "../../../types/windows/registry"; -import { WordWheelEntry } from "../../../types/windows/registry/wordwheel"; -import { EncodingError } from "../../encoding/errors"; -import { decode } from "../../encoding/mod"; -import { extractUtf16String } from "../../encoding/strings"; -import { FileError } from "../../filesystem/errors"; -import { glob } from "../../filesystem/files"; -import { WindowsError } from "../errors"; - -/** - * Fucntion to parse user WordWheel searches in Windows Explorer - * @param path Path to NTUSER.dat file. Can also provide a glob to NTUSER.dat files - * @returns Array of `WordWheelEntry` or `WindowsError` - */ -export function parseWordWheel(path: string): WordWheelEntry[] | WindowsError { - const globs = glob(path); - if (globs instanceof FileError) { - return new WindowsError( - "WORDWHEEL", - `Could not glob registry path ${path}: ${globs}`, - ); - } - - let wheels: WordWheelEntry[] = []; - for (const glob_path of globs) { - if (!glob_path.is_file) { - continue; - } - - const reg_data = getRegistry(glob_path.full_path); - if (reg_data instanceof WindowsError) { - console.warn(`Could not parse Registry file ${glob_path.full_path}`); - continue; - } - - wheels = wheels.concat(extractWheel(reg_data, glob_path.full_path)); - } - return wheels; -} - -/** - * Extract WordWheel entries from Regstry. It is just a string value - * @param reg Array of `Registry` entries - * @param source_path Source path to the NTUSER.dat file - * @returns Array of `WordWheelEntry` entries - */ -function extractWheel(reg: Registry[], source_path: string): WordWheelEntry[] { - const wheels: WordWheelEntry[] = []; - for (const entry of reg) { - if (!entry.path.includes("WordWheel")) { - continue; - } - - for (const value of entry.values) { - if ( - Number.isNaN(Number(value.value)) || value.data_type !== "REG_BINARY" - ) { - continue; - } - const bytes = decode(value.data); - if (bytes instanceof EncodingError) { - continue; - } - - const search_term = extractUtf16String(bytes); - const search: WordWheelEntry = { - search_term, - last_modified: entry.last_modified, - source_path, - reg_path: entry.path, - }; - wheels.push(search); - } - } - - return wheels; -} +import { getRegistry } from "../../../mod"; +import { Registry } from "../../../types/windows/registry"; +import { WordWheelEntry } from "../../../types/windows/registry/wordwheel"; +import { EncodingError } from "../../encoding/errors"; +import { decode } from "../../encoding/mod"; +import { extractUtf16String } from "../../encoding/strings"; +import { FileError } from "../../filesystem/errors"; +import { glob } from "../../filesystem/files"; +import { WindowsError } from "../errors"; + +/** + * Fucntion to parse user WordWheel searches in Windows Explorer + * @param path Path to NTUSER.dat file. Can also provide a glob to NTUSER.dat files + * @returns Array of `WordWheelEntry` or `WindowsError` + */ +export function parseWordWheel(path: string): WordWheelEntry[] | WindowsError { + const globs = glob(path); + if (globs instanceof FileError) { + return new WindowsError( + "WORDWHEEL", + `Could not glob registry path ${path}: ${globs}`, + ); + } + + let wheels: WordWheelEntry[] = []; + for (const glob_path of globs) { + if (!glob_path.is_file) { + continue; + } + + const reg_data = getRegistry(glob_path.full_path); + if (reg_data instanceof WindowsError) { + console.warn(`Could not parse Registry file ${glob_path.full_path}`); + continue; + } + + wheels = wheels.concat(extractWheel(reg_data)); + } + return wheels; +} + +/** + * Extract WordWheel entries from Regstry. It is just a string value + * @param reg Array of `Registry` entries + * @returns Array of `WordWheelEntry` entries + */ +function extractWheel(reg: Registry[]): WordWheelEntry[] { + const wheels: WordWheelEntry[] = []; + for (const entry of reg) { + if (!entry.path.includes("WordWheel")) { + continue; + } + + for (const value of entry.values) { + if ( + Number.isNaN(Number(value.value)) || value.data_type !== "REG_BINARY" + ) { + continue; + } + const bytes = decode(value.data); + if (bytes instanceof EncodingError) { + continue; + } + + const search_term = extractUtf16String(bytes); + const search: WordWheelEntry = { + search_term, + last_modified: entry.last_modified, + evidence: entry.evidence, + reg_path: entry.path, + }; + wheels.push(search); + } + } + + return wheels; +} diff --git a/src/windows/shellbags.ts b/src/windows/shellbags.ts index 17aa88e5..6fb6a9db 100644 --- a/src/windows/shellbags.ts +++ b/src/windows/shellbags.ts @@ -1,22 +1,22 @@ -import { Shellbags } from "../../types/windows/shellbags"; -import { WindowsError } from "./errors"; - -/** - * Function to parse and reconstruct `Shellbags` on the systemdrive - * @param resolve_guids Whether to lookup GUID values. Ex: Convert `20d04fe0-3aea-1069-a2d8-08002b30309d` to `This PC` - * @param path Optional path to Registry file containing `Shellbags`. If not provided, will parse `Shellbags` for all users - * @returns Array of `Shellbag` entries from from systemdrive or `WindowsError` - */ -export function getShellbags( - resolve_guids: boolean, - path?: string, -): Shellbags[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_shellbags(resolve_guids, path); - - return data; - } catch (err) { - return new WindowsError("SHELLBAGS", `failed to parse shellbags: ${err}`); - } -} +import { Shellbags } from "../../types/windows/shellbags"; +import { WindowsError } from "./errors"; + +/** + * Function to parse and reconstruct `Shellbags` on the system drive + * @param resolve_guids Whether to lookup GUID values. Ex: Convert `20d04fe0-3aea-1069-a2d8-08002b30309d` to `This PC` + * @param path Optional path to Registry file containing `Shellbags`. If not provided, will parse `Shellbags` for all users + * @returns Array of `Shellbag` entries from from system drive or `WindowsError` + */ +export function getShellbags( + resolve_guids: boolean, + path?: string, +): Shellbags[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_shellbags(resolve_guids, path); + + return data; + } catch (err) { + return new WindowsError("SHELLBAGS", `failed to parse shellbags: ${err}`); + } +} diff --git a/src/windows/shimcache.ts b/src/windows/shimcache.ts index f84d84e7..83ebff8c 100644 --- a/src/windows/shimcache.ts +++ b/src/windows/shimcache.ts @@ -1,18 +1,18 @@ -import { Shimcache } from "../../types/windows/shimcache"; -import { WindowsError } from "./errors"; - -/** - * Function to parse `Shimcache` entries on the systemdrive - * @param path Optional path t Registry file - * @returns Array of `Shimcache` entries parsed from the sysystemdrive letter or `WindowsError` - */ -export function getShimcache(path?: string): Shimcache[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_shimcache(path); - - return data; - } catch (err) { - return new WindowsError("SHIMCACHE", `failed to parse shimcache: ${err}`); - } -} +import { Shimcache } from "../../types/windows/shimcache"; +import { WindowsError } from "./errors"; + +/** + * Function to parse `Shimcache` entries on the systemdrive + * @param path Optional path t Registry file + * @returns Array of `Shimcache` entries parsed from the system drive letter or `WindowsError` + */ +export function getShimcache(path?: string): Shimcache[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_shimcache(path); + + return data; + } catch (err) { + return new WindowsError("SHIMCACHE", `failed to parse shimcache: ${err}`); + } +} diff --git a/src/windows/shimdb.ts b/src/windows/shimdb.ts index fab379f5..fd65d05e 100644 --- a/src/windows/shimdb.ts +++ b/src/windows/shimdb.ts @@ -1,18 +1,18 @@ -import { Shimdb } from "../../types/windows/shimdb"; -import { WindowsError } from "./errors"; - -/** - * Function to parse `ShimDB` entries on the systemdrive - * @param path Optional path to a `ShimDB` file - * @returns Array of `ShimDB` entries parsed from the sysystemdrive letter or `WindowsError` - */ -export function getShimdb(path?: string): Shimdb[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_shimdb(path); - - return data; - } catch (err) { - return new WindowsError("SHIMDB", `failed to parse shimdb: ${err}`); - } -} +import { Shimdb } from "../../types/windows/shimdb"; +import { WindowsError } from "./errors"; + +/** + * Function to parse `ShimDB` entries on the system drive + * @param path Optional path to a `ShimDB` file + * @returns Array of `ShimDB` entries parsed from the system drive letter or `WindowsError` + */ +export function getShimdb(path?: string): Shimdb[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_shimdb(path); + + return data; + } catch (err) { + return new WindowsError("SHIMDB", `failed to parse shimdb: ${err}`); + } +} diff --git a/src/windows/tasks.ts b/src/windows/tasks.ts index 197e8d49..abc23af3 100644 --- a/src/windows/tasks.ts +++ b/src/windows/tasks.ts @@ -1,21 +1,21 @@ -import { TaskXml } from "../../types/windows/tasks"; -import { WindowsError } from "./errors"; - -/** - * Parse the Schedule Task files using the default systemdrive (typically C). - * Will parse both XML and Job Task files - * @param path path to a Schedule Task file - * @returns Parsed XML and/or Job Task file(s) or `WindowsError` - */ -export function getTasks( - path: string, -): TaskXml | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_tasks(path); - - return data; - } catch (err) { - return new WindowsError("TASKS", `failed to parse tasks: ${err}`); - } -} +import { TaskXml } from "../../types/windows/tasks"; +import { WindowsError } from "./errors"; + +/** + * Parse the Schedule Task files using the default system drive (typically C). + * Will parse both XML and Job Task files + * @param path path to a Schedule Task file + * @returns Parsed XML and/or Job Task file(s) or `WindowsError` + */ +export function getTasks( + path: string, +): TaskXml | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_tasks(path); + + return data; + } catch (err) { + return new WindowsError("TASKS", `failed to parse tasks: ${err}`); + } +} diff --git a/src/windows/userassist.ts b/src/windows/userassist.ts index c7cc0240..16c7d444 100644 --- a/src/windows/userassist.ts +++ b/src/windows/userassist.ts @@ -1,22 +1,22 @@ -import { UserAssist } from "../../types/windows/userassist"; -import { WindowsError } from "./errors"; - -/** - * Function to parse `UserAssist` entries - * @param resolve Enable folder description GUID lookups by parsing the SYSTEM Registry file before parsing UserAssist. - * @param path Optinal path to an alternative Registry file - * @returns Array of `UserAssist` entries parsed from the sysystemdrive letter or `WindowsError` - */ -export function getUserassist( - resolve: boolean, - path?: string, -): UserAssist[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_userassist(resolve, path); - - return data; - } catch (err) { - return new WindowsError("USERASSIST", `failed to parse userassist: ${err}`); - } -} +import { UserAssist } from "../../types/windows/userassist"; +import { WindowsError } from "./errors"; + +/** + * Function to parse `UserAssist` entries + * @param resolve Enable folder description GUID lookups by parsing the SYSTEM Registry file before parsing UserAssist. + * @param path Optinal path to an alternative Registry file + * @returns Array of `UserAssist` entries parsed from the system drive letter or `WindowsError` + */ +export function getUserassist( + resolve: boolean, + path?: string, +): UserAssist[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_userassist(resolve, path); + + return data; + } catch (err) { + return new WindowsError("USERASSIST", `failed to parse userassist: ${err}`); + } +} diff --git a/src/windows/usnjrnl.ts b/src/windows/usnjrnl.ts index ebadc3fc..4390abeb 100644 --- a/src/windows/usnjrnl.ts +++ b/src/windows/usnjrnl.ts @@ -1,24 +1,24 @@ -import { UsnJrnl } from "../../types/windows/usnjrnl"; -import { WindowsError } from "./errors"; - -/** - * Function to parse the `UsnJrnl` on the systemdrive - * @param path Optional path to an alternative `UsnJrnl` file - * @param drive Optional alternative drive letter - * @param mft Optional path to an alternative MFT file - * @returns Array of `UsnJrnl` entries from sysystemdrive letter or `WindowsError` - */ -export function getUsnjrnl( - path?: string, - drive?: string, - mft?: string, -): UsnJrnl[] | WindowsError { - try { - // @ts-expect-error: Custom Artemis function - const data = js_usnjrnl(path, drive, mft); - - return data; - } catch (err) { - return new WindowsError("USNJRNL", `failed to parse usnjrnl: ${err}`); - } -} +import { UsnJrnl } from "../../types/windows/usnjrnl"; +import { WindowsError } from "./errors"; + +/** + * Function to parse the `UsnJrnl` on the systemdrive + * @param path Optional path to an alternative `UsnJrnl` file + * @param drive Optional alternative drive letter + * @param mft Optional path to an alternative MFT file + * @returns Array of `UsnJrnl` entries from system drive letter or `WindowsError` + */ +export function getUsnjrnl( + path?: string, + drive?: string, + mft?: string, +): UsnJrnl[] | WindowsError { + try { + // @ts-expect-error: Custom Artemis function + const data = js_usnjrnl(path, drive, mft); + + return data; + } catch (err) { + return new WindowsError("USNJRNL", `failed to parse usnjrnl: ${err}`); + } +} diff --git a/tests/applications/brave/main.ts b/tests/applications/brave/main.ts new file mode 100644 index 00000000..dd14345d --- /dev/null +++ b/tests/applications/brave/main.ts @@ -0,0 +1,30 @@ +import { Brave, Format, Output, OutputType, PlatformType } from "../../../mod"; +import { testChromiumCache } from "../../test"; + +function main() { + console.log('Running Brave tests....'); + console.log(' Starting live test....'); + const client = new Brave(PlatformType.Darwin); + const out: Output = { + name: "brave_test", + directory: "./tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + output: OutputType.LOCAL + }; + client.retrospect(out); + + + console.log(' Live test passed! 🥳\n'); + + console.log('Starting Brave Cache tests....'); + testChromiumCache(); + console.log(' Brave Cache tests passed! 🥳\n'); + + console.log('All Brave Cache tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/applications/firefox/main.ts b/tests/applications/firefox/main.ts new file mode 100644 index 00000000..bd1ff75e --- /dev/null +++ b/tests/applications/firefox/main.ts @@ -0,0 +1,29 @@ +import { FireFox, Format, Output, OutputType, PlatformType } from "../../../mod"; +import { testFirefoxJsonFiles } from "../../test"; + +function main() { + console.log('Running Firefox tests....'); + console.log(' Starting live test....'); + const client = new FireFox(PlatformType.Windows); + const out: Output = { + name: "firefox_test", + directory: "./tmp", + format: Format.JSONL, + compress: false, + timeline: false, + endpoint_id: "", + collection_id: 0, + output: OutputType.LOCAL + }; + client.retrospect(out); + + + console.log(' Live test passed! 🥳\n'); + + console.log('Starting Firefox JSON tests....'); + testFirefoxJsonFiles(); + console.log(' Firefox JSON tests passed! 🥳\n'); + +} + +main(); diff --git a/tests/applications/rustdesk/main.ts b/tests/applications/rustdesk/main.ts new file mode 100644 index 00000000..aa0e6d8a --- /dev/null +++ b/tests/applications/rustdesk/main.ts @@ -0,0 +1,23 @@ +import { PlatformType, RustDesk } from "../../../mod"; +import { testRustDeskLogs } from "../../test"; + +function main() { + console.log('Running RustDesk tests....'); + console.log(' Starting live test....'); + const results = new RustDesk(PlatformType.Linux, "../../test_data/rustdesk/1.4.6"); + const used_alt_dir = true; + const hits = results.logs(used_alt_dir); + if (hits.length !== 148) { + throw `Got ${hits.length} rows. Expected 148`; + } + + console.log(' Live test passed! 🥳\n'); + + console.log(' Starting Logs tests....'); + testRustDeskLogs(); + console.log(' All Logs tests passed! 🥳\n'); + + console.log('All RustDesk tests passed! 🥳💃🕺'); +} + +main(); \ No newline at end of file diff --git a/tests/esxi/accounts/main.ts b/tests/esxi/accounts/main.ts new file mode 100644 index 00000000..baa8ed7c --- /dev/null +++ b/tests/esxi/accounts/main.ts @@ -0,0 +1,13 @@ +import { testEsxiAccounts } from "../../test"; + +function main() { + console.log('Running ESXi Accounts tests....'); + + console.log(' Starting ESXi Accounts test....'); + testEsxiAccounts(); + + console.log(' Accounts test passed! 🥳'); + console.log('All ESXi Accounts tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/esxi/compile_tests.py b/tests/esxi/compile_tests.py new file mode 100644 index 00000000..fa6f75a4 --- /dev/null +++ b/tests/esxi/compile_tests.py @@ -0,0 +1,159 @@ +# AI took my simple 15 line bash script and turned it into a nice ~180 line Python script XD + +from pathlib import Path +import subprocess +import sys +import os +import time +from datetime import datetime +from contextlib import contextmanager +from statistics import mean, median, quantiles + +from colorama import init, Fore, Style + +# Initialize colorama (needed for Windows) +init(autoreset=True) + +@contextmanager +def pushd(new_dir: Path): + """Temporarily change working directory like `cd` in Bash.""" + prev_dir = Path.cwd() + try: + os.chdir(new_dir) + yield + finally: + os.chdir(prev_dir) + +def run_tests(): + target = "" + # By default all tests are run + # Can provide optional arg to run single test + if len(sys.argv) == 2: + target = sys.argv[1] + + base_dir = Path.cwd() + total_start = time.perf_counter() + + total_tests = 0 + passed_tests = 0 + durations = [] # (project_name, elapsed_time) + + for entry in base_dir.iterdir(): + if not entry.is_dir(): + continue + + if target != "" and target != entry.name: + continue + + total_tests += 1 + with pushd(entry): + start_time = time.perf_counter() + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + + print(f"\n{Fore.CYAN}[{timestamp}] 🔥 Running test for {entry.name} 🔥{Style.RESET_ALL}") + + # Run esbuild + try: + subprocess.run( + ["esbuild", "--log-level=silent", "--bundle", "--outfile=main.js", "main.ts"], + check=True + ) + except subprocess.CalledProcessError: + print(f"{Fore.RED}❌ Failed to build {entry.name}{Style.RESET_ALL}") + break + + # Run script_tester + try: + subprocess.run( + [str(base_dir / "script_tester"), "main.js"], + check=True + ) + except subprocess.CalledProcessError: + print(f"{Fore.RED}❌ Failed test for {entry.name}{Style.RESET_ALL}") + break + else: + passed_tests += 1 + + # Duration reporting + elapsed = time.perf_counter() - start_time + durations.append((entry.name, elapsed)) + print(f"{Fore.GREEN}✅ Completed {entry.name} in {elapsed:.2f} seconds{Style.RESET_ALL}") + + # Totals + total_elapsed = time.perf_counter() - total_start + print(f"\n{Fore.MAGENTA}📊 Test Summary{Style.RESET_ALL}") + print(f" Total tests: {total_tests}") + print(f" {Fore.GREEN}Passed: {passed_tests}{Style.RESET_ALL}") + print(f" {Fore.RED}Failed: {total_tests - passed_tests}{Style.RESET_ALL}") + print(f" Total time: {total_elapsed:.2f} seconds") + + # Stats + if durations: + slowest = max(durations, key=lambda x: x[1]) + fastest = min(durations, key=lambda x: x[1]) + times = [d[1] for d in durations] + + avg_time = mean(times) + q25, q50, q75 = quantiles(times, n=4) # quartiles + + print(f" {Fore.YELLOW}🐢 Slowest project: {slowest[0]} " + f"({slowest[1]:.2f} seconds){Style.RESET_ALL}") + print(f" {Fore.CYAN}⚡ Fastest project: {fastest[0]} " + f"({fastest[1]:.2f} seconds){Style.RESET_ALL}") + print(f" {Fore.BLUE}📈 Average duration: {avg_time:.2f} seconds{Style.RESET_ALL}") + print(f" {Fore.BLUE}📊 Median duration: {median(times):.2f} seconds{Style.RESET_ALL}") + + # Pareto 80% cumulative time + sorted_durations = sorted(durations, key=lambda x: x[1], reverse=True) + cumulative = 0.0 + cutoff = 0.8 * sum(times) + pareto_list = [] + for name, t in sorted_durations: + cumulative += t + pareto_list.append((name, t)) + if cumulative >= cutoff: + break + + print(f"\n{Fore.MAGENTA}📌 Top 80% time contributors (Pareto){Style.RESET_ALL}") + for name, t in pareto_list: + print(f" {Fore.YELLOW}{name:<20}{t:.2f} sec{Style.RESET_ALL}") + + # Histogram chart with color coding + percentile + cumulative % + print(f"\n{Fore.MAGENTA}📊 Runtime Histogram (with percentiles & cumulative){Style.RESET_ALL}") + max_time = slowest[1] + scale = 40 / max_time if max_time > 0 else 1 + total_time = sum(times) + cumulative = 0.0 + + for name, t in sorted_durations: + cumulative += t + bar = "█" * int(t * scale) + # Color by relative speed + if t <= 0.75 * avg_time: + color = Fore.GREEN + elif t <= 1.25 * avg_time: + color = Fore.YELLOW + else: + color = Fore.RED + + # Percentile marker + if t <= q25: + marker = " (≤25th %ile)" + elif t <= q50: + marker = " (≤50th %ile)" + elif t <= q75: + marker = " (≤75th %ile)" + else: + marker = " (>75th %ile)" + + # Cumulative percentage + cum_pct = (cumulative / total_time) * 100 + print(f" {name:<20} {color}{bar}{Style.RESET_ALL} {t:.2f}s{marker} " + f"→ {cum_pct:5.1f}% total") + + # Exit code mirrors success/failure + if passed_tests < total_tests: + sys.exit(1) + +if __name__ == "__main__": + run_tests() \ No newline at end of file diff --git a/tests/esxi/shelllog/main.ts b/tests/esxi/shelllog/main.ts new file mode 100644 index 00000000..88e05989 --- /dev/null +++ b/tests/esxi/shelllog/main.ts @@ -0,0 +1,13 @@ +import { testShellLogHistory } from "../../test"; + +function main() { + console.log('Running ESXi Shell Log tests....'); + + console.log(' Starting ESXi Shell Log test....'); + testShellLogHistory(); + + console.log(' Shell Log test passed! 🥳'); + console.log('All ESXi Shell Log tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/esxi/syslog/main.ts b/tests/esxi/syslog/main.ts new file mode 100644 index 00000000..8aa5adc3 --- /dev/null +++ b/tests/esxi/syslog/main.ts @@ -0,0 +1,13 @@ +import { testSyslogEsxi } from "../../test"; + +function main() { + console.log('Running ESXi Syslog tests....'); + + console.log(' Starting ESXi Syslog test....'); + testSyslogEsxi(); + + console.log(' Syslog test passed! 🥳'); + console.log('All ESXi Syslog tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/esxi/vibs/main.ts b/tests/esxi/vibs/main.ts new file mode 100644 index 00000000..633dc8ee --- /dev/null +++ b/tests/esxi/vibs/main.ts @@ -0,0 +1,13 @@ +import { testGetVibs } from "../../test"; + +function main() { + console.log('Running ESXi VIBs tests....'); + + console.log(' Starting ESXi VIBs test....'); + testGetVibs(); + + console.log(' VIBs test passed! 🥳'); + console.log('All ESXi VIBs tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/linux/ual/main.ts b/tests/linux/ual/main.ts new file mode 100644 index 00000000..48b42c67 --- /dev/null +++ b/tests/linux/ual/main.ts @@ -0,0 +1,11 @@ +import { testUserAccessLogging } from "../../test"; + +function main() { + console.log('Running Windows UAL tests (on Linux)....'); + console.log(' Running UAL tests'); + testUserAccessLogging(); + console.log(' UAL tests completed 🥳\n') + console.log('All Windows UAL tests passed! (on Linux) 🥳💃🕺'); +} + +main(); diff --git a/tests/macos/lulu/main.ts b/tests/macos/lulu/main.ts new file mode 100644 index 00000000..29149a38 --- /dev/null +++ b/tests/macos/lulu/main.ts @@ -0,0 +1,24 @@ +import { luluRules } from "../../../mod"; +import { MacosError } from "../../../src/macos/errors"; +import { testLuluRules } from "../../test"; + +function main() { + console.log('Running macOS LuLu tests....'); + console.log(' Starting live test....'); + const results = luluRules(); + if (results instanceof MacosError) { + throw results; + } + if (results.length !== 0 && results[ 0 ]?.file === "") { + throw 'empty file?'; + } + console.log(' Live test passed! 🥳\n'); + + console.log(' Starting LuLu test....'); + testLuluRules(); + console.log(' LuLu test passed! 🥳'); + + console.log('All LuLu tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/test.ts b/tests/test.ts index b159bc70..a63e1747 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -14,6 +14,9 @@ export { testChromiumPreferences } from "../src/applications/chromium/preference export { testChromiumLocalStorage } from "../src/applications/chromium/level"; export { testChromiumSessions } from "../src/applications/chromium/sessions"; export { testChromiumSqlite } from "../src/applications/chromium/sqlite"; +export { testFirefoxJsonFiles } from "../src/applications/firefox/json"; +export { testChromiumCache } from "../src/applications/chromium/cache"; +export { testRustDeskLogs } from "../src/applications/rustdesk/logs"; /** * Linux exported test functions @@ -39,15 +42,26 @@ export { testServiceInstalls } from "../src/windows/eventlogs/services"; export { testRdpLogons } from "../src/windows/eventlogs/rdp"; export { testParsePca } from "../src/windows/pca"; export { testDefenderQuarantineEventLog } from "../src/windows/eventlogs/defender"; - +export { testMsiInstalled } from "../src/windows/eventlogs/msi"; +export { testExtractAppCrash } from "../src/windows/appcrash"; +export { testUserAccessLogging } from "../src/windows/ese/ual"; /** * macOS exported test functions */ export { testHomebrew } from "../src/macos/homebrew"; export { testParseBom } from "../src/macos/bom"; +export { testLuluRules } from "../src/macos/plist/lulu"; /** * HTTP exported test functions */ export { testCircluHashlookup } from "../src/http/circlu"; -export { testCheckEolStatus } from "../src/http/eol"; \ No newline at end of file +export { testCheckEolStatus } from "../src/http/eol"; + +/** + * ESXi exported test functions + */ +export { testShellLogHistory } from "../src/esxi/logs/shell"; +export { testGetVibs } from "../src/esxi/vib"; +export { testSyslogEsxi } from "../src/esxi/logs/syslog"; +export { testEsxiAccounts } from "../src/esxi/accounts"; \ No newline at end of file diff --git a/tests/test_data/DFIRArtifactMuseum/eventlogs/Application.evtx b/tests/test_data/DFIRArtifactMuseum/eventlogs/Application.evtx new file mode 100644 index 00000000..2291583d Binary files /dev/null and b/tests/test_data/DFIRArtifactMuseum/eventlogs/Application.evtx differ diff --git a/tests/test_data/DFIRArtifactMuseum/ual/Current.mdb b/tests/test_data/DFIRArtifactMuseum/ual/Current.mdb new file mode 100644 index 00000000..2653ab65 Binary files /dev/null and b/tests/test_data/DFIRArtifactMuseum/ual/Current.mdb differ diff --git a/tests/test_data/DFIRArtifactMuseum/ual/SystemIdentity.mdb b/tests/test_data/DFIRArtifactMuseum/ual/SystemIdentity.mdb new file mode 100644 index 00000000..6cd96d51 Binary files /dev/null and b/tests/test_data/DFIRArtifactMuseum/ual/SystemIdentity.mdb differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_0 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_0 new file mode 100644 index 00000000..df2bbda6 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_1 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_1 new file mode 100644 index 00000000..a3f85e09 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_1 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_2 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_2 new file mode 100644 index 00000000..26733e24 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_2 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_3 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_3 new file mode 100644 index 00000000..df1071c6 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/data_3 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000001 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000001 new file mode 100644 index 00000000..65e8338f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000001 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000002 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000002 new file mode 100644 index 00000000..d15208de Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000002 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000003 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000003 new file mode 100644 index 00000000..941cefaf Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000003 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000004 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000004 new file mode 100644 index 00000000..d45b3f1d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000004 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000005 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000005 new file mode 100644 index 00000000..ad7c8eae Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000005 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000006 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000006 new file mode 100644 index 00000000..4eb939f8 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000006 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000007 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000007 new file mode 100644 index 00000000..39f654d9 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000007 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000008 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000008 new file mode 100644 index 00000000..b5c64423 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000008 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000009 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000009 new file mode 100644 index 00000000..053d1a93 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000009 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000a b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000a new file mode 100644 index 00000000..4be8925a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000a differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000b b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000b new file mode 100644 index 00000000..1f91c929 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000b differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000c b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000c new file mode 100644 index 00000000..d1a0db75 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000c differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000d b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000d new file mode 100644 index 00000000..d99a30b2 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000d differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000e b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000e new file mode 100644 index 00000000..528e8480 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000e differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000f b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000f new file mode 100644 index 00000000..24caec14 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00000f differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000010 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000010 new file mode 100644 index 00000000..5ba90498 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000010 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000011 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000011 new file mode 100644 index 00000000..9830cafb Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000011 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000012 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000012 new file mode 100644 index 00000000..e5ac97b0 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000012 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000013 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000013 new file mode 100644 index 00000000..295b3592 --- /dev/null +++ b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000013 @@ -0,0 +1 @@ +!function r(o,i,s){function a(t,e){if(!i[t]){if(!o[t]){var n="function"==typeof require&&require;if(!e&&n)return n(t,!0);if(c)return c(t,!0);throw(n=new Error("Cannot find module '"+t+"'")).code="MODULE_NOT_FOUND",n}n=i[t]={exports:{}},o[t][0].call(n.exports,function(e){return a(o[t][1][e]||e)},n,n.exports,r,o,i,s)}return i[t].exports}for(var c="function"==typeof require&&require,e=0;e>18&63,n=o>>12&63,r=o>>6&63,o=63&o,a[s++]=p.charAt(t)+p.charAt(n)+p.charAt(r)+p.charAt(o),ii.length){var a=p(e);if(c!==null)e.seed=c;i.push(a)}e.count=o;return i}t=l(e);n=f(t,e);r=d(t,n,e);return h([t,n,r],e)};function l(e){if(u.length>0){var t=P(e.hue);var n=_(t);var r=(t[1]-t[0])/u.length;var o=parseInt((n-t[0])/r);if(u[o]===true)o=(o+2)%u.length;else u[o]=true;var i=(t[0]+o*r)%359,s=(t[0]+(o+1)*r)%359;t=[i,s];n=_(t);if(n<0)n=360+n;return n}else{var t=g(e.hue);n=_(t);if(n<0)n=360+n;return n}}function f(e,t){if(t.hue==="monochrome")return 0;if(t.luminosity==="random")return _([0,100]);var n=s(e);var r=n[0],o=n[1];switch(t.luminosity){case"bright":r=55;break;case"dark":r=o-10;break;case"light":o=55;break}return _([r,o])}function d(e,t,n){var r=i(e,t),o=100;switch(n.luminosity){case"dark":o=r+20;break;case"light":r=(o+r)/2;break;case"random":r=0;o=100;break}return _([r,o])}function h(e,t){switch(t.format){case"hsvArray":return e;case"hslArray":return m(e);case"hsl":var n=m(e);return"hsl("+n[0]+", "+n[1]+"%, "+n[2]+"%)";case"hsla":var r=m(e);var o=t.alpha||Math.random();return"hsla("+r[0]+", "+r[1]+"%, "+r[2]+"%, "+o+")";case"rgbArray":return y(e);case"rgb":var i=y(e);return"rgb("+i.join(", ")+")";case"rgba":var s=y(e);var o=t.alpha||Math.random();return"rgba("+s.join(", ")+", "+o+")";default:return b(e)}}function i(e,t){var n=v(e).lowerBounds;for(var r=0;r=o&&t<=s){var c=(a-i)/(s-o),u=i-c*o;return c*t+u}}return 0}function g(e){if(typeof parseInt(e)==="number"){var t=parseInt(e);if(t<360&&t>0)return[t,t]}if(typeof e==="string")if(a[e]){var n=a[e];if(n.hueRange)return n.hueRange}else if(e.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)){var r=o(e)[0];return[r,r]}return[0,360]}function s(e){return v(e).saturationRange}function v(e){if(e>=334&&e<=360)e-=360;for(var t in a){var n=a[t];if(n.hueRange&&e>=n.hueRange[0]&&e<=n.hueRange[1])return a[t]}return"Color not found"}function _(e){if(c===null){var t=.618033988749895;var n=Math.random();n+=t;n%=1;return Math.floor(e[0]+n*(e[1]+1-e[0]))}else{var r=e[1]||1;var o=e[0]||0;c=(c*9301+49297)%233280;var i=c/233280;return Math.floor(o+i*(r-o))}}function b(e){var t=y(e);function n(e){var t=e.toString(16);return t.length==1?"0"+t:t}var r="#"+n(t[0])+n(t[1])+n(t[2]);return r}function e(e,t,n){var r=n[0][0],o=n[n.length-1][0],i=n[n.length-1][1],s=n[0][1];a[e]={hueRange:t,lowerBounds:n,saturationRange:[r,o],brightnessRange:[i,s]}}function t(){e("monochrome",null,[[0,0],[100,0]]);e("red",[-26,18],[[20,100],[30,92],[40,89],[50,85],[60,78],[70,70],[80,60],[90,55],[100,50]]);e("orange",[18,46],[[20,100],[30,93],[40,88],[50,86],[60,85],[70,70],[100,70]]);e("yellow",[46,62],[[25,100],[40,94],[50,89],[60,86],[70,84],[80,82],[90,80],[100,75]]);e("green",[62,178],[[30,100],[40,90],[50,85],[60,81],[70,74],[80,64],[90,50],[100,40]]);e("blue",[178,257],[[20,100],[30,86],[40,80],[50,74],[60,60],[70,52],[80,44],[90,39],[100,35]]);e("purple",[257,282],[[20,100],[30,87],[40,79],[50,70],[60,65],[70,59],[80,52],[90,45],[100,42]]);e("pink",[282,334],[[20,100],[30,90],[40,86],[60,84],[80,80],[90,75],[100,73]])}function y(e){var t=e[0];if(t===0)t=1;if(t===360)t=359;t=t/360;var n=e[1]/100,r=e[2]/100;var o=Math.floor(t*6),i=t*6-o,s=r*(1-n),a=r*(1-i*n),c=r*(1-(1-i)*n),u=256,p=256,l=256;switch(o){case 0:u=r;p=c;l=s;break;case 1:u=a;p=r;l=s;break;case 2:u=s;p=r;l=c;break;case 3:u=s;p=a;l=r;break;case 4:u=c;p=s;l=r;break;case 5:u=r;p=s;l=a;break}var f=[Math.floor(u*255),Math.floor(p*255),Math.floor(l*255)];return f}function o(e){e=e.replace(/^#/,"");e=e.length===3?e.replace(/(.)/g,"$1$1"):e;var t=parseInt(e.substr(0,2),16)/255,n=parseInt(e.substr(2,2),16)/255,r=parseInt(e.substr(4,2),16)/255;var o=Math.max(t,n,r),i=o-Math.min(t,n,r),s=o?i/o:0;switch(o){case t:return[60*((n-r)/i%6)||0,s,o];case n:return[60*((r-t)/i+2)||0,s,o];case r:return[60*((t-n)/i+4)||0,s,o]}}function m(e){var t=e[0],n=e[1]/100,r=e[2]/100,o=(2-n)*r;return[t,Math.round(n*r/(o<1?o:2-o)*1e4)/100,o/2*100]}function w(e){var t=0;for(var n=0;n!==e.length;n++){if(t>=Number.MAX_SAFE_INTEGER)break;t+=e.charCodeAt(n)}return t}function P(e){if(!isNaN(e)){var t=parseInt(e);if(t<360&&t>0)return v(e).hueRange}else if(typeof e==="string")if(a[e]){var n=a[e];if(n.hueRange)return n.hueRange}else if(e.match(/^#?([0-9A-F]{3}|[0-9A-F]{6})$/i)){var r=o(e)[0];return v(r).hueRange}return[0,360]}return p}(),(n=t&&t.exports?t.exports=r:n).randomColor=r;var v=o.exports;e.DebuggerPlugin=function(t){var o,i,n;function r(e,t,n){var n=(r=null!=n?n:[""])[0],r=r.slice(1);o.debug.apply(o,function(e,t,n){if(n||2===arguments.length)for(var r,o=0,i=t.length;o=e.rules[n].s&&r[n].e<=e.rules[n].e)){t="N/A";break}if(t=0,t+=Math.abs(r[n].s-e.rules[n].s),(t+=Math.abs(e.rules[n].e-r[n].e))>l.MAX_SCORE){t="N/A";break}}return function(e,t,n,r){if("N/A"!==n)return n;if("Asia/Beirut"===t){if("Africa/Cairo"===r.name&&13983768e5===e[6].s&&14116788e5===e[6].e)return 0;if("Asia/Jerusalem"===r.name&&13959648e5===e[6].s&&14118588e5===e[6].e)return 0}else if("America/Santiago"===t){if("America/Asuncion"===r.name&&14124816e5===e[6].s&&1397358e6===e[6].e)return 0;if("America/Campo_Grande"===r.name&&14136912e5===e[6].s&&13925196e5===e[6].e)return 0}else if("America/Montevideo"===t){if("America/Sao_Paulo"===r.name&&14136876e5===e[6].s&&1392516e6===e[6].e)return 0}else if("Pacific/Auckland"===t&&"Pacific/Fiji"===r.name&&14142456e5===e[6].s&&13961016e5===e[6].e)return 0;return n}(r,o,t,e)}(n[a]);"N/A"!==u&&(t[c.name]=u)}for(e in t)if(t.hasOwnProperty(e))for(var p=0;p>>((3&t)<<3)&255;return n});for(var i=[],s=0;s<256;++s)i[s]=(s+256).toString(16).substr(1);var p,l,a=function(e,t){var n=t||0;return[(t=i)[e[n++]],t[e[n++]],t[e[n++]],t[e[n++]],"-",t[e[n++]],t[e[n++]],"-",t[e[n++]],t[e[n++]],"-",t[e[n++]],t[e[n++]],"-",t[e[n++]],t[e[n++]],t[e[n++]],t[e[n++]],t[e[n++]],t[e[+n]]].join("")},f=r.exports,h=a,g=0,v=0;function c(e,t,n){var r=t&&n||0,o=t||[],i=(e=e||{}).node||p,s=void 0!==e.clockseq?e.clockseq:l;null!=i&&null!=s||(c=f(),null==i&&(i=p=[1|c[0],c[1],c[2],c[3],c[4],c[5]]),null==s&&(s=l=16383&(c[6]<<8|c[7])));var a=void 0!==e.msecs?e.msecs:(new Date).getTime(),n=void 0!==e.nsecs?e.nsecs:v+1,c=a-g+(n-v)/1e4;if(c<0&&void 0===e.clockseq&&(s=s+1&16383),1e4<=(n=(c<0||g>>24&255,o[r++]=n>>>16&255,o[r++]=n>>>8&255,o[r++]=255&n,a=a/4294967296*1e4&268435455,o[r++]=a>>>8&255,o[r++]=255&a,o[r++]=a>>>24&15|16,o[r++]=a>>>16&255,o[r++]=s>>>8|128,o[r++]=255&s;for(var u=0;u<6;++u)o[r+u]=i[u];return t||h(o)}var u=r.exports,_=a;r=function(e,t,n){var r=t&&n||0;"string"==typeof e&&(t="binary"===e?new Array(16):null,e=null);var o=(e=e||{}).random||(e.rng||u)();if(o[6]=15&o[6]|64,o[8]=63&o[8]|128,t)for(var i=0;i<16;++i)t[r+i]=o[i];return t||_(o)},a=r;a.v1=c,a.v4=r;var Ae=a;function b(e){return e&&function(e){var t,n,r,o,i=0,s=0,a=[];if(!e)return e;e=unescape(encodeURIComponent(e));for(;t=e.charCodeAt(i++),n=e.charCodeAt(i++),r=e.charCodeAt(i++),t=(o=t<<16|n<<8|r)>>18&63,n=o>>12&63,r=o>>6&63,o=63&o,a[s++]=y.charAt(t)+y.charAt(n)+y.charAt(r)+y.charAt(o),i=P.warn&&"undefined"!=typeof console&&(e=O+e,t?console.warn.apply(console,d([e+"\n",t],n,!1)):console.warn.apply(console,d([e],n,!1)))},error:function(e,t){for(var n=[],r=2;r=P.error&&"undefined"!=typeof console&&(e=O+e+"\n",t?console.error.apply(console,d([e+"\n",t],n,!1)):console.error.apply(console,d([e],n,!1)))},debug:function(e){for(var t=[],n=1;n=P.debug&&"undefined"!=typeof console&&console.debug.apply(console,d([O+e],t,!1))},info:function(e){for(var t=[],n=1;n=P.info&&"undefined"!=typeof console&&console.info.apply(console,d([O+e],t,!1))}}}();function C(){var c=[],u=[];return{getGlobalPrimitives:function(){return c},getConditionalProviders:function(){return u},addGlobalContexts:function(e){for(var t=[],n=[],r=0,o=e;r>>32-t},rotr:function(e,t){return e<<32-t|e>>>t},endian:function(e){if(e.constructor==Number)return 16711935&B.rotl(e,8)|4278255360&B.rotl(e,24);for(var t=0;t>>5]|=e[n]<<24-r%32;return t},wordsToBytes:function(e){for(var t=[],n=0;n<32*e.length;n+=8)t.push(e[n>>>5]>>>24-n%32&255);return t},bytesToHex:function(e){for(var t=[],n=0;n>>4).toString(16)),t.push((15&e[n]).toString(16));return t.join("")},hexToBytes:function(e){for(var t=[],n=0;n>>6*(3-o)&63)):t.push("=");return t.join("")},base64ToBytes:function(e){e=e.replace(/[^A-Z0-9+\/]/gi,"");for(var t=[],n=0,r=0;n>>6-2*r);return t}},r.exports=B;var z,F,G,Z={utf8:{stringToBytes:function(e){return Z.bin.stringToBytes(unescape(encodeURIComponent(e)))},bytesToString:function(e){return decodeURIComponent(escape(Z.bin.bytesToString(e)))}},bin:{stringToBytes:function(e){for(var t=[],n=0;n>5]|=128<<24-e%32,t[15+(64+e>>>9<<4)]=e;for(var c=0;c>>31);var g=(r<<5|r>>>27)+a+(n[h]>>>0)+(h<20?1518500249+(o&i|~o&s):h<40?1859775393+(o^i^s):h<60?(o&i|o&s|i&s)-1894007588:(o^i^s)-899497514),a=s,s=i,i=o<<30|o>>>2,o=r,r=g}r+=u,o+=p,i+=l,s+=f,a+=d}return[r,o,i,s,a]}(e)),t&&t.asBytes?e:t&&t.asString?G.bytesToString(e):z.bytesToHex(e)}z=r.exports,F=Z.utf8,G=Z.bin,H._blocksize=16,H._digestsize=20,a.exports=H;var De=a.exports;function q(){var e="modernizr";if(!function(){try{return window.localStorage}catch(e){return 1}}())return!1;try{var t=window.localStorage;return t.setItem(e,e),t.removeItem(e),!0}catch(e){return!1}}function W(e){return!(!e||"string"!=typeof e.valueOf())}function Ve(e){return Number.isInteger&&Number.isInteger(e)||"number"==typeof e&&isFinite(e)&&Math.floor(e)===e}function Le(e){var t;return W(e)||(e=e.text||"",(t=document.getElementsByTagName("title"))&&null!=t[0]&&(e=t[0].text)),e}function Ue(e){var t=new RegExp("^(?:(?:https?|ftp):)/*(?:[^@]+@)?([^:/#]+)").exec(e);return t?t[1]:e}function Re(e){var t=e.length;return e="*."===(e="."===e.charAt(--t)?e.slice(0,t):e).slice(0,2)?e.slice(1):e}function Be(e){var t=window,n=Fe("referrer",t.location.href)||Fe("referer",t.location.href);if(n)return n;if(e)return e;try{if(t.top)return t.top.document.referrer;if(t.parent)return t.parent.document.referrer}catch(e){}return document.referrer}function ze(e,t,n,r){return e.addEventListener?(e.addEventListener(t,n,r),!0):e.attachEvent?e.attachEvent("on"+t,n):void(e["on"+t]=n)}function Fe(e,t){t=new RegExp("^[^#]*[?&]"+e+"=([^&#]*)").exec(t);return t?decodeURIComponent(t[1].replace(/\+/g," ")):null}function Ge(e,t,n){void 0===n&&(n=63072e3);try{var r=window.localStorage,o=Date.now()+1e3*n;return r.setItem("".concat(e,".expires"),o.toString()),r.setItem(e,t),!0}catch(e){return!1}}function Ze(e){try{var t=window.localStorage;return t.removeItem(e),t.removeItem(e+".expires"),1}catch(e){return}}function He(e,t){for(var n=window.location.hostname,r="_sp_root_domain_test_",o=r+(new Date).getTime(),i="_test_value_"+(new Date).getTime(),s=n.split("."),a=s.length-1;0<=a;){var c=s.slice(a,s.length).join(".");if(We(o,i,0,"/",c,e,t),We(o)===i){qe(o,c,e,t);for(var u=function(e){for(var t=document.cookie.split("; "),n=[],r=0;r=p)return n(o.bytes,p),void M(o,g);_.push(o)}else{var r=function(e){var t,n,r="?",o={co:!0,cx:!0},i=!0;for(t in e)e.hasOwnProperty(t)&&!o.hasOwnProperty(t)&&(i?i=!1:r+="&",r+=encodeURIComponent(t)+"="+encodeURIComponent(e[t]));for(n in o)e.hasOwnProperty(n)&&o.hasOwnProperty(n)&&(r+="&"+n+"="+encodeURIComponent(e[n]));return r}(e);if(0=a)||x()},executeQueue:function(){v||x()},setUseLocalStorage:function(e){u=e},setAnonymousTracking:function(e){d=e},setCollectorUrl:function(e){g=e+O},setBufferSize:function(e){a=e}}}function Qe(e,t,n){var r,o;return"translate.googleusercontent.com"===e?(""===n&&(n=t),e=Ue(t=null!==(r=t,o="u",r=(r=new RegExp("^(?:https?|ftp)(?::/*(?:[^?]+))([?][^#]+)").exec(r))&&1<(null==r?void 0:r.length)?Fe(o,r[1]):null)&&void 0!==r?r:"")):"cc.bingj.com"!==e&&"webcache.googleusercontent.com"!==e||(e=Ue(t=document.links[0].href)),[e,t,n]}function J(e,t,n,r,o,i){var Me=[],i=function(e,t,n,r,o,i){i.eventMethod=null!==(j=i.eventMethod)&&void 0!==j?j:"post";function s(e){return null!==(e=e.stateStorageStrategy)&&void 0!==e?e:"cookieAndLocalStorage"}function a(e){return"boolean"!=typeof e.anonymousTracking&&(null!==(e=!0===(null===(e=e.anonymousTracking)||void 0===e?void 0:e.withSessionTracking))&&e)}function c(e){return"boolean"!=typeof e.anonymousTracking&&(null!==(e=!0===(null===(e=e.anonymousTracking)||void 0===e?void 0:e.withServerAnonymisation))&&e)}function u(e){return!!e.anonymousTracking}Me.push({beforeTrack:function(e){function t(e){return q?e:o(e)}var n,r,o=function(e){return J?null:e},i=Math.round((new Date).getTime()/1e3),s=ce("ses"),a=we(),c=a[0],u=a[1],p=a[2],l=a[3],f=a[4],d=a[5],h=a[6],a=!!_&&!!We(_);G||a?be():("0"===c?(O=h,s||"none"==Q||(l++,d=f,O=Ae.v4()),X=l):(new Date).getTime()-Y>1e3*H&&(O=Ae.v4(),X++),e.add("vp",(n="innerWidth"in window?(r=window.innerWidth,window.innerHeight):(r=(n=document.documentElement||document.body).clientWidth,n.clientHeight),0<=r&&0<=n?r+"x"+n:null)),e.add("ds",(l=document.documentElement,n=(r=document.body)?Math.max(r.offsetHeight,r.scrollHeight):0,r=Math.max(l.clientWidth,l.offsetWidth,l.scrollWidth),n=Math.max(l.clientHeight,l.offsetHeight,l.scrollHeight,n),isNaN(r)||isNaN(n)?"":r+"x"+n)),e.add("vid",t(X)),e.add("sid",t(O)),e.add("duid",o(u)),e.add("uid",o(C)),ne(),e.add("refr",ie(g||A)),e.add("url",ie(v||x)),"none"!=Q&&(ve(u,p,X,i,d,O),ge()),Y=(new Date).getTime())}}),null!==(T=null===(N=null==i?void 0:i.contexts)||void 0===N?void 0:N.webPage)&&void 0!==T&&!T||Me.push({contexts:function(){return[{schema:"iglu:com.snowplowanalytics.snowplow/web_page/jsonschema/1-0-0",data:{id:Ce()}}]}}),Me.push.apply(Me,null!==(D=i.plugins)&&void 0!==D?D:[]);var g,v,p,l,f,_,d,h,b,y,m,w,P,O,C,S=Ne({base64:i.encodeBase64,corePlugins:Me,callback:function(e){var t;t=!!_&&!!We(_);G||t||K.enqueueRequest(e.build(),E)}}),I=navigator.userLanguage||navigator.language,j=document.characterSet||document.charset,k=Qe(window.location.hostname,window.location.href,Be()),M=Re(k[0]),x=k[1],A=k[2],T=null!==(N=i.platform)&&void 0!==N?N:"web",E=Pe(r),N=null!==(D=i.postPath)&&void 0!==D?D:"/com.snowplowanalytics.snowplow/tp2",D=null!==(r=i.appId)&&void 0!==r?r:"",V=document.title,L=null===(r=i.resetActivityTrackingOnPageView)||void 0===r||r,U=null!==(r=i.cookieName)&&void 0!==r?r:"_sp_",R=null!==(r=i.cookieDomain)&&void 0!==r?r:void 0,B="/",z=null!==(r=i.cookieSameSite)&&void 0!==r?r:"None",F=null===(r=i.cookieSecure)||void 0===r||r,r=navigator.doNotTrack||navigator.msDoNotTrack||window.doNotTrack,G=void 0!==i.respectDoNotTrack&&(i.respectDoNotTrack&&("yes"===r||"1"===r)),Z=null!==(r=i.cookieLifetime)&&void 0!==r?r:63072e3,H=null!==(r=i.sessionCookieTimeout)&&void 0!==r?r:1800,q=a(i),W=c(i),J=u(i),Q=s(i),Y=(new Date).getTime(),X=1,K=Je(e,o,"localStorage"==Q||"cookieAndLocalStorage"==Q,i.eventMethod,N,null!==(N=i.bufferSize)&&void 0!==N?N:1,null!==(N=i.maxPostBytes)&&void 0!==N?N:4e4,null!==(N=i.maxGetBytes)&&void 0!==N?N:0,null===(N=i.useStm)||void 0===N||N,null!==(N=i.maxLocalStorageQueueSize)&&void 0!==N?N:1e3,null!==(N=i.connectionTimeout)&&void 0!==N?N:5e3,W,null!==(N=i.customHeaders)&&void 0!==N?N:{},null===(N=i.withCredentials)||void 0===N||N),$=!1,ee=!1,te={enabled:!1,installed:!1,configurations:{}};function ne(){(k=Qe(window.location.hostname,window.location.href,Be()))[1]!==x&&(A=Be(x)),M=Re(k[0]),x=k[1]}function re(e){var t=(new Date).getTime(),e=e.currentTarget;null!=e&&e.href&&(e.href=function(e,t,n){var r=t+"="+n,o=e.split("#"),e=(n=o[0].split("?")).shift(),i=n.join("?");if(i){for(var s=!0,a=i.split("&"),c=0;cDate.now()?t.getItem(e):(t.removeItem(e),void t.removeItem(e+".expires"))}catch(e){return}}(e):"cookie"==Q||"cookieAndLocalStorage"==Q?We(e):void 0}function ue(){ne(),w=De((R||M)+(B||"/")).slice(0,4)}function pe(){var e=new Date;d=e.getTime()}function le(){!function(){var e=fe(),t=e[0];te.getTime()&&i(t.callback,Se(n,r))}var i=function(e,t){ne(),e({context:t,pageViewId:Ce(),minXOffset:h,minYOffset:y,maxXOffset:b,maxYOffset:m}),de()};0!=t.configMinimumVisitLength?t.activityInterval=window.setTimeout(function(){var e=new Date;d+t.configMinimumVisitLength>e.getTime()&&i(t.callback,Se(n,r)),t.activityInterval=window.setInterval(o,t.configHeartBeatTimer)},t.configMinimumVisitLength):t.activityInterval=window.setInterval(o,t.configHeartBeatTimer)}(u,n,o))}}}function je(e){var t=e.minimumVisitLength,n=e.heartbeatDelay,e=e.callback;if(Ve(t)&&Ve(n))return{configMinimumVisitLength:1e3*t,configHeartBeatTimer:1e3*n,callback:e};Ee.error("Activity tracking minimumVisitLength & heartbeatDelay must be integers")}function ke(e){var t,n,r=e.context,o=e.minXOffset,i=e.minYOffset,s=e.maxXOffset,a=e.maxYOffset,c=document.title;c!==V&&(V=c,p=void 0),S.track((t={pageUrl:ie(v||x),pageTitle:Le(p||V),referrer:ie(g||A),minXOffset:he(o),maxXOffset:he(s),minYOffset:he(i),maxYOffset:he(a)},n=t.pageUrl,e=t.pageTitle,c=t.referrer,o=t.minXOffset,s=t.maxXOffset,i=t.minYOffset,a=t.maxYOffset,(t=Te()).add("e","pp"),t.add("url",n),t.add("page",e),t.add("refr",c),o&&!isNaN(Number(o))&&t.add("pp_mix",o.toString()),s&&!isNaN(Number(s))&&t.add("pp_max",s.toString()),i&&!isNaN(Number(i))&&t.add("pp_miy",i.toString()),a&&!isNaN(Number(a))&&t.add("pp_may",a.toString()),t),r)}i.hasOwnProperty("discoverRootDomain")&&i.discoverRootDomain&&(R=He(z,F)),S.setTrackerVersion(n),S.setTrackerNamespace(t),S.setAppId(D),S.setPlatform(T),S.addPayloadPair("cookie",navigator.cookieEnabled?"1":"0"),S.addPayloadPair("cs",j),S.addPayloadPair("lang",I),S.addPayloadPair("res",screen.width+"x"+screen.height),S.addPayloadPair("cd",screen.colorDepth),ue(),me(),i.crossDomainLinker&&oe(i.crossDomainLinker);I={getDomainSessionIndex:function(){return X},getPageViewId:Ce,newSession:function(){var e=Math.round((new Date).getTime()/1e3),t=(a=we())[0],n=a[1],r=a[2],o=a[3],i=a[4],s=a[5],a=a[6];"0"===t?(O=a,"none"!=Q&&(o++,s=i,O=Ae.v4()),X=o,ge()):(O=Ae.v4(),X++),"none"!=Q&&(ve(n,r,X,e,s,O),ge()),Y=(new Date).getTime()},getCookieName:ae,getUserId:function(){return C},getDomainUserId:function(){return we()[1]},getDomainUserInfo:we,setReferrerUrl:function(e){g=e},setCustomUrl:function(e){var t,n;ne(),t=x,v=se(n=e)?n:"/"===n.slice(0,1)?se(t)+"://"+Ue(t)+n:(t=(e=(t=0<=(e=(t=ie(t)).indexOf("?"))?t.slice(0,e):t).lastIndexOf("/"))!==t.length-1?t.slice(0,e+1):t)+n},setDocumentTitle:function(e){V=document.title,p=e},discardHashTag:function(e){l=e},discardBrace:function(e){f=e},setCookiePath:function(e){B=e,ue()},setVisitorCookieTimeout:function(e){Z=e},crossDomainLinker:function(e){oe(e)},enableActivityTracking:function(e){te.configurations.pagePing||(te.enabled=!0,te.configurations.pagePing=je(xe(xe({},e),{callback:ke})))},enableActivityTrackingCallback:function(e){te.configurations.callback||(te.enabled=!0,te.configurations.callback=je(e))},updatePageActivity:function(){pe()},setOptOutCookie:function(e){_=e},setUserId:function(e){C=e},setUserIdFromLocation:function(e){ne(),C=Fe(e,x)},setUserIdFromReferrer:function(e){ne(),C=Fe(e,A)},setUserIdFromCookie:function(e){C=We(e)},setCollectorUrl:function(e){E=Pe(e),K.setCollectorUrl(E)},setBufferSize:function(e){K.setBufferSize(e)},flushBuffer:function(e){void 0===e&&(e={}),K.executeQueue(),e.newBufferSize&&K.setBufferSize(e.newBufferSize)},trackPageView:function(e){Ie(e=void 0===e?{}:e)},preservePageViewId:function(){$=!0},disableAnonymousTracking:function(e){i.anonymousTracking=!1,ye(e),me(),K.executeQueue()},enableAnonymousTracking:function(e){var t;i.anonymousTracking=null===(t=e&&(null==e?void 0:e.options))||void 0===t||t,ye(e),q||Oe()},clearUserData:be};return xe(xe({},I),{id:e,namespace:t,core:S,sharedState:o})}(e,t,n,r,o,i=void 0===i?{}:i),s=xe(xe({},i),{addPlugin:function(e){var t;s.core.addPlugin(e),null!==(e=(t=e.plugin).activateBrowserPlugin)&&void 0!==e&&e.call(t,s)}});return Me.forEach(function(e){var t;null!==(t=e.activateBrowserPlugin)&&void 0!==t&&t.call(e,s)}),s}var Q={};function Y(e,t){try{(function(e,t){for(var n=[],r=0,o=e;r>16&255,i[s++]=t>>8&255,i[s++]=255&t;2===r&&(t=c[e.charCodeAt(n)]<<2|c[e.charCodeAt(n+1)]>>4,i[s++]=255&t);1===r&&(t=c[e.charCodeAt(n)]<<10|c[e.charCodeAt(n+1)]<<4|c[e.charCodeAt(n+2)]>>2,i[s++]=t>>8&255,i[s++]=255&t);return i},n.fromByteArray=function(e){for(var t,n=e.length,r=n%3,o=[],i=0,s=n-r;i>18&63]+a[e>>12&63]+a[e>>6&63]+a[63&e]}(r));return o.join("")}(e,i,s>2]+a[t<<4&63]+"==")):2==r&&(t=(e[n-2]<<8)+e[n-1],o.push(a[t>>10]+a[t>>4&63]+a[t<<2&63]+"="));return o.join("")};for(var a=[],c=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",o=0,i=r.length;o>>1;case"base64":return j(e).length;default:if(o)return r?-1:I(e).length;t=(""+t).toLowerCase(),o=!0}}function n(e,t,n){var r,o,i,s=!1;if((t=void 0===t||t<0?0:t)>this.length)return"";if((n=void 0===n||n>this.length?this.length:n)<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e=e||"utf8";;)switch(e){case"hex":return function(e,t,n){var r=e.length;(!t||t<0)&&(t=0);(!n||n<0||r=e.length){if(o)return-1;n=e.length-1}else if(n<0){if(!o)return-1;n=0}if("string"==typeof t&&(t=l.from(t,r)),l.isBuffer(t))return 0===t.length?-1:g(e,t,n,r,o);if("number"==typeof t)return t&=255,"function"==typeof Uint8Array.prototype.indexOf?(o?Uint8Array.prototype.indexOf:Uint8Array.prototype.lastIndexOf).call(e,t,n):g(e,[t],n,r,o);throw new TypeError("val must be string, number or Buffer")}function g(e,t,n,r,o){var i=1,s=e.length,a=t.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(e.length<2||t.length<2)return-1;s/=i=2,a/=2,n/=2}function c(e,t){return 1===i?e[t]:e.readUInt16BE(t*i)}if(o)for(var u=-1,p=n;p>8,r=r%256,o.push(r),o.push(n);return o}(t,e.length-n),e,n,r)}function b(e,t,n){n=Math.min(e.length,n);for(var r=[],o=t;o>>10&1023|55296),p=56320|1023&p),r.push(p),o+=l}return function(e){var t=e.length;if(t<=y)return String.fromCharCode.apply(String,e);var n="",r=0;for(;rt&&(e+=" ... "),""},l.prototype.compare=function(e,t,n,r,o){if(M(e,Uint8Array)&&(e=l.from(e,e.offset,e.byteLength)),!l.isBuffer(e))throw new TypeError('The "target" argument must be one of type Buffer or Uint8Array. Received type '+typeof e);if(void 0===n&&(n=e?e.length:0),void 0===r&&(r=0),void 0===o&&(o=this.length),(t=void 0===t?0:t)<0||n>e.length||r<0||o>this.length)throw new RangeError("out of range index");if(o<=r&&n<=t)return 0;if(o<=r)return-1;if(n<=t)return 1;if(this===e)return 0;for(var i=(o>>>=0)-(r>>>=0),s=(n>>>=0)-(t>>>=0),a=Math.min(i,s),c=this.slice(r,o),u=e.slice(t,n),p=0;p>>=0,isFinite(n)?(n>>>=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}var o=this.length-t;if((void 0===n||othis.length)throw new RangeError("Attempt to write outside buffer bounds");r=r||"utf8";for(var i,s,a,c=!1;;)switch(r){case"hex":return function(e,t,n,r){n=Number(n)||0;var o=e.length-n;(!r||o<(r=Number(r)))&&(r=o),(o=t.length)/2e.length)throw new RangeError("Index out of range")}function P(e,t,n,r){if(n+r>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function O(e,t,n,r,o){return t=+t,n>>>=0,o||P(e,0,n,4),i.write(e,t,n,r,23,4),n+4}function C(e,t,n,r,o){return t=+t,n>>>=0,o||P(e,0,n,8),i.write(e,t,n,r,52,8),n+8}l.prototype.slice=function(e,t){var n=this.length;(e=~~e)<0?(e+=n)<0&&(e=0):n>>=0,t>>>=0,n||m(e,t,this.length);for(var r=this[e],o=1,i=0;++i>>=0,t>>>=0,n||m(e,t,this.length);for(var r=this[e+--t],o=1;0>>=0,t||m(e,1,this.length),this[e]},l.prototype.readUInt16LE=function(e,t){return e>>>=0,t||m(e,2,this.length),this[e]|this[e+1]<<8},l.prototype.readUInt16BE=function(e,t){return e>>>=0,t||m(e,2,this.length),this[e]<<8|this[e+1]},l.prototype.readUInt32LE=function(e,t){return e>>>=0,t||m(e,4,this.length),(this[e]|this[e+1]<<8|this[e+2]<<16)+16777216*this[e+3]},l.prototype.readUInt32BE=function(e,t){return e>>>=0,t||m(e,4,this.length),16777216*this[e]+(this[e+1]<<16|this[e+2]<<8|this[e+3])},l.prototype.readIntLE=function(e,t,n){e>>>=0,t>>>=0,n||m(e,t,this.length);for(var r=this[e],o=1,i=0;++i>>=0,t>>>=0,n||m(e,t,this.length);for(var r=t,o=1,i=this[e+--r];0>>=0,t||m(e,1,this.length),128&this[e]?-1*(255-this[e]+1):this[e]},l.prototype.readInt16LE=function(e,t){e>>>=0,t||m(e,2,this.length);e=this[e]|this[e+1]<<8;return 32768&e?4294901760|e:e},l.prototype.readInt16BE=function(e,t){e>>>=0,t||m(e,2,this.length);e=this[e+1]|this[e]<<8;return 32768&e?4294901760|e:e},l.prototype.readInt32LE=function(e,t){return e>>>=0,t||m(e,4,this.length),this[e]|this[e+1]<<8|this[e+2]<<16|this[e+3]<<24},l.prototype.readInt32BE=function(e,t){return e>>>=0,t||m(e,4,this.length),this[e]<<24|this[e+1]<<16|this[e+2]<<8|this[e+3]},l.prototype.readFloatLE=function(e,t){return e>>>=0,t||m(e,4,this.length),i.read(this,e,!0,23,4)},l.prototype.readFloatBE=function(e,t){return e>>>=0,t||m(e,4,this.length),i.read(this,e,!1,23,4)},l.prototype.readDoubleLE=function(e,t){return e>>>=0,t||m(e,8,this.length),i.read(this,e,!0,52,8)},l.prototype.readDoubleBE=function(e,t){return e>>>=0,t||m(e,8,this.length),i.read(this,e,!1,52,8)},l.prototype.writeUIntLE=function(e,t,n,r){e=+e,t>>>=0,n>>>=0,r||w(this,e,t,n,Math.pow(2,8*n)-1,0);var o=1,i=0;for(this[t]=255&e;++i>>=0,n>>>=0,r||w(this,e,t,n,Math.pow(2,8*n)-1,0);var o=n-1,i=1;for(this[t+o]=255&e;0<=--o&&(i*=256);)this[t+o]=e/i&255;return t+n},l.prototype.writeUInt8=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,1,255,0),this[t]=255&e,t+1},l.prototype.writeUInt16LE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,2,65535,0),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeUInt16BE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,2,65535,0),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeUInt32LE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,4,4294967295,0),this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e,t+4},l.prototype.writeUInt32BE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,4,4294967295,0),this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeIntLE=function(e,t,n,r){e=+e,t>>>=0,r||w(this,e,t,n,(r=Math.pow(2,8*n-1))-1,-r);var o=0,i=1,s=0;for(this[t]=255&e;++o>0)-s&255;return t+n},l.prototype.writeIntBE=function(e,t,n,r){e=+e,t>>>=0,r||w(this,e,t,n,(r=Math.pow(2,8*n-1))-1,-r);var o=n-1,i=1,s=0;for(this[t+o]=255&e;0<=--o&&(i*=256);)e<0&&0===s&&0!==this[t+o+1]&&(s=1),this[t+o]=(e/i>>0)-s&255;return t+n},l.prototype.writeInt8=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,1,127,-128),this[t]=255&(e=e<0?255+e+1:e),t+1},l.prototype.writeInt16LE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,2,32767,-32768),this[t]=255&e,this[t+1]=e>>>8,t+2},l.prototype.writeInt16BE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,2,32767,-32768),this[t]=e>>>8,this[t+1]=255&e,t+2},l.prototype.writeInt32LE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,4,2147483647,-2147483648),this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24,t+4},l.prototype.writeInt32BE=function(e,t,n){return e=+e,t>>>=0,n||w(this,e,t,4,2147483647,-2147483648),this[t]=(e=e<0?4294967295+e+1:e)>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e,t+4},l.prototype.writeFloatLE=function(e,t,n){return O(this,e,t,!0,n)},l.prototype.writeFloatBE=function(e,t,n){return O(this,e,t,!1,n)},l.prototype.writeDoubleLE=function(e,t,n){return C(this,e,t,!0,n)},l.prototype.writeDoubleBE=function(e,t,n){return C(this,e,t,!1,n)},l.prototype.copy=function(e,t,n,r){if(!l.isBuffer(e))throw new TypeError("argument should be a Buffer");if(n=n||0,r||0===r||(r=this.length),t>=e.length&&(t=e.length),(r=0=this.length)throw new RangeError("Index out of range");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length);var o=(r=e.length-t>>=0,n=void 0===n?this.length:n>>>0,"number"==typeof(e=e||0))for(i=t;i>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;i.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;i.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return i}function j(e){return a.toByteArray(function(e){if((e=(e=e.split("=")[0]).trim().replace(S,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function k(e,t,n,r){for(var o=0;o=t.length||o>=e.length);++o)t[o+n]=e[o];return o}function M(e,t){return e instanceof t||null!=e&&null!=e.constructor&&null!=e.constructor.name&&e.constructor.name===t.name}function x(e){return e!=e}}.call(this)}.call(this,A("buffer").Buffer)},{"base64-js":9,buffer:10,ieee754:11}],11:[function(e,t,n){n.read=function(e,t,n,r,o){var i,s,a=8*o-r-1,c=(1<>1,p=-7,l=n?o-1:0,f=n?-1:1,n=e[t+l];for(l+=f,i=n&(1<<-p)-1,n>>=-p,p+=a;0>=-p,p+=r;0>1,l=23===o?Math.pow(2,-24)-Math.pow(2,-77):0,f=r?0:i-1,d=r?1:-1,i=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(a=isNaN(t)?1:0,s=u):(s=Math.floor(Math.log(t)/Math.LN2),t*(r=Math.pow(2,-s))<1&&(s--,r*=2),2<=(t+=1<=s+p?l/r:l*Math.pow(2,1-p))*r&&(s++,r/=2),u<=s+p?(a=0,s=u):1<=s+p?(a=(t*r-1)*Math.pow(2,o),s+=p):(a=t*Math.pow(2,p-1)*Math.pow(2,o),s=0));8<=o;e[n+f]=255&a,f+=d,a/=256,o-=8);for(s=s<i.value&&(i.value=s,i.entries=a,o()))}var o,i=u("CLS",0),s=0,a=[],c=p("layout-shift",r);c&&(o=f(n,i,e),l(function(){c.takeRecords().map(r),o(!0)}),b(function(){m=-1,i=u("CLS",s=0),o=f(n,i,e)}))},e.getFCP=d,e.getFID=function(e,t){function n(e){e.startTimeperformance.now())return;n.entries=[e],t(n)}catch(e){}};"complete"===document.readyState?setTimeout(e,0):addEventListener("pageshow",e)},Object.defineProperty(e,"__esModule",{value:!0})},"object"==typeof n&&void 0!==t?o(n):"function"==typeof define&&define.amd?define(["exports"],o):o((r="undefined"!=typeof globalThis?globalThis:r||self).webVitals={})},{}],13:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.StorageServiceFactory=void 0;var r=e("./clients/WebStorageService"),e=(o.assignStorageService=function(e){return new r.StorageService(e)},o);function o(){}n.StorageServiceFactory=e},{"./clients/WebStorageService":14}],14:[function(e,t,n){"use strict";var r=this&&this.__awaiter||function(e,s,a,c){return new(a=a||Promise)(function(n,t){function r(e){try{i(c.next(e))}catch(e){t(e)}}function o(e){try{i(c.throw(e))}catch(e){t(e)}}function i(e){var t;e.done?n(e.value):((t=e.value)instanceof a?t:new a(function(e){e(t)})).then(r,o)}i((c=c.apply(e,s||[])).next())})},o=this&&this.__generator||function(n,r){var o,i,s,a={label:0,sent:function(){if(1&s[0])throw s[1];return s[1]},trys:[],ops:[]},c=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return c.next=e(0),c.throw=e(1),c.return=e(2),"function"==typeof Symbol&&(c[Symbol.iterator]=function(){return this}),c;function e(t){return function(e){return function(t){if(o)throw new TypeError("Generator is already executing.");for(;c&&t[c=0]&&(a=0),a;)try{if(o=1,i&&(s=2&t[0]?i.return:t[0]?i.throw||((s=i.return)&&s.call(i),0):i.next)&&!(s=s.call(i,t[1])).done)return s;switch(i=0,(t=s?[2&t[0],s.value]:t)[0]){case 0:case 1:s=t;break;case 4:return a.label++,{value:t[1],done:!1};case 5:a.label++,i=t[1],t=[0];continue;case 7:t=a.ops.pop(),a.trys.pop();continue;default:if(!(s=0<(s=a.trys).length&&s[s.length-1])&&(6===t[0]||2===t[0])){a=0;continue}if(3===t[0]&&(!s||t[1]>s[0]&&t[1]s[0]&&t[1]s[0]&&t[1]s[0]&&t[1]s[0]&&t[1]s[0]&&t[1]s[0]&&t[1]s[0]&&t[1]n||o-t.created>r)?(i=new u.SessionEntry({created:o,expires:o+18e5,updated:o,value:(0,c.uuidv4)()}),[4,this.store.remove("zion.session_history")]):[3,2];case 1:return e.sent(),[3,3];case 2:i=new u.SessionEntry(s(s({},t),{updated:o})),e.label=3;case 3:return[4,this.store.set(i)];case 4:return e.sent(),this.sessionIdCache=i.value,[2,i]}})})},this.get=function(){return n.config},this.getSessionIdCacheValue=function(){return n.sessionIdCache},this.getSessionId=function(){return r(n,void 0,void 0,function(){var t;return a(this,function(e){switch(e.label){case 0:return this.config&&this.config.enableLogging&&console.log("getSessionId called on the SessionManager with config: ",this.config),[4,this.store.get()];case 1:return(t=e.sent())?[2,t.value]:[2,this.refreshSessionId()]}})})},this.refreshSessionId=function(){return r(n,void 0,void 0,function(){var t;return a(this,function(e){switch(e.label){case 0:return this.config&&this.config.enableLogging&&console.log("refreshSessionId called on the SessionManager with config: ",this.config),t=this.refresh,[4,this.store.get()];case 1:return[4,t.apply(this,[e.sent()])];case 2:return[2,e.sent().value]}})})},this.config=e,this.store=t,this.sessionTimeout=e.sessionTimeout||432e5,this.idleTimeout=e.idleTimeout||18e5},e=(l.prototype.assignClient=function(e,t){return e&&e.enableLogging&&console.log("Found client. Assigning to manager."),new p(e,new i.SessionStoreImpl(e,t))},l);function l(){}n.SessionManagerFactory=e},{"./stores/SessionEntry":103,"./stores/SessionStoreImpl":104,"@cnnprivate/zion-common-v3":96}],103:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.SessionEntry=void 0,n.SessionEntry=function(e){e&&(this.created=e.created,this.expires=e.expires,this.updated=e.updated,this.value=e.value)}},{}],104:[function(e,t,r){"use strict";var o=this&&this.__awaiter||function(e,s,a,c){return new(a=a||Promise)(function(n,t){function r(e){try{i(c.next(e))}catch(e){t(e)}}function o(e){try{i(c.throw(e))}catch(e){t(e)}}function i(e){var t;e.done?n(e.value):((t=e.value)instanceof a?t:new a(function(e){e(t)})).then(r,o)}i((c=c.apply(e,s||[])).next())})},i=this&&this.__generator||function(n,r){var o,i,s,a={label:0,sent:function(){if(1&s[0])throw s[1];return s[1]},trys:[],ops:[]},c=Object.create(("function"==typeof Iterator?Iterator:Object).prototype);return c.next=e(0),c.throw=e(1),c.return=e(2),"function"==typeof Symbol&&(c[Symbol.iterator]=function(){return this}),c;function e(t){return function(e){return function(t){if(o)throw new TypeError("Generator is already executing.");for(;c&&t[c=0]&&(a=0),a;)try{if(o=1,i&&(s=2&t[0]?i.return:t[0]?i.throw||((s=i.return)&&s.call(i),0):i.next)&&!(s=s.call(i,t[1])).done)return s;switch(i=0,(t=s?[2&t[0],s.value]:t)[0]){case 0:case 1:s=t;break;case 4:return a.label++,{value:t[1],done:!1};case 5:a.label++,i=t[1],t=[0];continue;case 7:t=a.ops.pop(),a.trys.pop();continue;default:if(!(s=0<(s=a.trys).length&&s[s.length-1])&&(6===t[0]||2===t[0])){a=0;continue}if(3===t[0]&&(!s||t[1]>s[0]&&t[1]s[0]&&t[1]/DA+AAAAAAAAAAAABQaAfc3agAAoAiZDVUVJAAAAAQDgAACktx4OEHdiZC1jbm4taGxuLWZhc3Q0AQEAAANoH4M=AAAC2HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAri4AgAAAQABAK4CPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBrADAAawBCAEEAVwBsAEQAZABKAGUAbgBOAG4AVgBIADQAWgByAGYAUgBBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AGEASABZAHIAVQBlAFcAbQBCAE0ASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBuAHkAdwBlAGIAbQBzAGQAcgBtADAAMQBkAC4AaABiAG8ALgBoAG8AbQBlAGIAbwB4AC4AYwBvAG0ALwBQAGwAYQB5AFIAZQBhAGQAeQAtAGQAaQByAGUAYwB0AC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=1AAAAknBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAHISEAEASLBRTeVDOquBLFkvMScSEAEBSZNDaZd0pzZ1R+Ga30QSEAEC3qkFeosL0b1rQckRJSkSEAED9ofE4Sq3/4VhCBgGDtISEAEErAOyDgnQcGjvCCXYzUQSEAEFShsiHq62tROJfN1sQZpI49yVmwY=AAAAknBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAHISEAEASLBRTeVDOquBLFkvMScSEAEBSZNDaZd0pzZ1R+Ga30QSEAEC3qkFeosL0b1rQckRJSkSEAED9ofE4Sq3/4VhCBgGDtISEAEErAOyDgnQcGjvCCXYzUQSEAEFShsiHq62tROJfN1sQZpI49yVmwY=AAAC2HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAri4AgAAAQABAK4CPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBxAGQANABDAEEAWABvAEYAQwA0AHYAUgB2AFcAdABCAHkAUgBFAGwASwBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEgARgBkAGoARgB5AHIAcQAxAGwAZwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBuAHkAdwBlAGIAbQBzAGQAcgBtADAAMQBkAC4AaABiAG8ALgBoAG8AbQBlAGIAbwB4AC4AYwBvAG0ALwBQAGwAYQB5AFIAZQBhAGQAeQAtAGQAaQByAGUAYwB0AC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=1AAAC2HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAri4AgAAAQABAK4CPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBzAEUAZwBBAEEAVQAxAFIAUQArAFUANgBxADQARQBzAFcAUwA4AHgASgB3AD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AG8AbgBiAEoAbwBHAFAASAA3AE4AdwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBuAHkAdwBlAGIAbQBzAGQAcgBtADAAMQBkAC4AaABiAG8ALgBoAG8AbQBlAGIAbwB4AC4AYwBvAG0ALwBQAGwAYQB5AFIAZQBhAGQAeQAtAGQAaQByAGUAYwB0AC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=1AAAAknBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAHISEAEASLBRTeVDOquBLFkvMScSEAEBSZNDaZd0pzZ1R+Ga30QSEAEC3qkFeosL0b1rQckRJSkSEAED9ofE4Sq3/4VhCBgGDtISEAEErAOyDgnQcGjvCCXYzUQSEAEFShsiHq62tROJfN1sQZpI49yVmwY=/DA3AAAAAAAAAAAABQaAfnKaRwAhAh9DVUVJAAAAAQCgDhB3YmQtY25uLWhsbi1mYXN0NQEB82O9KA==AAAC2HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAri4AgAAAQABAK4CPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBrADAAawBCAEEAVwBsAEQAZABKAGUAbgBOAG4AVgBIADQAWgByAGYAUgBBAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AGEASABZAHIAVQBlAFcAbQBCAE0ASQA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBuAHkAdwBlAGIAbQBzAGQAcgBtADAAMQBkAC4AaABiAG8ALgBoAG8AbQBlAGIAbwB4AC4AYwBvAG0ALwBQAGwAYQB5AFIAZQBhAGQAeQAtAGQAaQByAGUAYwB0AC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=1AAAAknBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAHISEAEASLBRTeVDOquBLFkvMScSEAEBSZNDaZd0pzZ1R+Ga30QSEAEC3qkFeosL0b1rQckRJSkSEAED9ofE4Sq3/4VhCBgGDtISEAEErAOyDgnQcGjvCCXYzUQSEAEFShsiHq62tROJfN1sQZpI49yVmwY=AAAAknBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAHISEAEASLBRTeVDOquBLFkvMScSEAEBSZNDaZd0pzZ1R+Ga30QSEAEC3qkFeosL0b1rQckRJSkSEAED9ofE4Sq3/4VhCBgGDtISEAEErAOyDgnQcGjvCCXYzUQSEAEFShsiHq62tROJfN1sQZpI49yVmwY=AAAC2HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAri4AgAAAQABAK4CPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBxAGQANABDAEEAWABvAEYAQwA0AHYAUgB2AFcAdABCAHkAUgBFAGwASwBRAD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AEgARgBkAGoARgB5AHIAcQAxAGwAZwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBuAHkAdwBlAGIAbQBzAGQAcgBtADAAMQBkAC4AaABiAG8ALgBoAG8AbQBlAGIAbwB4AC4AYwBvAG0ALwBQAGwAYQB5AFIAZQBhAGQAeQAtAGQAaQByAGUAYwB0AC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=1AAAC2HBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAri4AgAAAQABAK4CPABXAFIATQBIAEUAQQBEAEUAUgAgAHgAbQBsAG4AcwA9ACIAaAB0AHQAcAA6AC8ALwBzAGMAaABlAG0AYQBzAC4AbQBpAGMAcgBvAHMAbwBmAHQALgBjAG8AbQAvAEQAUgBNAC8AMgAwADAANwAvADAAMwAvAFAAbABhAHkAUgBlAGEAZAB5AEgAZQBhAGQAZQByACIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMAAuADAALgAwACIAPgA8AEQAQQBUAEEAPgA8AFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBFAFkATABFAE4APgAxADYAPAAvAEsARQBZAEwARQBOAD4APABBAEwARwBJAEQAPgBBAEUAUwBDAFQAUgA8AC8AQQBMAEcASQBEAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwASwBJAEQAPgBzAEUAZwBBAEEAVQAxAFIAUQArAFUANgBxADQARQBzAFcAUwA4AHgASgB3AD0APQA8AC8ASwBJAEQAPgA8AEMASABFAEMASwBTAFUATQA+AG8AbgBiAEoAbwBHAFAASAA3AE4AdwA9ADwALwBDAEgARQBDAEsAUwBVAE0APgA8AEwAQQBfAFUAUgBMAD4AaAB0AHQAcAA6AC8ALwBuAHkAdwBlAGIAbQBzAGQAcgBtADAAMQBkAC4AaABiAG8ALgBoAG8AbQBlAGIAbwB4AC4AYwBvAG0ALwBQAGwAYQB5AFIAZQBhAGQAeQAtAGQAaQByAGUAYwB0AC8AcgBpAGcAaAB0AHMAbQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APAAvAEQAQQBUAEEAPgA8AC8AVwBSAE0ASABFAEEARABFAFIAPgA=1AAAAknBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAHISEAEASLBRTeVDOquBLFkvMScSEAEBSZNDaZd0pzZ1R+Ga30QSEAEC3qkFeosL0b1rQckRJSkSEAED9ofE4Sq3/4VhCBgGDtISEAEErAOyDgnQcGjvCCXYzUQSEAEFShsiHq62tROJfN1sQZpI49yVmwY= \ No newline at end of file diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00004f b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00004f new file mode 100644 index 00000000..bb78925c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_00004f differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000050 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000050 new file mode 100644 index 00000000..32abc27c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000050 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000051 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000051 new file mode 100644 index 00000000..f5bae714 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000051 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000052 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000052 new file mode 100644 index 00000000..1b1d190b Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000052 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000053 b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000053 new file mode 100644 index 00000000..f4f3fd8c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/f_000053 differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/index b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/index new file mode 100644 index 00000000..3e718653 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/Cache_Data/index differ diff --git a/tests/test_data/brave/v143.1.85.11/Cache/No_Vary_Search/journal.baj b/tests/test_data/brave/v143.1.85.11/Cache/No_Vary_Search/journal.baj new file mode 100644 index 00000000..54fe66eb --- /dev/null +++ b/tests/test_data/brave/v143.1.85.11/Cache/No_Vary_Search/journal.baj @@ -0,0 +1 @@ +$F~ \ No newline at end of file diff --git a/tests/test_data/brave/v143.1.85.11/Cache/No_Vary_Search/snapshot.baf b/tests/test_data/brave/v143.1.85.11/Cache/No_Vary_Search/snapshot.baf new file mode 100644 index 00000000..8912405f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Cache/No_Vary_Search/snapshot.baf differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/004fbc7e98844ea1_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/004fbc7e98844ea1_0 new file mode 100644 index 00000000..3eb3d610 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/004fbc7e98844ea1_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/01ad5fed55aec09d_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/01ad5fed55aec09d_0 new file mode 100644 index 00000000..d969ab9e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/01ad5fed55aec09d_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/05eef45c4ffe4d55_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/05eef45c4ffe4d55_0 new file mode 100644 index 00000000..e8bd9335 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/05eef45c4ffe4d55_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/062687caa45d93fc_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/062687caa45d93fc_0 new file mode 100644 index 00000000..267ad231 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/062687caa45d93fc_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/0a03be047f30b6c0_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/0a03be047f30b6c0_0 new file mode 100644 index 00000000..cddf287d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/0a03be047f30b6c0_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/0c98c15f5e20c59d_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/0c98c15f5e20c59d_0 new file mode 100644 index 00000000..17ff02cd Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/0c98c15f5e20c59d_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/0ed72285c297450f_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/0ed72285c297450f_0 new file mode 100644 index 00000000..5885a98b Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/0ed72285c297450f_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/156af69da3722831_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/156af69da3722831_0 new file mode 100644 index 00000000..add736c5 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/156af69da3722831_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/15bb132773cee6e6_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/15bb132773cee6e6_0 new file mode 100644 index 00000000..ead34f47 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/15bb132773cee6e6_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/15e9770be3760751_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/15e9770be3760751_0 new file mode 100644 index 00000000..7d47cb75 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/15e9770be3760751_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/1684acdd4635144a_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1684acdd4635144a_0 new file mode 100644 index 00000000..8f5df5ad Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1684acdd4635144a_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/1ce5b2378f4c61f4_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1ce5b2378f4c61f4_0 new file mode 100644 index 00000000..69ebfad6 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1ce5b2378f4c61f4_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/1d9efc11f1b962dc_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1d9efc11f1b962dc_0 new file mode 100644 index 00000000..bc4cc534 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1d9efc11f1b962dc_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/1f05ecbd08cfd234_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1f05ecbd08cfd234_0 new file mode 100644 index 00000000..89325246 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/1f05ecbd08cfd234_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/247796fb4130cec0_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/247796fb4130cec0_0 new file mode 100644 index 00000000..14936004 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/247796fb4130cec0_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/24a9cad142669639_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/24a9cad142669639_0 new file mode 100644 index 00000000..af452f81 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/24a9cad142669639_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/250d85268f9991aa_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/250d85268f9991aa_0 new file mode 100644 index 00000000..2a79d232 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/250d85268f9991aa_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/25fae3e7b620897a_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/25fae3e7b620897a_0 new file mode 100644 index 00000000..6e591ed7 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/25fae3e7b620897a_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/288e52a528f66cfd_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/288e52a528f66cfd_0 new file mode 100644 index 00000000..5755973c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/288e52a528f66cfd_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/28eadf52b9767be5_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/28eadf52b9767be5_0 new file mode 100644 index 00000000..1d29ce3d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/28eadf52b9767be5_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2a54e744b501bc57_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2a54e744b501bc57_0 new file mode 100644 index 00000000..82de25c2 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2a54e744b501bc57_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2b0d88ad47d3cdeb_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2b0d88ad47d3cdeb_0 new file mode 100644 index 00000000..213f6ba8 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2b0d88ad47d3cdeb_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2d403ab20903bb2c_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2d403ab20903bb2c_0 new file mode 100644 index 00000000..144175a5 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2d403ab20903bb2c_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2d95b7e27cf8930d_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2d95b7e27cf8930d_0 new file mode 100644 index 00000000..a6b4f67d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2d95b7e27cf8930d_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2e7d9221bd5e11de_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2e7d9221bd5e11de_0 new file mode 100644 index 00000000..83b72fb7 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2e7d9221bd5e11de_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2ec8d90130f0788c_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2ec8d90130f0788c_0 new file mode 100644 index 00000000..36cf491e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2ec8d90130f0788c_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/2f7241ba512fab34_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2f7241ba512fab34_0 new file mode 100644 index 00000000..bbc0f32d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/2f7241ba512fab34_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/312cf04ea15702d3_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/312cf04ea15702d3_0 new file mode 100644 index 00000000..ea1bc492 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/312cf04ea15702d3_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/31975226e37a8124_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/31975226e37a8124_0 new file mode 100644 index 00000000..18b1dcb5 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/31975226e37a8124_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/333464b45501b1ed_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/333464b45501b1ed_0 new file mode 100644 index 00000000..95976b9f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/333464b45501b1ed_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/33c949d4f7588a7f_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/33c949d4f7588a7f_0 new file mode 100644 index 00000000..30e421cb Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/33c949d4f7588a7f_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/3904f87ba66a4583_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/3904f87ba66a4583_0 new file mode 100644 index 00000000..70ea24bf Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/3904f87ba66a4583_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/3c81d77fae8b877d_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/3c81d77fae8b877d_0 new file mode 100644 index 00000000..12487e68 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/3c81d77fae8b877d_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/402eee7c26269f59_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/402eee7c26269f59_0 new file mode 100644 index 00000000..807319d3 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/402eee7c26269f59_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/4267adc68e8f4b06_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4267adc68e8f4b06_0 new file mode 100644 index 00000000..5131111e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4267adc68e8f4b06_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/43cf973d1c03b24f_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/43cf973d1c03b24f_0 new file mode 100644 index 00000000..5f6ccaf8 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/43cf973d1c03b24f_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/4a9ad7c7efd9275c_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4a9ad7c7efd9275c_0 new file mode 100644 index 00000000..691c5489 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4a9ad7c7efd9275c_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/4d85a634d0e4e977_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4d85a634d0e4e977_0 new file mode 100644 index 00000000..05185677 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4d85a634d0e4e977_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/4fd18adbfec05169_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4fd18adbfec05169_0 new file mode 100644 index 00000000..e2e2c0a2 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/4fd18adbfec05169_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/50a4043604d15155_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/50a4043604d15155_0 new file mode 100644 index 00000000..6227e8b8 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/50a4043604d15155_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/50db903ed286eb59_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/50db903ed286eb59_0 new file mode 100644 index 00000000..8100390d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/50db903ed286eb59_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5116dfb2b1df1f6c_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5116dfb2b1df1f6c_0 new file mode 100644 index 00000000..bcbf3dc1 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5116dfb2b1df1f6c_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/51ab724b0ef82ff3_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/51ab724b0ef82ff3_0 new file mode 100644 index 00000000..a61df807 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/51ab724b0ef82ff3_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5255ee2a886d5385_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5255ee2a886d5385_0 new file mode 100644 index 00000000..6ad28721 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5255ee2a886d5385_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5304377104e0810b_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5304377104e0810b_0 new file mode 100644 index 00000000..4d0d0952 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5304377104e0810b_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/53b6e06e47def719_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/53b6e06e47def719_0 new file mode 100644 index 00000000..7034feff Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/53b6e06e47def719_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/558e54a4911c72c6_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/558e54a4911c72c6_0 new file mode 100644 index 00000000..c255b493 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/558e54a4911c72c6_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/570c1f2a3739b6c8_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/570c1f2a3739b6c8_0 new file mode 100644 index 00000000..be736d4f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/570c1f2a3739b6c8_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5b71010aabb576e3_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5b71010aabb576e3_0 new file mode 100644 index 00000000..c631f014 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5b71010aabb576e3_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5bce222c2ba4b55e_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5bce222c2ba4b55e_0 new file mode 100644 index 00000000..390e178d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5bce222c2ba4b55e_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5d5c9b2443db9595_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5d5c9b2443db9595_0 new file mode 100644 index 00000000..3da91294 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5d5c9b2443db9595_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5f19f3908793b3b2_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5f19f3908793b3b2_0 new file mode 100644 index 00000000..4b84cd76 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5f19f3908793b3b2_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5fb7533807e6c574_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5fb7533807e6c574_0 new file mode 100644 index 00000000..fdfd7c4b Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5fb7533807e6c574_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/5fd001735f9b7e48_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5fd001735f9b7e48_0 new file mode 100644 index 00000000..0a3fb5f3 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/5fd001735f9b7e48_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/6100ca099aa4619a_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6100ca099aa4619a_0 new file mode 100644 index 00000000..38871b04 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6100ca099aa4619a_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/618f472027d0ca44_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/618f472027d0ca44_0 new file mode 100644 index 00000000..e8e1e536 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/618f472027d0ca44_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/61f53f6d15e22ba6_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/61f53f6d15e22ba6_0 new file mode 100644 index 00000000..536c3d15 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/61f53f6d15e22ba6_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/63f4285a327a57ea_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/63f4285a327a57ea_0 new file mode 100644 index 00000000..a8836a59 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/63f4285a327a57ea_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/64605b4cae4fbe31_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/64605b4cae4fbe31_0 new file mode 100644 index 00000000..bf18adaa Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/64605b4cae4fbe31_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/652ab95abfa4c7fe_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/652ab95abfa4c7fe_0 new file mode 100644 index 00000000..75f09aca Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/652ab95abfa4c7fe_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/679c1fc8e90ab148_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/679c1fc8e90ab148_0 new file mode 100644 index 00000000..9de3ff8e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/679c1fc8e90ab148_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/6b416c4e7d00e6a1_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6b416c4e7d00e6a1_0 new file mode 100644 index 00000000..91877647 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6b416c4e7d00e6a1_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/6d7e071a93ac21ee_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6d7e071a93ac21ee_0 new file mode 100644 index 00000000..0aa59d22 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6d7e071a93ac21ee_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/6dfcab5ae58c4ced_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6dfcab5ae58c4ced_0 new file mode 100644 index 00000000..04712be4 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/6dfcab5ae58c4ced_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/750b28103f2b1fc4_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/750b28103f2b1fc4_0 new file mode 100644 index 00000000..cbd4ad76 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/750b28103f2b1fc4_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/76e16b2b16211212_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/76e16b2b16211212_0 new file mode 100644 index 00000000..84df7f35 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/76e16b2b16211212_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/77af00562ec3b821_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/77af00562ec3b821_0 new file mode 100644 index 00000000..69d857b5 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/77af00562ec3b821_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/7b3bbecd4909433b_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7b3bbecd4909433b_0 new file mode 100644 index 00000000..3a6e06a8 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7b3bbecd4909433b_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/7c1178175ed8ddad_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7c1178175ed8ddad_0 new file mode 100644 index 00000000..53f8647e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7c1178175ed8ddad_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/7c6c6b5d83ae9d23_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7c6c6b5d83ae9d23_0 new file mode 100644 index 00000000..2367faf4 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7c6c6b5d83ae9d23_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/7d08fdd2e485f278_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7d08fdd2e485f278_0 new file mode 100644 index 00000000..a04ba65c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7d08fdd2e485f278_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/7eae5badde72d3ea_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7eae5badde72d3ea_0 new file mode 100644 index 00000000..e1d9b6cd Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7eae5badde72d3ea_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/7fd6d8393f0b4c14_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7fd6d8393f0b4c14_0 new file mode 100644 index 00000000..65d38088 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/7fd6d8393f0b4c14_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/80e02a87ba0199ea_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/80e02a87ba0199ea_0 new file mode 100644 index 00000000..222f284f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/80e02a87ba0199ea_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/81a2a524474277b4_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/81a2a524474277b4_0 new file mode 100644 index 00000000..65bd1a9e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/81a2a524474277b4_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/848bed1b4b3a9b6b_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/848bed1b4b3a9b6b_0 new file mode 100644 index 00000000..d35d8d54 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/848bed1b4b3a9b6b_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/84cb0fdac5c65742_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/84cb0fdac5c65742_0 new file mode 100644 index 00000000..9ef5e62e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/84cb0fdac5c65742_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/893542bd122c96dd_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/893542bd122c96dd_0 new file mode 100644 index 00000000..981b698f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/893542bd122c96dd_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/8f3a0dfa4844a0c6_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/8f3a0dfa4844a0c6_0 new file mode 100644 index 00000000..e4f9707a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/8f3a0dfa4844a0c6_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/8f5fc3f2c75dcd09_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/8f5fc3f2c75dcd09_0 new file mode 100644 index 00000000..5c29ef84 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/8f5fc3f2c75dcd09_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/900e49fa42d6da18_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/900e49fa42d6da18_0 new file mode 100644 index 00000000..2247de5d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/900e49fa42d6da18_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/908ef9c5874442b3_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/908ef9c5874442b3_0 new file mode 100644 index 00000000..70facd7d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/908ef9c5874442b3_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/9114e48260d22a78_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9114e48260d22a78_0 new file mode 100644 index 00000000..39f446a2 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9114e48260d22a78_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/95153516f4c21144_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/95153516f4c21144_0 new file mode 100644 index 00000000..5eadde07 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/95153516f4c21144_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/97367bd0213322ce_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/97367bd0213322ce_0 new file mode 100644 index 00000000..703eb50d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/97367bd0213322ce_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/97d074eedd957c4b_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/97d074eedd957c4b_0 new file mode 100644 index 00000000..d9b81d32 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/97d074eedd957c4b_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/986e94acb53c3f28_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/986e94acb53c3f28_0 new file mode 100644 index 00000000..4d8df34a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/986e94acb53c3f28_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/9996c895911adb56_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9996c895911adb56_0 new file mode 100644 index 00000000..e98aa0f2 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9996c895911adb56_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/99c31a3c691c3102_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/99c31a3c691c3102_0 new file mode 100644 index 00000000..e47efdda Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/99c31a3c691c3102_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/9c080556db16659b_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9c080556db16659b_0 new file mode 100644 index 00000000..0a34a8e1 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9c080556db16659b_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/9c3b468b21561c0e_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9c3b468b21561c0e_0 new file mode 100644 index 00000000..72b6f1c9 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9c3b468b21561c0e_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/9d299701caa20b61_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9d299701caa20b61_0 new file mode 100644 index 00000000..943a6910 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9d299701caa20b61_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/9ff63e260f305a7f_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9ff63e260f305a7f_0 new file mode 100644 index 00000000..ab953b3f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/9ff63e260f305a7f_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/a1c67bda6c319549_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a1c67bda6c319549_0 new file mode 100644 index 00000000..bed743ef Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a1c67bda6c319549_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/a21431f976a04eae_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a21431f976a04eae_0 new file mode 100644 index 00000000..d1dd1000 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a21431f976a04eae_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/a5a43b80ac9c8f18_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a5a43b80ac9c8f18_0 new file mode 100644 index 00000000..d5d81c4f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a5a43b80ac9c8f18_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/a65f3a4aeff178ba_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a65f3a4aeff178ba_0 new file mode 100644 index 00000000..5d555f14 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a65f3a4aeff178ba_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/a88469e334bec92f_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a88469e334bec92f_0 new file mode 100644 index 00000000..99ad4b0d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a88469e334bec92f_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/a9ca71977bd4d000_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a9ca71977bd4d000_0 new file mode 100644 index 00000000..1391205c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/a9ca71977bd4d000_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ad2e58c6fd2decaf_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ad2e58c6fd2decaf_0 new file mode 100644 index 00000000..aae707df Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ad2e58c6fd2decaf_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ae97986479d58eac_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ae97986479d58eac_0 new file mode 100644 index 00000000..0bd5ed90 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ae97986479d58eac_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/af1a98ec05fb7375_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/af1a98ec05fb7375_0 new file mode 100644 index 00000000..8d999dee Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/af1a98ec05fb7375_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/b8d7faa20a362ea4_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/b8d7faa20a362ea4_0 new file mode 100644 index 00000000..57717d2d Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/b8d7faa20a362ea4_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/c35ef7759ccc01b6_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c35ef7759ccc01b6_0 new file mode 100644 index 00000000..c0318434 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c35ef7759ccc01b6_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/c3916fff1f6af4d8_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c3916fff1f6af4d8_0 new file mode 100644 index 00000000..2850ffc7 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c3916fff1f6af4d8_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/c3af557ea9e53d9c_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c3af557ea9e53d9c_0 new file mode 100644 index 00000000..3c8c297f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c3af557ea9e53d9c_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/c7f16163b4137069_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c7f16163b4137069_0 new file mode 100644 index 00000000..24292e61 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c7f16163b4137069_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/c85d7a38bd35b934_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c85d7a38bd35b934_0 new file mode 100644 index 00000000..175c978c Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/c85d7a38bd35b934_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ca45d4976d02da2e_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ca45d4976d02da2e_0 new file mode 100644 index 00000000..80aed111 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ca45d4976d02da2e_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/cb6ab257193944f0_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/cb6ab257193944f0_0 new file mode 100644 index 00000000..52bd08a6 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/cb6ab257193944f0_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/cbbb585380af4185_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/cbbb585380af4185_0 new file mode 100644 index 00000000..5152d023 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/cbbb585380af4185_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/cc8264952d2b011f_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/cc8264952d2b011f_0 new file mode 100644 index 00000000..f02a55a1 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/cc8264952d2b011f_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ccddd8c16d1a6451_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ccddd8c16d1a6451_0 new file mode 100644 index 00000000..1723ce17 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ccddd8c16d1a6451_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ce4698c0f5f8dd95_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ce4698c0f5f8dd95_0 new file mode 100644 index 00000000..504485a6 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ce4698c0f5f8dd95_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/d1bb1ef28b960dd4_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/d1bb1ef28b960dd4_0 new file mode 100644 index 00000000..6903c03a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/d1bb1ef28b960dd4_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/e01f3e76fe242776_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e01f3e76fe242776_0 new file mode 100644 index 00000000..39b0d54b Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e01f3e76fe242776_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/e039b972f2f64270_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e039b972f2f64270_0 new file mode 100644 index 00000000..9d395281 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e039b972f2f64270_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/e39892bee47af434_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e39892bee47af434_0 new file mode 100644 index 00000000..9f503e8e Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e39892bee47af434_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/e60b67510577c276_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e60b67510577c276_0 new file mode 100644 index 00000000..2c542570 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e60b67510577c276_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/e9e0e3b0ec7acdc2_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e9e0e3b0ec7acdc2_0 new file mode 100644 index 00000000..f75aa9dc Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/e9e0e3b0ec7acdc2_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ec9c342e3b0e7ac7_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ec9c342e3b0e7ac7_0 new file mode 100644 index 00000000..d70b0ff6 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ec9c342e3b0e7ac7_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/f258d9af56d0db9d_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/f258d9af56d0db9d_0 new file mode 100644 index 00000000..56ec0d1f Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/f258d9af56d0db9d_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/f4aad66163d81784_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/f4aad66163d81784_0 new file mode 100644 index 00000000..a7d51172 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/f4aad66163d81784_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/f6f61f096738cdf4_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/f6f61f096738cdf4_0 new file mode 100644 index 00000000..725c0086 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/f6f61f096738cdf4_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/ff1749fe6f560985_0 b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ff1749fe6f560985_0 new file mode 100644 index 00000000..da3fe58a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/ff1749fe6f560985_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/index b/tests/test_data/brave/v143.1.85.11/Code Cache/js/index new file mode 100644 index 00000000..79bd403a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/index differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/js/index-dir/the-real-index b/tests/test_data/brave/v143.1.85.11/Code Cache/js/index-dir/the-real-index new file mode 100644 index 00000000..b16f2576 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/js/index-dir/the-real-index differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/wasm/index b/tests/test_data/brave/v143.1.85.11/Code Cache/wasm/index new file mode 100644 index 00000000..79bd403a Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/wasm/index differ diff --git a/tests/test_data/brave/v143.1.85.11/Code Cache/wasm/index-dir/the-real-index b/tests/test_data/brave/v143.1.85.11/Code Cache/wasm/index-dir/the-real-index new file mode 100644 index 00000000..c63e99da Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/Code Cache/wasm/index-dir/the-real-index differ diff --git a/tests/test_data/brave/v143.1.85.11/GPUCache/data_0 b/tests/test_data/brave/v143.1.85.11/GPUCache/data_0 new file mode 100644 index 00000000..615270f0 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/GPUCache/data_0 differ diff --git a/tests/test_data/brave/v143.1.85.11/GPUCache/data_1 b/tests/test_data/brave/v143.1.85.11/GPUCache/data_1 new file mode 100644 index 00000000..fcc0ab88 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/GPUCache/data_1 differ diff --git a/tests/test_data/brave/v143.1.85.11/GPUCache/data_2 b/tests/test_data/brave/v143.1.85.11/GPUCache/data_2 new file mode 100644 index 00000000..6381df71 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/GPUCache/data_2 differ diff --git a/tests/test_data/brave/v143.1.85.11/GPUCache/data_3 b/tests/test_data/brave/v143.1.85.11/GPUCache/data_3 new file mode 100644 index 00000000..5eec9735 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/GPUCache/data_3 differ diff --git a/tests/test_data/brave/v143.1.85.11/GPUCache/index b/tests/test_data/brave/v143.1.85.11/GPUCache/index new file mode 100644 index 00000000..8dff0f79 Binary files /dev/null and b/tests/test_data/brave/v143.1.85.11/GPUCache/index differ diff --git a/tests/test_data/esxi/accounts/passwd.txt b/tests/test_data/esxi/accounts/passwd.txt new file mode 100644 index 00000000..a985d519 --- /dev/null +++ b/tests/test_data/esxi/accounts/passwd.txt @@ -0,0 +1,4 @@ +root:x:0:0:Administrator:/:/bin/sh +dcui:x:100:100:DCUI User:/:/bin/sh +vpxuser:x:500:100:VMware VirtualCenter administration account:/:/bin/sh +testUser:x:1000:1000:ESXi User:/:/bin/sh diff --git a/tests/test_data/esxi/logs/shell.log b/tests/test_data/esxi/logs/shell.log new file mode 100644 index 00000000..fc44958b --- /dev/null +++ b/tests/test_data/esxi/logs/shell.log @@ -0,0 +1,164 @@ +2026-04-03T23:08:06.065Z No(13) ESXShell[132707]: ESXi Shell unavailable +2026-04-03T23:13:31.786Z No(13) ESXShell[132622]: ESXi Shell unavailable +2026-04-03T23:15:52.435Z No(13) SSH[132771]: SSH login enabled +2026-04-03T23:15:52.508Z No(13) SSH[132775]: SSH sandbox is not enabled, SSH will run in superdom +2026-04-03T23:16:32.866Z In(14) shell[132915]: Interactive shell session started +2026-04-03T23:16:33.976Z In(14) shell[132915]: [root]: ls +2026-04-03T23:17:03.211Z In(14) shell[132915]: [root]: uname -r +2026-04-03T23:17:05.310Z In(14) shell[132915]: [root]: uname +2026-04-03T23:17:41.733Z In(14) shell[132915]: [root]: which ls +2026-04-03T23:17:45.046Z In(14) shell[132915]: [root]: file /bin/ls +2026-04-03T23:18:04.630Z In(14) shell[132915]: [root]: ps +2026-04-03T23:19:45.875Z In(14) shell[132915]: [root]: pwd +2026-04-03T23:20:10.164Z In(14) shell[132915]: [root]: ls +2026-04-03T23:20:13.351Z In(14) shell[132915]: [root]: touch test +2026-04-03T23:20:14.537Z In(14) shell[132915]: [root]: ls +2026-04-03T23:20:17.736Z In(14) shell[132915]: [root]: rm test +2026-04-03T23:20:26.695Z In(14) shell[132915]: [root]: pwd +2026-04-03T23:20:27.739Z In(14) shell[132915]: [root]: ls +2026-04-03T23:20:35.595Z In(14) shell[132915]: [root]: ls -lh +2026-04-03T23:20:40.510Z In(14) shell[132915]: [root]: ./artemis -h +2026-04-03T23:20:56.704Z In(14) shell[132915]: [root]: nano +2026-04-03T23:21:02.975Z In(14) shell[132915]: [root]: touch test.sh +2026-04-03T23:21:10.575Z In(14) shell[132915]: [root]: echo 'ls' > test.sh +2026-04-03T23:21:13.585Z In(14) shell[132915]: [root]: ls +2026-04-03T23:21:16.085Z In(14) shell[132915]: [root]: rm test.sh +2026-04-03T23:21:19.509Z In(14) shell[132915]: [root]: rm artemis +2026-04-03T23:22:02.205Z In(14) shell[132915]: [root]: ls vmfs/ +2026-04-03T23:22:04.390Z In(14) shell[132915]: [root]: ls vmfs/volumes/ +2026-04-03T23:22:09.290Z In(14) shell[132915]: [root]: du -h +2026-04-03T23:22:11.954Z In(14) shell[132915]: [root]: df -h +2026-04-03T23:22:35.624Z In(14) shell[132915]: [root]: cd /vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0 +2026-04-03T23:22:36.271Z In(14) shell[132915]: [root]: ls +2026-04-03T23:22:39.267Z In(14) shell[132915]: [root]: ./artemis -h +2026-04-03T23:24:27.392Z In(14) shell[132915]: [root]: ldd artemis +2026-04-03T23:24:39.698Z In(14) shell[132915]: [root]: ls -lh +2026-04-03T23:24:44.637Z In(14) shell[132915]: [root]: ./artemis +2026-04-03T23:24:50.923Z In(14) shell[132915]: [root]: sudo ./artemis +2026-04-03T23:25:26.559Z In(14) shell[132915]: [root]: vsish -e set /config/User/intOpts/UserProcEnable 1 +2026-04-03T23:25:30.036Z In(14) shell[132915]: [root]: ./artemis -h +2026-04-03T23:27:31.220Z In(14) shell[132915]: [root]: supershell -c "./artemis -h" +2026-04-03T23:27:41.734Z In(14) shell[132915]: [root]: esxcli system settings encryption set --require-exec-installed-only=FALSE +2026-04-03T23:27:59.301Z In(14) shell[132915]: [root]: esxcli system settings encryption get +2026-04-03T23:28:13.501Z In(14) shell[132915]: [root]: ./artemis -h +2026-04-03T23:28:21.520Z In(14) shell[132915]: [root]: df -h +2026-04-03T23:28:30.769Z In(14) shell[132915]: [root]: ls +2026-04-03T23:28:33.853Z In(14) shell[132915]: [root]: ls log/ +2026-04-03T23:28:41.381Z In(14) shell[132915]: [root]: touch test.sh +2026-04-03T23:28:46.455Z In(14) shell[132915]: [root]: echo 'ls' > test.sh +2026-04-03T23:28:49.215Z In(14) shell[132915]: [root]: sh test.sh +2026-04-03T23:29:00.953Z In(14) shell[132915]: [root]: echo 'artemis -h' > test.sh +2026-04-03T23:29:02.826Z In(14) shell[132915]: [root]: sh test.sh +2026-04-03T23:29:09.943Z In(14) shell[132915]: [root]: echo './artemis -h' > test.sh +2026-04-03T23:29:11.491Z In(14) shell[132915]: [root]: sh test.sh +2026-04-03T23:29:44.109Z In(14) shell[132915]: [root]: mount +2026-04-03T23:29:55.943Z In(14) shell[132915]: [root]: esxcli storage filesystem list +2026-04-03T23:30:18.720Z In(14) shell[132915]: [root]: vdf -h +2026-04-03T23:30:36.846Z In(14) shell[132915]: [root]: ls /tmp/ +2026-04-03T23:30:41.343Z In(14) shell[132915]: [root]: mv artemis /tmp +2026-04-03T23:30:44.417Z In(14) shell[132915]: [root]: cd /tmp/ +2026-04-03T23:30:46.353Z In(14) shell[132915]: [root]: ./artemis -h +2026-04-03T23:31:15.705Z In(14) shell[132915]: [root]: mv artemis /opt/ +2026-04-03T23:31:34.298Z In(14) shell[132915]: [root]: mv artemis /vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0 +2026-04-03T23:31:42.479Z In(14) shell[132915]: [root]: esxcli system settings encryption get +2026-04-03T23:32:12.643Z In(14) shell[132915]: [root]: cat /proc/mounts +2026-04-03T23:32:23.777Z In(14) shell[132915]: [root]: esxcli system settings advanced list -o /User/ExecInstalledOnly +2026-04-03T23:32:48.191Z In(14) shell[132915]: [root]: esxcli system settings advanced set -o /User/ExecInstalledOnly -i 0 +2026-04-03T23:32:55.800Z In(14) shell[132915]: [root]: cd /vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0 +2026-04-03T23:32:56.914Z In(14) shell[132915]: [root]: esxcli system settings advanced set -o /User/ExecInstalledOnly -i 0 +2026-04-03T23:33:00.883Z In(14) shell[132915]: [root]: esxcli system settings advanced list -o /User/ExecInstalledOnly +2026-04-03T23:33:06.647Z In(14) shell[132915]: [root]: ./artemis -h +2026-04-03T23:33:16.890Z In(14) shell[132915]: [root]: ./artemis acquire sysinfo +2026-04-03T23:33:22.971Z In(14) shell[132915]: [root]: ./artemis acquire systeminfo +2026-04-03T23:33:24.846Z In(14) shell[132915]: [root]: ls +2026-04-03T23:33:26.934Z In(14) shell[132915]: [root]: cd tmp/local_collector/ +2026-04-03T23:33:27.447Z In(14) shell[132915]: [root]: ls +2026-04-03T23:33:29.354Z In(14) shell[132915]: [root]: ls -lh +2026-04-03T23:33:32.036Z In(14) shell[132915]: [root]: cat systeminfo_c4cfe846-08c1-494f-b588-49971fc0fead.json +2026-04-03T23:33:50.427Z In(14) shell[132915]: [root]: cd ../.. +2026-04-03T23:33:52.155Z In(14) shell[132915]: [root]: rm -r tmp/ +2026-04-03T23:34:06.803Z In(14) shell[132915]: [root]: y +2026-04-03T23:34:08.400Z In(14) shell[132915]: [root]: ls +2026-04-03T23:34:15.801Z In(14) shell[132915]: [root]: ./artemis acquire processes +2026-04-03T23:34:45.205Z In(14) shell[132915]: [root]: ./artemis acquire filelisting -h +2026-04-03T23:34:57.692Z In(14) shell[132915]: [root]: ./artemis acquire filelisting --start-path /vmfs --depth 99 +2026-04-03T23:35:00.589Z In(14) shell[132915]: [root]: ls tmp/local_collector/ +2026-04-03T23:35:04.079Z In(14) shell[132915]: [root]: ls -lh tmp/local_collector/ +2026-04-03T23:35:41.831Z In(14) shell[132915]: [root]: rm -h +2026-04-03T23:35:46.392Z In(14) shell[132915]: [root]: rm -rf tmp/ +2026-04-03T23:35:53.359Z In(14) shell[132915]: [root]: ./artemis acquire filelisting --md5 --start-path /vmfs --depth 99 +2026-04-03T23:36:17.389Z In(14) shell[132915]: [root]: ls +2026-04-03T23:36:19.251Z In(14) shell[132915]: [root]: ls log/ +2026-04-03T23:36:20.292Z In(14) shell[132915]: [root]: pwd +2026-04-03T23:36:25.960Z In(14) shell[132915]: [root]: ls -lh log/ +2026-04-03T23:37:58.768Z In(14) shell[132915]: [root]: ls +2026-04-03T23:38:02.031Z In(14) shell[132915]: [root]: ./artemis main.js +2026-04-03T23:38:06.272Z In(14) shell[132915]: [root]: ./artemis -j main.js +2026-04-03T23:38:52.589Z In(14) shell[132915]: [root]: ./artemis -j main.js +2026-04-03T23:39:02.598Z In(14) shell[132915]: [root]: less +2026-04-03T23:39:05.013Z In(14) shell[132915]: [root]: less /vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmkernel.log +2026-04-03T23:39:26.315Z In(14) shell[132915]: [root]: ./artemis -j main.js +2026-04-03T23:39:56.512Z In(14) shell[132915]: [root]: ./artemis acquire processes +2026-04-03T23:55:20.388Z In(14) shell[132915]: [root]: ls +2026-04-03T23:55:25.094Z In(14) shell[132915]: [root]: ./artemis acquire connections +2026-04-03T23:55:27.766Z In(14) shell[132915]: [root]: ls tmp/local_collector/ +2026-04-03T23:55:41.064Z In(14) shell[132915]: [root]: ./artemis -j main.js +2026-04-03T23:55:55.598Z In(14) shell[132915]: [root]: ls log +2026-04-03T23:56:01.469Z In(14) shell[132915]: [root]: cat log/shell.log +2026-04-03T23:56:10.221Z In(14) shell[132915]: [root]: ls +2026-04-03T23:56:17.331Z In(14) shell[132915]: [root]: ls vmware/ +2026-04-03T23:56:18.643Z In(14) shell[132915]: [root]: ls vmware/lifecycle/ +2026-04-03T23:56:24.797Z In(14) shell[132915]: [root]: ls vmware/lifecycle/hostSeed/ +2026-04-03T23:56:26.577Z In(14) shell[132915]: [root]: ls vmware/lifecycle/hostSeed/esxioCachedVibs/ +2026-04-03T23:56:42.958Z In(14) shell[132915]: [root]: less tmp/local_collector/files_171ae265-1d77-4133-ae9b-3fab2c565f78.json +2026-04-03T23:57:38.088Z In(14) shell[132915]: [root]: less tmp/local_collector/files_171ae265-1d77-4133-ae9b-3fab2c565f78.json +2026-04-03T23:57:51.625Z In(14) shell[132915]: [root]: rm -rf tmp/ +2026-04-03T23:58:03.716Z In(14) shell[132915]: [root]: ./artemis -j main.js +2026-04-03T23:58:17.720Z In(14) shell[132915]: [root]: ps +2026-04-03T23:58:19.948Z In(14) shell[132915]: [root]: ps -h +2026-04-03T23:58:24.791Z In(14) shell[132915]: [root]: ps -i +2026-04-03T23:58:27.413Z In(14) shell[132915]: [root]: ps -h +2026-04-03T23:58:39.095Z In(14) shell[132915]: [root]: ps -T +2026-04-03T23:58:41.733Z In(14) shell[132915]: [root]: ps +2026-04-03T23:58:44.524Z In(14) shell[132915]: [root]: ps -h +2026-04-03T23:58:45.917Z In(14) shell[132915]: [root]: netstat +2026-04-03T23:58:48.454Z In(14) shell[132915]: [root]: ip addr +2026-04-03T23:58:55.590Z In(14) shell[132915]: [root]: ps -i +2026-04-03T23:58:58.530Z In(14) shell[132915]: [root]: ls +2026-04-03T23:59:02.106Z In(14) shell[132915]: [root]: ls var/ +2026-04-03T23:59:03.723Z In(14) shell[132915]: [root]: ls var/tmp/ +2026-04-03T23:59:06.638Z In(14) shell[132915]: [root]: ls core/ +2026-04-03T23:59:09.325Z In(14) shell[132915]: [root]: ls downloads/ +2026-04-03T23:59:11.518Z In(14) shell[132915]: [root]: ls healthd/ +2026-04-03T23:59:21.899Z In(14) shell[132915]: [root]: less healthd/hostd-health-0.json +2026-04-03T23:59:27.023Z In(14) shell[132915]: [root]: ls healthd/ +2026-04-03T23:59:29.401Z In(14) shell[132915]: [root]: ls -lh healthd/ +2026-04-03T23:59:35.489Z In(14) shell[132915]: [root]: ls +2026-04-03T23:59:39.595Z In(14) shell[132915]: [root]: ls store/ +2026-04-03T23:59:47.118Z In(14) shell[132915]: [root]: ls vmkdump/ +2026-04-03T23:59:51.055Z In(14) shell[132915]: [root]: ls / +2026-04-03T23:59:57.229Z In(14) shell[132915]: [root]: ls /var/lo +2026-04-04T00:00:00.628Z In(14) shell[132915]: [root]: ls /var/db +2026-04-04T00:00:03.473Z In(14) shell[132915]: [root]: ls /var/db/payloads/ +2026-04-04T00:00:12.258Z In(14) shell[132915]: [root]: ls /var/db/esximg/vibs/ +2026-04-04T00:00:23.226Z In(14) shell[132915]: [root]: less /var/db/esximg/vibs/lpfc-4107835131937698196.xml +2026-04-04T00:00:47.093Z In(14) shell[132915]: [root]: ls +2026-04-04T00:00:49.494Z In(14) shell[132915]: [root]: ls / +2026-04-04T00:00:52.241Z In(14) shell[132915]: [root]: /pro +2026-04-04T00:00:53.250Z In(14) shell[132915]: [root]: /proc/ +2026-04-04T00:00:59.192Z In(14) shell[132915]: [root]: ls /etc/ +2026-04-04T00:01:05.266Z In(14) shell[132915]: [root]: ls /usr/ +2026-04-04T00:01:09.232Z In(14) shell[132915]: [root]: ls -a +2026-04-04T00:01:19.264Z In(14) shell[132915]: [root]: ls -h / +2026-04-04T00:01:21.279Z In(14) shell[132915]: [root]: ls -ha / +2026-04-04T00:01:25.390Z In(14) shell[132915]: [root]: ls -ha /.ssh/ +2026-04-04T00:01:29.699Z In(14) shell[132915]: [root]: ls -ha /.ssh/known_hosts +2026-04-04T00:01:33.867Z In(14) shell[132915]: [root]: less -ha /.ssh/known_hosts +2026-04-04T00:01:37.828Z In(14) shell[132915]: [root]: less /.ssh/known_hosts +2026-04-04T00:01:42.740Z In(14) shell[132915]: [root]: ls /opt/ +2026-04-04T00:01:47.986Z In(14) shell[132915]: [root]: rm /opt/artemis +2026-04-04T00:01:51.675Z In(14) shell[132915]: [root]: ls /opt/vmware/ +2026-04-04T00:01:54.954Z In(14) shell[132915]: [root]: ls /opt/vmware/vpxa/vpx/ +2026-04-04T00:02:03.371Z In(14) shell[132915]: [root]: ls /opt/vmware/nvme/esxcli-nvme-plugin +2026-04-04T00:02:07.285Z In(14) shell[132915]: [root]: ls +2026-04-04T00:02:44.796Z In(14) shell[132915]: [root]: sudo dnf install sshfs diff --git a/tests/test_data/esxi/logs/syslog.5.gz b/tests/test_data/esxi/logs/syslog.5.gz new file mode 100644 index 00000000..ec918194 Binary files /dev/null and b/tests/test_data/esxi/logs/syslog.5.gz differ diff --git a/tests/test_data/esxi/logs/syslog.log b/tests/test_data/esxi/logs/syslog.log new file mode 100644 index 00000000..a4f27b04 --- /dev/null +++ b/tests/test_data/esxi/logs/syslog.log @@ -0,0 +1,973 @@ +2026-04-03T23:05:57.058Z In(14) Jumper2[131343]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:05:57.058Z In(14) Jumper2[131343]: 131343:VVOLLIB : VVolLib_SetLogLevel:1351: Log level for vvolLib set to 10 +2026-04-03T23:05:57.058Z In(14) Jumper2[131343]: 131343:VVOLLIB : VVolLib_IpcInit:206: IPC timeouts set to: connect = 30 sec, send = 30 sec, receive = 200 sec +2026-04-03T23:05:57.058Z In(14) Jumper2[131343]: Using VMware ESXi syslog APIs +2026-04-03T23:05:57.063Z Er(11) Jumper2[131350]: error [ConfigStore:3ccddcf240] [2000] Failed to connect to database +2026-04-03T23:05:57.065Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] ConfigStoreException: [context]zKq7AVICAgAAAPeNeAEPY29uZmlnc3RvcmUAAFJ7AWxpYmNvbmZpZ3N0b3JlY29tbW9uLnNvAAEE6wJsaWJjb25maWdzdG9yZS5zbwABnXYDAkW2cmxpYnZta2N0bC5zbwADbN0NbGlic3lzdGVtLnNvAATb7AVsaWJjcHB0b29scy5zbwAEdx0GBR42AWp1bXBlcjIABRFHAQUKyQEFJl0CBU9fAgUIYwIFVYkBBaGYAQ==[/context] +2026-04-03T23:05:57.065Z Er(11) Jumper2[131350]: error [ConfigStore:3ccddcf240] [2000] Failed to connect to database +2026-04-03T23:05:57.066Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] ConfigStoreException: [context]zKq7AVICAgAAAPeNeAEPY29uZmlnc3RvcmUAAFJ7AWxpYmNvbmZpZ3N0b3JlY29tbW9uLnNvAAEE6wJsaWJjb25maWdzdG9yZS5zbwABnXYDAqOncmxpYnZta2N0bC5zbwAC3bJyA43dDWxpYnN5c3RlbS5zbwAE2+wFbGliY3BwdG9vbHMuc28ABHcdBgUeNgFqdW1wZXIyAAURRwEFCskBBSZdAgVPXwIFCGMCBVWJAQ==[/context] +2026-04-03T23:05:57.066Z In(14) Jumper2[131351]: Invoking method start on plugin check-required-memory +2026-04-03T23:05:57.066Z In(14) Jumper2[131351]: Skipping check required memory. +2026-04-03T23:05:57.066Z In(14) Jumper2[131351]: Method start executed successfully for plugin check-required-memory +2026-04-03T23:05:57.070Z In(14) Jumper2[131352]: Invoking method start on plugin dma-engine +2026-04-03T23:05:57.070Z In(14) Jumper2[131352]: Method start executed successfully for plugin dma-engine +2026-04-03T23:05:57.073Z In(14) Jumper2[131353]: Invoking method start on plugin fips-configuration-fallback +2026-04-03T23:05:57.073Z In(14) Jumper2[131353]: FIPS configuration was already handled in inittab +2026-04-03T23:05:57.073Z In(14) Jumper2[131353]: Method start executed successfully for plugin fips-configuration-fallback +2026-04-03T23:05:57.077Z In(14) Jumper2[131354]: Invoking method start on plugin mount-filesystems +2026-04-03T23:05:57.077Z In(14) Jumper2[131354]: Method start executed successfully for plugin mount-filesystems +2026-04-03T23:05:57.084Z In(14) Jumper2[131356]: Invoking method start on plugin vmkcrypto +2026-04-03T23:05:57.084Z In(14) Jumper2[131356]: Method start executed successfully for plugin vmkcrypto +2026-04-03T23:05:57.084Z In(14) Jumper2[131355]: Invoking method start on plugin storage-psa-init +2026-04-03T23:05:57.084Z In(14) Jumper2[131355]: Method start executed successfully for plugin storage-psa-init +2026-04-03T23:05:57.084Z Er(11) Jumper2[131350]: error [ConfigStore:3ccddcf240] [2000] Failed to connect to database +2026-04-03T23:05:57.085Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] ConfigStoreException: [context]zKq7AVICAgAAAPeNeAEPY29uZmlnc3RvcmUAAFJ7AWxpYmNvbmZpZ3N0b3JlY29tbW9uLnNvAAEE6wJsaWJjb25maWdzdG9yZS5zbwABnXYDAo5NA2xpYnZta21vZC5zbwACs4sBAjeQAQMRs3JsaWJ2bWtjdGwuc28ABI3dDWxpYnN5c3RlbS5zbwAF2+wFbGliY3BwdG9vbHMuc28ABXcdBgYeNgFqdW1wZXIyAAYRRwEGCskBBiZdAgZPXwI=[/context] +2026-04-03T23:05:57.086Z Er(11) Jumper2[131350]: error [ConfigStore:3ccddcf240] [2000] Failed to connect to database +2026-04-03T23:05:57.086Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] ConfigStoreException: [context]zKq7AVICAgAAAPeNeAEPY29uZmlnc3RvcmUAAFJ7AWxpYmNvbmZpZ3N0b3JlY29tbW9uLnNvAAEE6wJsaWJjb25maWdzdG9yZS5zbwABnXYDAhFTA2xpYnZta21vZC5zbwACz40BAjeQAQMRs3JsaWJ2bWtjdGwuc28ABI3dDWxpYnN5c3RlbS5zbwAF2+wFbGliY3BwdG9vbHMuc28ABXcdBgYeNgFqdW1wZXIyAAYRRwEGCskBBiZdAgZPXwI=[/context] +2026-04-03T23:05:57.087Z In(14) Jumper2[131357]: Invoking method start on plugin vmkeventd +2026-04-03T23:05:57.090Z No(13) Jumper2[131350]: Module 'hyperclock' load by uid=0 who=? successful +2026-04-03T23:05:57.090Z In(14) Jumper2[131358]: Invoking method start on plugin vobd +2026-04-03T23:05:57.091Z In(14) Jumper2[131351]: Invoking method start on plugin entropy-daemon +2026-04-03T23:05:57.092Z In(14) Jumper2[131352]: Invoking method start on plugin tpm +2026-04-03T23:05:57.092Z In(14) Jumper2[131352]: Method start executed successfully for plugin tpm +2026-04-03T23:05:57.094Z In(14) Jumper2[131352]: Invoking method start on plugin config-encryption-early +2026-04-03T23:05:57.097Z In(14) Jumper2[131358]: Method start executed successfully for plugin vobd +2026-04-03T23:05:57.099Z In(14) Jumper2[131350]: lib/ssl: OpenSSL using FIPS provider +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: Client usage +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.101Z In(14) Jumper2[131354]: lib/ssl: OpenSSL using FIPS provider +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: Server usage +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.101Z In(14) Jumper2[131350]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: Client usage +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: Server usage +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.103Z In(14) Jumper2[131354]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.105Z In(14) Jumper2[131353]: lib/ssl: OpenSSL using FIPS provider +2026-04-03T23:05:57.107Z In(14) Jumper2[131355]: lib/ssl: OpenSSL using FIPS provider +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: Client usage +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: Server usage +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.109Z In(14) Jumper2[131355]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.109Z In(14) Jumper2[131353]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: Client usage +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: Successfully read tls config dictionary file. +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: Server usage +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: protocol list tls1.2,tls1.3 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: protocol min 0x303 max 0x304 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: protocol list tls1.2,tls1.3 (openssl flags 0x16000000) +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: cipher list ECDHE+AESGCM:ECDHE+AES +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: cipher suites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +2026-04-03T23:05:57.110Z In(14) Jumper2[131353]: lib/ssl: curves list prime256v1:secp384r1:secp521r1 +2026-04-03T23:05:57.135Z In(14) Jumper2[131357]: Method start executed successfully for plugin vmkeventd +2026-04-03T23:05:57.186Z In(14) Jumper2[131352]: Method start executed successfully for plugin config-encryption-early +2026-04-03T23:05:57.187Z In(14) Jumper2[131350]: Invoking method start on plugin restore-configuration +2026-04-03T23:05:57.187Z In(14) Jumper2[131350]: restoring configuration +2026-04-03T23:05:57.188Z Wa(12) Jumper2[131350]: FileGetUserName: Unable to retrieve the user name associated with UID 0. +2026-04-03T23:05:57.188Z Wa(12) Jumper2[131350]: FileGetUserIdentifier: Failed to get user name, using UID. +2026-04-03T23:05:57.188Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] LoadStore Start. +2026-04-03T23:05:57.188Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Restore ConfigStore from /var/lib/vmware/configstore/firstbootBackup/current-store-1 +2026-04-03T23:05:57.195Z In(30) init-entropyd[131397]: Entropy source from USERSPACE_DAEMON is not enabled +2026-04-03T23:05:57.200Z In(14) Jumper2[131351]: Method start executed successfully for plugin entropy-daemon +2026-04-03T23:05:57.206Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] ConfigStore restored successfully from backup +2026-04-03T23:05:57.206Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] DataFileStore backup not found /var/lib/vmware/configstore/backup/datafile-store +2026-04-03T23:05:57.207Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Database Upgrade invoked for /etc/vmware/schemastore/schema-store-1 +2026-04-03T23:05:57.207Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Upgrading DB from version 0 to 7 +2026-04-03T23:05:57.207Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:846930886]BeginTransaction invoked. +2026-04-03T23:05:57.207Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:846930886]Transaction started, level = 1 +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:846930886]CommitTransaction invoked. +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:846930886]Transaction committed,level = 1 +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Database Upgrade done +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Database Upgrade invoked for /etc/vmware/configstore/datafile-store +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Upgrading DB from version 0 to 1 +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1681692777]BeginTransaction invoked. +2026-04-03T23:05:57.208Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1681692777]Transaction started, level = 1 +2026-04-03T23:05:57.209Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1681692777]CommitTransaction invoked. +2026-04-03T23:05:57.209Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1681692777]Transaction committed,level = 1 +2026-04-03T23:05:57.209Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Database Upgrade done +2026-04-03T23:05:57.209Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Database Upgrade invoked for /etc/vmware/configstore/current-store-1 +2026-04-03T23:05:57.210Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Bootbank changed 70a749fa74a0482abf9d47e478068aac!= +2026-04-03T23:05:57.210Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Processing schemas.. +2026-04-03T23:05:57.210Z In(14) Jumper2[131350]: Esx imagedb locked. +2026-04-03T23:05:57.210Z In(14) Jumper2[131350]: Found 123 vibs in image profile. +2026-04-03T23:05:57.211Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/clusterstore--1214674521922854400.xml' schemaId='clusterstore-8.0.3_0.70.24677879' +2026-04-03T23:05:57.211Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/loadesx--4406820425343553021.xml' +2026-04-03T23:05:57.211Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmkusb-esxio-8068825622063450629.xml' +2026-04-03T23:05:57.211Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esx-dvfilter-generic-fastpath--2796132382397695479.xml' +2026-04-03T23:05:57.211Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nhpsa--9042448519025678324.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-esxio--7257226554711861746.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ntg3-1612139405784773647.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/spidev-esxio-2757219259853774351.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bfedac-esxio--5908840446760875505.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rdmahl--6417830317847683054.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlxbf-gige-esxio--2182571780174278633.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmkusb--6652899420347977701.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pvscsi--2839273288992145377.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-intelv2-nvme-vmd-plugin-5227254194474269731.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/penspi-esxio-3766505619419817000.xml' +2026-04-03T23:05:57.212Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bmcal-esxio-2172309767494890025.xml' +2026-04-03T23:05:57.213Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-oem-dell-plugin--7142196400986022358.xml' +2026-04-03T23:05:57.213Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-oem-lenovo-plugin--3705752284914773972.xml' +2026-04-03T23:05:57.213Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/vsanhealth--7252272331400887238.xml' schemaId='vsanhealth-8.0.3_0.70.24677879' +2026-04-03T23:05:57.213Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmware-esx-esxcli-nvme-plugin-5001823443837432381.xml' +2026-04-03T23:05:57.213Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmerdma--5738423847728050625.xml' +2026-04-03T23:05:57.213Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/native-misc-drivers--7773646079442447807.xml' +2026-04-03T23:05:57.213Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nipmi-6591894657689168451.xml' +2026-04-03T23:05:57.216Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/esx-base-750844975848581188.xml' schemaId='esx-base-8.0.3_0.70.24677879' +2026-04-03T23:05:57.217Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-msgpt35-4090110432831115856.xml' +2026-04-03T23:05:57.217Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmetcp--7818873969079180207.xml' +2026-04-03T23:05:57.217Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/iavmd--6422297680685323694.xml' +2026-04-03T23:05:57.217Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/atlantic-3137850086131581025.xml' +2026-04-03T23:05:57.217Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-mr3-4132488351422931049.xml' +2026-04-03T23:05:57.219Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/esxio-base-8762523016439111276.xml' schemaId='esxio-base-8.0.3_0.70.24677879' +2026-04-03T23:05:57.220Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-msgpt2--5970613167340249490.xml' +2026-04-03T23:05:57.220Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qfle3--2517390882679429516.xml' +2026-04-03T23:05:57.220Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-msgpt3--2868007923650569099.xml' +2026-04-03T23:05:57.220Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-core--2290432825855889281.xml' +2026-04-03T23:05:57.220Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-rdma--5837419105191776129.xml' +2026-04-03T23:05:57.220Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/dwi2c-esxio-2830377460432849029.xml' +2026-04-03T23:05:57.221Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/esxio-update-7124153484416468614.xml' schemaId='esxio-update-8.0.3_0.70.24677879' +2026-04-03T23:05:57.221Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pvscsi-esxio--8387789160151327080.xml' +2026-04-03T23:05:57.221Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-ens-474301829729690777.xml' +2026-04-03T23:05:57.221Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmksdhci-9008127084135543966.xml' +2026-04-03T23:05:57.221Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmetcp-esxio--4604779040360807264.xml' +2026-04-03T23:05:57.221Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/elxnet-1946758428691706529.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esx-ui-8293235535419959971.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/infravisor-8997907133149463716.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/iser--4693103531430742363.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qcnic--5907714000110839638.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-ens-esxio-5003902926672119979.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esxio-dvfilter-generic-fastpath-5845242820056691884.xml' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvme-pcie--6244974539466769749.xml' +2026-04-03T23:05:57.222Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/vmware-hbrsrv--7613977505523482961.xml' schemaId='vmware-hbrsrv-8.0.3_0.0.24022510' +2026-04-03T23:05:57.222Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvme-pcie-esxio-5776915539691625648.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-5682775502847760051.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pensandoatlas-6637280343931241140.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/cndi-igc-7179879369177023159.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rshim-net-155130162716811960.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmware-esx-esxcli-nvme-plugin-esxio--1931829931018724169.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esx-xserver-4002444622698975418.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nenic-3487522764178328255.xml' +2026-04-03T23:05:57.223Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/penedac-esxio-6835941441514987712.xml' +2026-04-03T23:05:57.224Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/vcls-pod-crx--1512940133824995130.xml' schemaId='vcls-8.0.3_0.70.24677879' +2026-04-03T23:05:57.224Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-nvme-pcie-plugin-4945301170736811730.xml' +2026-04-03T23:05:57.224Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/igbn--8746341788573877036.xml' +2026-04-03T23:05:57.224Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/icen-8916530193377992404.xml' +2026-04-03T23:05:57.224Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bnxtnet--2632387123670074668.xml' +2026-04-03T23:05:57.224Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-core-esxio--7700149672255215910.xml' +2026-04-03T23:05:57.224Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-rdma-esxio-388014082325948122.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/gc-6553940035640415455.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/intelgpio-4683471463143431907.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/mnet-esxio-2664124115780522213.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmw-ahci--2863041570900223257.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ionic-cloud-8808722694233827047.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ne1000-7553652028367298799.xml' +2026-04-03T23:05:57.225Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/loadesxio--159341403978589451.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vdfs--5534425978642025221.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esxio-combiner-esxio--7415527965380486402.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-cc-7221626889777496831.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rste-3332480026299810559.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/mlnx-bfbootctl-esxio--6355423439093048575.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ionic-en-esxio--137840806006651640.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rd1173-esxio-6065201338491481866.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/crx-2409901468917765906.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/trx--7871915387220191467.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esxio-combiner--4257326179364409573.xml' +2026-04-03T23:05:57.226Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-lsiv2-drivers-plugin-3707373368893515038.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/elxiscsi-7779274521910363945.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bmcal-407707237903790385.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/cpu-microcode-3874795112238877499.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-smartpqiv2-plugin--1654087741036243136.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/i40en--7005967177474860220.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/native-misc-drivers-esxio-6542998665911438665.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ionic-en--3426907647270974127.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qfle3f-2961494683449831764.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedentv-7929314746453517149.xml' +2026-04-03T23:05:57.227Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/mtip32xx-native-781255057096333158.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qfle3i--5072165982467884688.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-cc-esxio--2924112524489379977.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/irdman--5252481207035399808.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/dwi2c-3155320132838243204.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/elx-esx-libelxima.so-4614560044973845893.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedrntv--2050234672095986300.xml' +2026-04-03T23:05:57.228Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/esx-update--596325549009631859.xml' schemaId='esx-update-8.0.3_0.70.24677879' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmkata-689454865869777299.xml' +2026-04-03T23:05:57.228Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lpfc-4107835131937698196.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-hpv2-hpsa-plugin-1891047060682121621.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qflge-8159324616431210900.xml' +2026-04-03T23:05:57.229Z In(14) Jumper2[131350]: xmlfile='/var/db/esximg/vibs/esxio--7380652109566714472.xml' schemaId='esxio-8.0.3_0.70.24677879' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qlnativefc-5721695675842464164.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmksdhci-esxio-7183246134909510565.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/brcmfcoe--7077156919334518355.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/drivervm-gpu-base-7060616953794939310.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nfnic--5839761048527452742.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pengpio-esxio-3957121686344348094.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/sfvmk--7386550714053122617.xml' +2026-04-03T23:05:57.229Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ixgben-7203919405131732427.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlxbf-pmc-esxio-1867198322714335691.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lpnic--6166021181119968307.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vds-vsip--1884423871890520101.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bcm-mpi3--8667463485179117088.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bnxtroce--4646501423496497182.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/smartpqi--7747802190966660115.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/gc-esxio-732539502187435506.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vsan-7350783098597441531.xml' +2026-04-03T23:05:57.230Z Db(15) Jumper2[131350]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rshim-6097877412238171135.xml' +2026-04-03T23:05:57.231Z In(14) Jumper2[131350]: Esx imagedb unlocked. +2026-04-03T23:05:57.231Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] VIB IDs from store: +2026-04-03T23:05:57.231Z Er(11) Jumper2[131350]: error [ConfigStore:3ccddcf240] [SchemaProcessor] Skip processing . +2026-04-03T23:05:57.231Z Er(11) Jumper2[131350]: error [ConfigStore:3ccddcf240] [SchemaProcessor] Skip processing .. +2026-04-03T23:05:57.231Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] IDs from dir: esx-base-8.0.3_0.70.24677879-schema esx-update-8.0.3_0.70.24677879-schema clusterstore-8.0.3_0.70.24677879-schema vcls-8.0.3_0.70.24677879-schema vmware-hbrsrv-8.0.3_0.0.24022510-schema vsanhealth-8.0.3_0.70.24677879-schema +2026-04-03T23:05:57.231Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing schema from 'esx-base-8.0.3_0.70.24677879-schema.json' to SchemaStore +2026-04-03T23:05:57.244Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]BeginTransaction invoked. +2026-04-03T23:05:57.244Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction started, level = 1 +2026-04-03T23:05:57.257Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]CommitTransaction invoked. +2026-04-03T23:05:57.258Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction committed,level = 1 +2026-04-03T23:05:57.258Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema 'esx-base-8.0.3_0.70.24677879-schema.json' processed successfully +2026-04-03T23:05:57.258Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing schema from 'esx-update-8.0.3_0.70.24677879-schema.json' to SchemaStore +2026-04-03T23:05:57.258Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]BeginTransaction invoked. +2026-04-03T23:05:57.258Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction started, level = 1 +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]CommitTransaction invoked. +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction committed,level = 1 +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema 'esx-update-8.0.3_0.70.24677879-schema.json' processed successfully +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing schema from 'clusterstore-8.0.3_0.70.24677879-schema.json' to SchemaStore +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]BeginTransaction invoked. +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction started, level = 1 +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]CommitTransaction invoked. +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction committed,level = 1 +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema 'clusterstore-8.0.3_0.70.24677879-schema.json' processed successfully +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing schema from 'vcls-8.0.3_0.70.24677879-schema.json' to SchemaStore +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]BeginTransaction invoked. +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction started, level = 1 +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]CommitTransaction invoked. +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction committed,level = 1 +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema 'vcls-8.0.3_0.70.24677879-schema.json' processed successfully +2026-04-03T23:05:57.259Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing schema from 'vmware-hbrsrv-8.0.3_0.0.24022510-schema.json' to SchemaStore +2026-04-03T23:05:57.260Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]BeginTransaction invoked. +2026-04-03T23:05:57.260Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction started, level = 1 +2026-04-03T23:05:57.260Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]CommitTransaction invoked. +2026-04-03T23:05:57.260Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction committed,level = 1 +2026-04-03T23:05:57.260Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema 'vmware-hbrsrv-8.0.3_0.0.24022510-schema.json' processed successfully +2026-04-03T23:05:57.260Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing schema from 'vsanhealth-8.0.3_0.70.24677879-schema.json' to SchemaStore +2026-04-03T23:05:57.264Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]BeginTransaction invoked. +2026-04-03T23:05:57.264Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction started, level = 1 +2026-04-03T23:05:57.267Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]CommitTransaction invoked. +2026-04-03T23:05:57.267Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [ss:1:424238335]Transaction committed,level = 1 +2026-04-03T23:05:57.267Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema 'vsanhealth-8.0.3_0.70.24677879-schema.json' processed successfully +2026-04-03T23:05:57.267Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [SchemaProcessor] Schema processing completed: success +2026-04-03T23:05:57.267Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing default configuration. +2026-04-03T23:05:57.268Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:719885386]BeginTransaction invoked. +2026-04-03T23:05:57.268Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:719885386]Transaction started, level = 1 +2026-04-03T23:05:57.268Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing default config from '/etc/vmware/default-config//esx-base-8.0.3_0.70.24677879-default.json' +2026-04-03T23:05:57.273Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Copying defaultConfig to Config for schema= {esx/services/hostd}. +2026-04-03T23:05:57.276Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing default config from '/etc/vmware/default-config//clusterstore-8.0.3_0.70.24677879-default.json' +2026-04-03T23:05:57.276Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing default config from '/etc/vmware/default-config//vcls-8.0.3_0.70.24677879-default.json' +2026-04-03T23:05:57.276Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Processing default config from '/etc/vmware/default-config//vmware-hbrsrv-8.0.3_0.0.24022510-default.json' +2026-04-03T23:05:57.277Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:719885386]CommitTransaction invoked. +2026-04-03T23:05:57.277Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:719885386]Transaction committed,level = 1 +2026-04-03T23:05:57.277Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Completed processing default configuration: success. +2026-04-03T23:05:57.277Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Cleanup auto configurations +2026-04-03T23:05:57.279Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] First boot, not considerd upgrade. +2026-04-03T23:05:57.280Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Generating desired user config: BootMode = 0 +2026-04-03T23:05:57.280Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1025202362]BeginTransaction invoked. +2026-04-03T23:05:57.280Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1025202362]Transaction started, level = 1 +2026-04-03T23:05:57.282Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1025202362]CommitTransaction invoked. +2026-04-03T23:05:57.282Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:1025202362]Transaction committed,level = 1 +2026-04-03T23:05:57.282Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Desired user config generated successfully. +2026-04-03T23:05:57.282Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] ConfigStore autoconf start +2026-04-03T23:05:57.283Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Next boot mode set to 0 +2026-04-03T23:05:57.283Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Upgrade delete file not found. +2026-04-03T23:05:57.283Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] LoadStore End. +2026-04-03T23:05:57.286Z In(14) Jumper2[131350]: ConfigCheck: converting module lines +2026-04-03T23:05:57.286Z In(14) Jumper2[131350]: ConfigCheck: enabling TSO on all NIC policies +2026-04-03T23:05:57.286Z In(14) Jumper2[131350]: ConfigCheck: removing .o from module lines +2026-04-03T23:05:57.286Z In(14) Jumper2[131350]: ConfigCheck: Running ipv6 option upgrade, redundantly +2026-04-03T23:05:57.287Z In(14) Jumper2[131350]: Util: tcpip4 IPv6 enabled +2026-04-03T23:05:57.288Z In(14) Jumper2[131350]: Done restoring user accounts +2026-04-03T23:05:57.288Z In(14) Jumper2[131350]: Restore lockdown settings. +2026-04-03T23:05:57.288Z In(14) Jumper2[131350]: Lockdown settings haven't changed, nothing to restore. +2026-04-03T23:05:57.288Z In(14) Jumper2[131350]: Method start executed successfully for plugin restore-configuration +2026-04-03T23:05:57.289Z In(14) Jumper2[131350]: KeyPersist: : Cannot connect to the keypersistence daemon: No such file or directory +2026-04-03T23:05:57.289Z In(14) Jumper2[131351]: Invoking method start on plugin advanced-user-configuration-options +2026-04-03T23:05:57.289Z In(14) Jumper2[131352]: Invoking method start on plugin autodeploy-enabled +2026-04-03T23:05:57.289Z In(14) Jumper2[131352]: Method start executed successfully for plugin autodeploy-enabled +2026-04-03T23:05:57.289Z In(14) Jumper2[131353]: Invoking method start on plugin cleanup-files +2026-04-03T23:05:57.290Z In(14) Jumper2[131354]: Invoking method start on plugin tls +2026-04-03T23:05:57.292Z In(14) Jumper2[131354]: TlsConfigImpl Client: GetProfile CS values: desired configuration found! +2026-04-03T23:05:57.292Z In(14) Jumper2[131354]: TlsConfigImpl Client: GetProfile: CS values profile = COMPATIBLE (default), cipher list = ECDHE+AESGCM:ECDHE+AES, cipher suites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384, groups = prime256v1:secp384r1:secp521r1, protocols = tls1.2,tls1.3 +2026-04-03T23:05:57.292Z In(14) Jumper2[131354]: TlsConfigImpl Client: GetProfile: profile value COMPATIBLE +2026-04-03T23:05:57.293Z In(14) Jumper2[131358]: Invoking method start on plugin restore-system-uuid +2026-04-03T23:05:57.293Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ActiveDirectoryPreferredDomainControllers' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.294Z In(14) Jumper2[131358]: Method start executed successfully for plugin restore-system-uuid +2026-04-03T23:05:57.295Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ActiveDirectoryVerifyCAMCertificate' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.296Z No(13) Jumper2[131356]: Module 'vfat' load by uid=0 who=root successful +2026-04-03T23:05:57.301Z In(14) Jumper2[131354]: TlsConfigImpl Client: RestoreProfile: CS values profile = COMPATIBLE (default), cipher list = ECDHE+AESGCM:ECDHE+AES, cipher suites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384, groups = prime256v1:secp384r1:secp521r1, protocols = tls1.2,tls1.3 +2026-04-03T23:05:57.302Z In(14) Jumper2[131354]: TlsConfigImpl Client: GetProfile CS values: current configuration found! +2026-04-03T23:05:57.302Z In(14) Jumper2[131354]: TlsConfigImpl Client: GetProfile: CS values profile = COMPATIBLE (default), cipher list = ECDHE+AESGCM:ECDHE+AES, cipher suites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384, groups = prime256v1:secp384r1:secp521r1, protocols = tls1.2,tls1.3 +2026-04-03T23:05:57.302Z In(14) Jumper2[131354]: TlsConfigImpl Client: GetProfile: profile value COMPATIBLE +2026-04-03T23:05:57.302Z In(14) Jumper2[131354]: TlsConfigImpl Client: Installed new config file successfully +2026-04-03T23:05:57.302Z In(14) Jumper2[131354]: Restored TLS client configuration from configstore. +2026-04-03T23:05:57.303Z No(13) Jumper2[131357]: Module 'lfHelper' load by uid=0 who=root successful +2026-04-03T23:05:57.303Z In(14) Jumper2[131354]: TlsConfigImpl Server: GetProfile CS values: desired configuration found! +2026-04-03T23:05:57.303Z In(14) Jumper2[131354]: TlsConfigImpl Server: GetProfile: CS values profile = COMPATIBLE (default), cipher list = ECDHE+AESGCM:ECDHE+AES, cipher suites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384, groups = prime256v1:secp384r1:secp521r1, protocols = tls1.2,tls1.3 +2026-04-03T23:05:57.303Z In(14) Jumper2[131354]: TlsConfigImpl Server: GetProfile: profile value COMPATIBLE +2026-04-03T23:05:57.304Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ESXiShellTimeOut' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.305Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id DcuiTimeOut' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.306Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ESXiShellInteractiveTimeOut' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.307Z No(13) Jumper2[131357]: Module 'vmklink_mpi' load by uid=0 who=root successful +2026-04-03T23:05:57.307Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id SuppressShellWarning' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.309Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id SuppressHyperthreadWarning' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.310Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id SuppressSgxDisabledWarning' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.311Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id SuppressSgxAddPackageWarning' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.312Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id EsximageNetTimeout' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.313Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id EsximageNetRetries' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.313Z No(13) Jumper2[131357]: Module 'vsanapi' load by uid=0 who=root successful +2026-04-03T23:05:57.314Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id EsximageNetRateLimit' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.315Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id SuppressCoredumpWarning' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.316Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ReservedUsbDevice' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.317Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id HostdStatsstoreRamdiskSize' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.318Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ESXiVPsDisabledProtocols' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: TlsConfigImpl Server: RestoreProfile: CS values profile = COMPATIBLE (default), cipher list = ECDHE+AESGCM:ECDHE+AES, cipher suites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384, groups = prime256v1:secp384r1:secp521r1, protocols = tls1.2,tls1.3 +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: TlsConfigImpl Server: GetProfile CS values: current configuration found! +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: TlsConfigImpl Server: GetProfile: CS values profile = COMPATIBLE (default), cipher list = ECDHE+AESGCM:ECDHE+AES, cipher suites = TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384, groups = prime256v1:secp384r1:secp521r1, protocols = tls1.2,tls1.3 +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: TlsConfigImpl Server: GetProfile: profile value COMPATIBLE +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: TlsConfigImpl Server: Installed new config file successfully +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: Restored TLS server configuration from configstore. +2026-04-03T23:05:57.321Z In(14) Jumper2[131354]: Method start executed successfully for plugin tls +2026-04-03T23:05:57.323Z No(13) Jumper2[131357]: Module 'vsanbase' load by uid=0 who=root successful +2026-04-03T23:05:57.327Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ESXiVPsAllowedCiphers' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.328Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id HardwareHealthSyncTime' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.329Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id HardwareHealthIgnoredSensors' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.330Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id StatefulInstallScheduled' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.332Z In(14) Jumper2[131353]: Removing /etc/vmware/dpd.conf +2026-04-03T23:05:57.332Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id CheckAccessTemplateEnabled' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.333Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id ShellSandboxEnabled' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.334Z In(14) Jumper2[131351]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key user_var_definitions id AuditApiCallEnabled' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.335Z In(14) Jumper2[131351]: Method start executed successfully for plugin advanced-user-configuration-options +2026-04-03T23:05:57.373Z In(14) Jumper2[131350]: Invoking method start on plugin cbrc +2026-04-03T23:05:57.374Z In(14) Jumper2[131350]: Method start executed successfully for plugin cbrc +2026-04-03T23:05:57.390Z No(13) Jumper2[131351]: Module 'vprobe' load by uid=0 who=root successful +2026-04-03T23:05:57.390Z In(14) Jumper2[131353]: Method start executed successfully for plugin cleanup-files +2026-04-03T23:05:57.390Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:338888228]BeginTransaction invoked. +2026-04-03T23:05:57.391Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:338888228]Transaction started, level = 1 +2026-04-03T23:05:57.392Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Checking for empty objects and arrays in comp esx grp advanced_options key userMem object +2026-04-03T23:05:57.393Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] Error while sending notification for config object 'comp esx grp advanced_options key userMem' to HostD: Failed to connect socket: No such file or directory +2026-04-03T23:05:57.393Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:338888228]CommitTransaction invoked. +2026-04-03T23:05:57.394Z In(14) Jumper2[131404]: Invoking method start on plugin apei +2026-04-03T23:05:57.395Z In(14) Jumper2[131404]: Method start executed successfully for plugin apei +2026-04-03T23:05:57.397Z In(14) Jumper2[131405]: Invoking method start on plugin dvx +2026-04-03T23:05:57.399Z In(14) Jumper2[131406]: Invoking method start on plugin hardware-config +2026-04-03T23:05:57.399Z Er(11) Jumper2[131406]: Failed to symlink /etc/vmware/pci.ids: No such file or directory +2026-04-03T23:05:57.400Z In(14) Jumper2[131350]: info [ConfigStore:3ccddcf240] [cs:1:338888228]Transaction committed,level = 1 +2026-04-03T23:05:57.404Z In(14) Jumper2[131408]: Invoking method start on plugin lineage-tags +2026-04-03T23:05:57.409Z In(14) Jumper2[131407]: Invoking method start on plugin krb5 +2026-04-03T23:05:57.410Z In(14) Jumper2[131410]: Invoking method start on plugin oem-modules +2026-04-03T23:05:57.410Z No(13) Jumper2[131351]: Module 'deltadisk' load by uid=0 who=root successful +2026-04-03T23:05:57.410Z In(14) Jumper2[131410]: Method start executed successfully for plugin oem-modules +2026-04-03T23:05:57.412Z In(14) Jumper2[131407]: Method start executed successfully for plugin krb5 +2026-04-03T23:16:10.555Z No(13) usbarbitrator[132898]: Exiting USB storage detach monitor +2026-04-03T23:16:27.906Z In(38) sshd-session[132909]: Connection from 192.168.124.1 port 32980 +2026-04-03T23:16:32.824Z In(38) sshd-session[132909]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 32980 ssh2 +2026-04-03T23:16:32.840Z In(14) addvob[132913]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:16:32.840Z In(14) addvob[132913]: Could not expand environment variable HOME. +2026-04-03T23:16:32.840Z In(14) addvob[132913]: Could not expand environment variable HOME. +2026-04-03T23:16:32.840Z In(14) addvob[132913]: Using VMware ESXi syslog APIs +2026-04-03T23:16:32.846Z In(86) sshd-session[132909]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-03T23:16:32.861Z In(38) sshd-session[132909]: Session opened for 'root' on /dev/char/pty/t0 +2026-04-03T23:19:55.823Z In(38) sshd-session[132984]: Connection from 192.168.124.1 port 60854 +2026-04-03T23:20:00.266Z In(78) crond[131666]: USER root pid 132987 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:20:00.266Z In(78) crond[131666]: USER root pid 132988 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:20:00.267Z In(78) crond[131666]: USER root pid 132989 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:20:00.267Z In(78) crond[131666]: USER root pid 132990 cmd /bin/crx-cli ++securitydom=crxCliGcDom gc +2026-04-03T23:20:02.214Z In(38) sshd-session[132984]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 60854 ssh2 +2026-04-03T23:20:02.229Z In(14) addvob[133009]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:20:02.229Z In(14) addvob[133009]: Could not expand environment variable HOME. +2026-04-03T23:20:02.229Z In(14) addvob[133009]: Could not expand environment variable HOME. +2026-04-03T23:20:02.229Z In(14) addvob[133009]: Using VMware ESXi syslog APIs +2026-04-03T23:20:02.236Z In(86) sshd-session[132984]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-03T23:20:02.286Z In(38) sshd-session[132984]: User 'root' running command '/usr/lib/vmware/openssh/bin/sftp-server -f LOCAL5 -l INFO' +2026-04-03T23:20:02.294Z In(174) sftp-server[133012]: session opened for local user root from [192.168.124.1] +2026-04-03T23:20:02.297Z In(174) sftp-server[133012]: open "/artemis" flags WRITE,CREATE mode 0755 +2026-04-03T23:20:02.526Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.526Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.527Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.527Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.528Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.528Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.529Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.529Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.530Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.530Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.531Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.531Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.532Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.532Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.533Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.533Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.534Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.534Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.535Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.535Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.535Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.536Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.536Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.536Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.537Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.537Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.538Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.538Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.539Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.539Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.539Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.539Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.540Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.540Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.541Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.541Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.542Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.542Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.543Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.543Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.543Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.543Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.545Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.545Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.545Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.545Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.546Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.546Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.547Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.547Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.548Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.548Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.549Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": No space left on device +2026-04-03T23:20:02.549Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.549Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.549Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.551Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.551Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.552Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.552Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.553Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.553Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.554Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.554Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.555Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.555Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.556Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.556Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.557Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.557Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.557Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.557Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.558Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.558Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.559Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.559Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.560Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.560Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.561Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.561Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.562Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.562Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.562Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.562Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.563Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.563Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.564Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.564Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.564Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.564Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.565Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.565Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.565Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.565Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.566Z Er(171) sftp-server[133012]: error: process_write: write "/artemis": File too large +2026-04-03T23:20:02.566Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.566Z In(174) sftp-server[133012]: set "/artemis" size 39120576 +2026-04-03T23:20:02.566Z In(174) sftp-server[133012]: sent status Failure +2026-04-03T23:20:02.567Z In(174) sftp-server[133012]: close "/artemis" bytes read 0 written 26634240 +2026-04-03T23:20:02.568Z In(174) sftp-server[133012]: session closed for local user root from [192.168.124.1] +2026-04-03T23:20:02.570Z In(38) sshd-session[133010]: Received disconnect from 192.168.124.1 port 60854:11: disconnected by user +2026-04-03T23:20:02.570Z In(38) sshd-session[133010]: Disconnected from user root 192.168.124.1 port 60854 +2026-04-03T23:20:02.584Z In(14) addvob[133014]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:20:02.584Z In(14) addvob[133014]: Could not expand environment variable HOME. +2026-04-03T23:20:02.584Z In(14) addvob[133014]: Could not expand environment variable HOME. +2026-04-03T23:20:02.584Z In(14) addvob[133014]: Using VMware ESXi syslog APIs +2026-04-03T23:20:02.592Z In(86) sshd-session[132984]: pam_unix(sshd:session): session closed for user root +2026-04-03T23:22:27.941Z In(38) sshd-session[133064]: Connection from 192.168.124.1 port 43742 +2026-04-03T23:22:31.639Z In(38) sshd-session[133064]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 43742 ssh2 +2026-04-03T23:22:31.656Z In(14) addvob[133068]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:22:31.656Z In(14) addvob[133068]: Could not expand environment variable HOME. +2026-04-03T23:22:31.656Z In(14) addvob[133068]: Could not expand environment variable HOME. +2026-04-03T23:22:31.656Z In(14) addvob[133068]: Using VMware ESXi syslog APIs +2026-04-03T23:22:31.663Z In(86) sshd-session[133064]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-03T23:22:31.712Z In(38) sshd-session[133064]: User 'root' running command '/usr/lib/vmware/openssh/bin/sftp-server -f LOCAL5 -l INFO' +2026-04-03T23:22:31.719Z In(174) sftp-server[133071]: session opened for local user root from [192.168.124.1] +2026-04-03T23:22:31.722Z In(174) sftp-server[133071]: open "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/artemis" flags WRITE,CREATE mode 0755 +2026-04-03T23:22:32.160Z In(174) sftp-server[133071]: set "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/artemis" size 39120576 +2026-04-03T23:22:32.161Z In(174) sftp-server[133071]: close "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/artemis" bytes read 0 written 39120576 +2026-04-03T23:22:32.166Z In(174) sftp-server[133071]: session closed for local user root from [192.168.124.1] +2026-04-03T23:22:32.174Z In(38) sshd-session[133069]: Received disconnect from 192.168.124.1 port 43742:11: disconnected by user +2026-04-03T23:22:32.174Z In(38) sshd-session[133069]: Disconnected from user root 192.168.124.1 port 43742 +2026-04-03T23:22:32.193Z In(14) addvob[133073]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:22:32.193Z In(14) addvob[133073]: Could not expand environment variable HOME. +2026-04-03T23:22:32.193Z In(14) addvob[133073]: Could not expand environment variable HOME. +2026-04-03T23:22:32.193Z In(14) addvob[133073]: Using VMware ESXi syslog APIs +2026-04-03T23:22:32.198Z In(86) sshd-session[133064]: pam_unix(sshd:session): session closed for user root +2026-04-03T23:25:00.282Z In(78) crond[131666]: USER root pid 133112 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:25:00.282Z In(78) crond[131666]: USER root pid 133113 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:25:00.282Z In(78) crond[131666]: USER root pid 133114 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:27:31.231Z In(14) supershell[133167]: Starting supershell execution +2026-04-03T23:30:00.295Z In(78) crond[131666]: USER root pid 133215 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:30:00.296Z In(78) crond[131666]: USER root pid 133216 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:30:00.296Z In(78) crond[131666]: USER root pid 133217 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:30:00.296Z In(78) crond[131666]: USER root pid 133218 cmd /bin/crx-cli ++securitydom=crxCliGcDom gc +2026-04-03T23:35:00.311Z In(78) crond[131666]: USER root pid 133328 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:35:00.311Z In(78) crond[131666]: USER root pid 133329 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:35:00.311Z In(78) crond[131666]: USER root pid 133330 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:37:52.707Z In(38) sshd-session[133389]: Connection from 192.168.124.1 port 47206 +2026-04-03T23:37:56.285Z In(38) sshd-session[133389]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 47206 ssh2 +2026-04-03T23:37:56.302Z In(14) addvob[133393]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:37:56.302Z In(14) addvob[133393]: Could not expand environment variable HOME. +2026-04-03T23:37:56.302Z In(14) addvob[133393]: Could not expand environment variable HOME. +2026-04-03T23:37:56.302Z In(14) addvob[133393]: Using VMware ESXi syslog APIs +2026-04-03T23:37:56.309Z In(86) sshd-session[133389]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-03T23:37:56.358Z In(38) sshd-session[133389]: User 'root' running command '/usr/lib/vmware/openssh/bin/sftp-server -f LOCAL5 -l INFO' +2026-04-03T23:37:56.366Z In(174) sftp-server[133396]: session opened for local user root from [192.168.124.1] +2026-04-03T23:37:56.369Z In(174) sftp-server[133396]: open "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" flags WRITE,CREATE mode 0644 +2026-04-03T23:37:56.374Z In(174) sftp-server[133396]: set "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" size 1119 +2026-04-03T23:37:56.376Z In(174) sftp-server[133396]: close "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" bytes read 0 written 1119 +2026-04-03T23:37:56.377Z In(174) sftp-server[133396]: session closed for local user root from [192.168.124.1] +2026-04-03T23:37:56.385Z In(38) sshd-session[133394]: Received disconnect from 192.168.124.1 port 47206:11: disconnected by user +2026-04-03T23:37:56.385Z In(38) sshd-session[133394]: Disconnected from user root 192.168.124.1 port 47206 +2026-04-03T23:37:56.396Z In(14) addvob[133398]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:37:56.396Z In(14) addvob[133398]: Could not expand environment variable HOME. +2026-04-03T23:37:56.396Z In(14) addvob[133398]: Could not expand environment variable HOME. +2026-04-03T23:37:56.396Z In(14) addvob[133398]: Using VMware ESXi syslog APIs +2026-04-03T23:37:56.404Z In(86) sshd-session[133389]: pam_unix(sshd:session): session closed for user root +2026-04-03T23:38:37.510Z In(38) sshd-session[133403]: Connection from 192.168.124.1 port 44570 +2026-04-03T23:38:40.922Z No(85) sshd-session[133405]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=192.168.124.1 user=root +2026-04-03T23:38:40.923Z Er(11) sshd-session[133405]: [module:pam_lsass]pam_do_authenticate: error [login:root][error code:2] +2026-04-03T23:38:40.923Z Er(11) sshd-session[133405]: [module:pam_lsass]pam_sm_authenticate: failed [error code:2] +2026-04-03T23:38:40.934Z In(14) addvob[133407]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:38:40.934Z In(14) addvob[133407]: Could not expand environment variable HOME. +2026-04-03T23:38:40.934Z In(14) addvob[133407]: Could not expand environment variable HOME. +2026-04-03T23:38:40.934Z In(14) addvob[133407]: Using VMware ESXi syslog APIs +2026-04-03T23:38:45.004Z In(38) sshd-session[133408]: Connection from 192.168.124.1 port 52580 +2026-04-03T23:38:45.699Z Er(35) sshd-session[133403]: error: PAM: Authentication failure for root from 192.168.124.1 +2026-04-03T23:38:45.703Z In(38) sshd-session[133403]: Connection closed by authenticating user root 192.168.124.1 port 44570 [preauth] +2026-04-03T23:38:48.453Z In(38) sshd-session[133408]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 52580 ssh2 +2026-04-03T23:38:48.472Z In(14) addvob[133412]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:38:48.472Z In(14) addvob[133412]: Could not expand environment variable HOME. +2026-04-03T23:38:48.472Z In(14) addvob[133412]: Could not expand environment variable HOME. +2026-04-03T23:38:48.472Z In(14) addvob[133412]: Using VMware ESXi syslog APIs +2026-04-03T23:38:48.479Z In(86) sshd-session[133408]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-03T23:38:48.529Z In(38) sshd-session[133408]: User 'root' running command '/usr/lib/vmware/openssh/bin/sftp-server -f LOCAL5 -l INFO' +2026-04-03T23:38:48.537Z In(174) sftp-server[133415]: session opened for local user root from [192.168.124.1] +2026-04-03T23:38:48.539Z In(174) sftp-server[133415]: open "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" flags WRITE,CREATE mode 0644 +2026-04-03T23:38:48.542Z In(174) sftp-server[133415]: set "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" size 1161 +2026-04-03T23:38:48.543Z In(174) sftp-server[133415]: close "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" bytes read 0 written 1161 +2026-04-03T23:38:48.546Z In(174) sftp-server[133415]: session closed for local user root from [192.168.124.1] +2026-04-03T23:38:48.553Z In(38) sshd-session[133413]: Received disconnect from 192.168.124.1 port 52580:11: disconnected by user +2026-04-03T23:38:48.553Z In(38) sshd-session[133413]: Disconnected from user root 192.168.124.1 port 52580 +2026-04-03T23:38:48.569Z In(14) addvob[133417]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:38:48.569Z In(14) addvob[133417]: Could not expand environment variable HOME. +2026-04-03T23:38:48.569Z In(14) addvob[133417]: Could not expand environment variable HOME. +2026-04-03T23:38:48.569Z In(14) addvob[133417]: Using VMware ESXi syslog APIs +2026-04-03T23:38:48.574Z In(86) sshd-session[133408]: pam_unix(sshd:session): session closed for user root +2026-04-03T23:39:19.765Z In(38) sshd-session[133434]: Connection from 192.168.124.1 port 45878 +2026-04-03T23:39:23.182Z In(38) sshd-session[133434]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 45878 ssh2 +2026-04-03T23:39:23.195Z In(14) addvob[133447]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:39:23.195Z In(14) addvob[133447]: Could not expand environment variable HOME. +2026-04-03T23:39:23.195Z In(14) addvob[133447]: Could not expand environment variable HOME. +2026-04-03T23:39:23.195Z In(14) addvob[133447]: Using VMware ESXi syslog APIs +2026-04-03T23:39:23.203Z In(86) sshd-session[133434]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-03T23:39:23.252Z In(38) sshd-session[133434]: User 'root' running command '/usr/lib/vmware/openssh/bin/sftp-server -f LOCAL5 -l INFO' +2026-04-03T23:39:23.259Z In(174) sftp-server[133450]: session opened for local user root from [192.168.124.1] +2026-04-03T23:39:23.263Z In(174) sftp-server[133450]: open "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" flags WRITE,CREATE mode 0644 +2026-04-03T23:39:23.266Z In(174) sftp-server[133450]: set "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" size 1165 +2026-04-03T23:39:23.267Z In(174) sftp-server[133450]: close "/vmfs/volumes/OSDATA-69d0473d-ded27d57-be04-52540075d1a0/main.js" bytes read 0 written 1165 +2026-04-03T23:39:23.269Z In(174) sftp-server[133450]: session closed for local user root from [192.168.124.1] +2026-04-03T23:39:23.270Z In(38) sshd-session[133448]: Received disconnect from 192.168.124.1 port 45878:11: disconnected by user +2026-04-03T23:39:23.271Z In(38) sshd-session[133448]: Disconnected from user root 192.168.124.1 port 45878 +2026-04-03T23:39:23.284Z In(14) addvob[133452]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-03T23:39:23.284Z In(14) addvob[133452]: Could not expand environment variable HOME. +2026-04-03T23:39:23.284Z In(14) addvob[133452]: Could not expand environment variable HOME. +2026-04-03T23:39:23.284Z In(14) addvob[133452]: Using VMware ESXi syslog APIs +2026-04-03T23:39:23.290Z In(86) sshd-session[133434]: pam_unix(sshd:session): session closed for user root +2026-04-03T23:40:00.326Z In(78) crond[131666]: USER root pid 133465 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:40:00.326Z In(78) crond[131666]: USER root pid 133466 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:40:00.326Z In(78) crond[131666]: USER root pid 133467 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:40:00.327Z In(78) crond[131666]: USER root pid 133468 cmd /bin/crx-cli ++securitydom=crxCliGcDom gc +2026-04-03T23:45:00.339Z In(78) crond[131666]: USER root pid 133554 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:45:00.340Z In(78) crond[131666]: USER root pid 133555 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:45:00.340Z In(78) crond[131666]: USER root pid 133556 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:50:00.354Z In(78) crond[131666]: USER root pid 133640 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:50:00.354Z In(78) crond[131666]: USER root pid 133641 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:50:00.355Z In(78) crond[131666]: USER root pid 133642 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-03T23:50:00.355Z In(78) crond[131666]: USER root pid 133643 cmd /bin/crx-cli ++securitydom=crxCliGcDom gc +2026-04-03T23:55:00.369Z In(78) crond[131666]: USER root pid 133728 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-03T23:55:00.370Z In(78) crond[131666]: USER root pid 133729 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-03T23:55:00.370Z In(78) crond[131666]: USER root pid 133730 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-04T00:00:00.383Z In(78) crond[131666]: USER root pid 133851 cmd python ++group=host/vim/vmvisor/systemStorage,securitydom=systemStorageMonitorDom /sbin/systemStorageMonitor.pyc +2026-04-04T00:00:00.383Z In(78) crond[131666]: USER root pid 133852 cmd /usr/lib/vmware/vmksummary/log-heartbeat.py +2026-04-04T00:00:00.384Z In(78) crond[131666]: USER root pid 133853 cmd /bin/hostd-probe.sh ++group=host/vim/vmvisor/hostd-probe/stats/sh,securitydom=hostdProbeDom +2026-04-04T00:00:00.384Z In(78) crond[131666]: USER root pid 133854 cmd /usr/lib/vmware/misc/bin/vmkmemstats.sh ++group=host/vim/vmvisor/vmkmemstats.sh,mem=groupMax +2026-04-04T00:00:00.384Z In(78) crond[131666]: USER root pid 133855 cmd /bin/pam_tally2 --reset +2026-04-04T00:00:00.385Z In(78) crond[131666]: USER root pid 133856 cmd /bin/crx-cli ++securitydom=crxCliGcDom gc +2026-04-04T00:01:00.390Z In(78) crond[131666]: USER root pid 133911 cmd /sbin/auto-backup.sh ++group=host/vim/vmvisor/auto-backup.sh +2026-04-04T00:01:00.521Z In(14) auto-backup.sh[133912]: ConfigStore has been modified since the last backup +2026-04-04T00:01:00.604Z In(14) backup.sh[133926]: Bootbank lock is /var/lock/bootbank/fa49a770-2a48a074-9dbf-47e478068aac +2026-04-04T00:01:00.681Z In(14) backup.sh[133926]: Saving current state in /bootbank +2026-04-04T00:01:00.738Z In(14) backup-ssh[133943]: SSH auth keys configuration synced to config store successfully. +2026-04-04T00:01:00.740Z In(14) backup-ssh[133943]: SSH client configuration synced to config store successfully. +2026-04-04T00:01:00.742Z In(14) backup-ssh[133943]: SSH server configuration synced to config store successfully +2026-04-04T00:01:00.751Z In(14) backup.sh[133926]: Ssh configuration synced to configstore +2026-04-04T00:01:00.757Z In(14) backup.sh[133926]: Creating ConfigStore Backup +2026-04-04T00:01:00.763Z Er(11) configStoreBackup[133946]: Stat backup /var/lib/vmware/configstore/backup/current-store-1: rc = 0, errno = 0 +2026-04-04T00:01:00.766Z In(14) configStoreBackup[133946]: ConfigStore backup into /var/lib/vmware/configstore/backup/current-store-1 completed successfully +2026-04-04T00:01:00.766Z In(14) configStoreBackup[133946]: ConfigStore backup into /var/lib/vmware/configstore/backup/current-store-1 completed with rc = 0 +2026-04-04T00:01:00.766Z Er(11) configStoreBackup[133946]: Stat backup /var/lib/vmware/configstore/backup/datafile-store: rc = 0, errno = 2 +2026-04-04T00:01:00.767Z In(14) configStoreBackup[133946]: ConfigStore backup into /var/lib/vmware/configstore/backup/datafile-store completed successfully +2026-04-04T00:01:00.767Z In(14) configStoreBackup[133946]: ConfigStore backup into /var/lib/vmware/configstore/backup/datafile-store completed with rc = 0 +2026-04-04T00:01:00.793Z In(14) backup-check[133948]: Esx imagedb locked. +2026-04-04T00:01:00.794Z In(14) backup-check[133948]: Found 123 vibs in image profile. +2026-04-04T00:01:00.795Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/clusterstore--1214674521922854400.xml' schemaId='clusterstore-8.0.3_0.70.24677879' +2026-04-04T00:01:00.795Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/loadesx--4406820425343553021.xml' +2026-04-04T00:01:00.795Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmkusb-esxio-8068825622063450629.xml' +2026-04-04T00:01:00.796Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esx-dvfilter-generic-fastpath--2796132382397695479.xml' +2026-04-04T00:01:00.796Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nhpsa--9042448519025678324.xml' +2026-04-04T00:01:00.796Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-esxio--7257226554711861746.xml' +2026-04-04T00:01:00.796Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ntg3-1612139405784773647.xml' +2026-04-04T00:01:00.796Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/spidev-esxio-2757219259853774351.xml' +2026-04-04T00:01:00.797Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bfedac-esxio--5908840446760875505.xml' +2026-04-04T00:01:00.797Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rdmahl--6417830317847683054.xml' +2026-04-04T00:01:00.797Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlxbf-gige-esxio--2182571780174278633.xml' +2026-04-04T00:01:00.797Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmkusb--6652899420347977701.xml' +2026-04-04T00:01:00.797Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pvscsi--2839273288992145377.xml' +2026-04-04T00:01:00.797Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-intelv2-nvme-vmd-plugin-5227254194474269731.xml' +2026-04-04T00:01:00.798Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/penspi-esxio-3766505619419817000.xml' +2026-04-04T00:01:00.798Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bmcal-esxio-2172309767494890025.xml' +2026-04-04T00:01:00.798Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-oem-dell-plugin--7142196400986022358.xml' +2026-04-04T00:01:00.798Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-oem-lenovo-plugin--3705752284914773972.xml' +2026-04-04T00:01:00.798Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/vsanhealth--7252272331400887238.xml' schemaId='vsanhealth-8.0.3_0.70.24677879' +2026-04-04T00:01:00.799Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmware-esx-esxcli-nvme-plugin-5001823443837432381.xml' +2026-04-04T00:01:00.799Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmerdma--5738423847728050625.xml' +2026-04-04T00:01:00.799Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/native-misc-drivers--7773646079442447807.xml' +2026-04-04T00:01:00.799Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nipmi-6591894657689168451.xml' +2026-04-04T00:01:00.804Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/esx-base-750844975848581188.xml' schemaId='esx-base-8.0.3_0.70.24677879' +2026-04-04T00:01:00.804Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-msgpt35-4090110432831115856.xml' +2026-04-04T00:01:00.805Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmetcp--7818873969079180207.xml' +2026-04-04T00:01:00.805Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/iavmd--6422297680685323694.xml' +2026-04-04T00:01:00.805Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/atlantic-3137850086131581025.xml' +2026-04-04T00:01:00.805Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-mr3-4132488351422931049.xml' +2026-04-04T00:01:00.808Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/esxio-base-8762523016439111276.xml' schemaId='esxio-base-8.0.3_0.70.24677879' +2026-04-04T00:01:00.808Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-msgpt2--5970613167340249490.xml' +2026-04-04T00:01:00.809Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qfle3--2517390882679429516.xml' +2026-04-04T00:01:00.809Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsi-msgpt3--2868007923650569099.xml' +2026-04-04T00:01:00.809Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-core--2290432825855889281.xml' +2026-04-04T00:01:00.809Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-rdma--5837419105191776129.xml' +2026-04-04T00:01:00.810Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/dwi2c-esxio-2830377460432849029.xml' +2026-04-04T00:01:00.810Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/esxio-update-7124153484416468614.xml' schemaId='esxio-update-8.0.3_0.70.24677879' +2026-04-04T00:01:00.810Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pvscsi-esxio--8387789160151327080.xml' +2026-04-04T00:01:00.810Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-ens-474301829729690777.xml' +2026-04-04T00:01:00.810Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmksdhci-9008127084135543966.xml' +2026-04-04T00:01:00.810Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmetcp-esxio--4604779040360807264.xml' +2026-04-04T00:01:00.811Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/elxnet-1946758428691706529.xml' +2026-04-04T00:01:00.811Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esx-ui-8293235535419959971.xml' +2026-04-04T00:01:00.811Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/infravisor-8997907133149463716.xml' +2026-04-04T00:01:00.811Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/iser--4693103531430742363.xml' +2026-04-04T00:01:00.812Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qcnic--5907714000110839638.xml' +2026-04-04T00:01:00.812Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-ens-esxio-5003902926672119979.xml' +2026-04-04T00:01:00.812Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esxio-dvfilter-generic-fastpath-5845242820056691884.xml' +2026-04-04T00:01:00.812Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvme-pcie--6244974539466769749.xml' +2026-04-04T00:01:00.812Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/vmware-hbrsrv--7613977505523482961.xml' schemaId='vmware-hbrsrv-8.0.3_0.0.24022510' +2026-04-04T00:01:00.812Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvme-pcie-esxio-5776915539691625648.xml' +2026-04-04T00:01:00.813Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nvmxnet3-5682775502847760051.xml' +2026-04-04T00:01:00.813Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pensandoatlas-6637280343931241140.xml' +2026-04-04T00:01:00.813Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/cndi-igc-7179879369177023159.xml' +2026-04-04T00:01:00.813Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rshim-net-155130162716811960.xml' +2026-04-04T00:01:00.814Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmware-esx-esxcli-nvme-plugin-esxio--1931829931018724169.xml' +2026-04-04T00:01:00.814Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esx-xserver-4002444622698975418.xml' +2026-04-04T00:01:00.814Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nenic-3487522764178328255.xml' +2026-04-04T00:01:00.814Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/penedac-esxio-6835941441514987712.xml' +2026-04-04T00:01:00.814Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/vcls-pod-crx--1512940133824995130.xml' schemaId='vcls-8.0.3_0.70.24677879' +2026-04-04T00:01:00.814Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-nvme-pcie-plugin-4945301170736811730.xml' +2026-04-04T00:01:00.814Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/igbn--8746341788573877036.xml' +2026-04-04T00:01:00.815Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/icen-8916530193377992404.xml' +2026-04-04T00:01:00.815Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bnxtnet--2632387123670074668.xml' +2026-04-04T00:01:00.815Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-core-esxio--7700149672255215910.xml' +2026-04-04T00:01:00.816Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-rdma-esxio-388014082325948122.xml' +2026-04-04T00:01:00.816Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/gc-6553940035640415455.xml' +2026-04-04T00:01:00.816Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/intelgpio-4683471463143431907.xml' +2026-04-04T00:01:00.816Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/mnet-esxio-2664124115780522213.xml' +2026-04-04T00:01:00.816Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmw-ahci--2863041570900223257.xml' +2026-04-04T00:01:00.817Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ionic-cloud-8808722694233827047.xml' +2026-04-04T00:01:00.817Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ne1000-7553652028367298799.xml' +2026-04-04T00:01:00.817Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/loadesxio--159341403978589451.xml' +2026-04-04T00:01:00.817Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vdfs--5534425978642025221.xml' +2026-04-04T00:01:00.817Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esxio-combiner-esxio--7415527965380486402.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-cc-7221626889777496831.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rste-3332480026299810559.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/mlnx-bfbootctl-esxio--6355423439093048575.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ionic-en-esxio--137840806006651640.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rd1173-esxio-6065201338491481866.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/crx-2409901468917765906.xml' +2026-04-04T00:01:00.818Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/trx--7871915387220191467.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/esxio-combiner--4257326179364409573.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-lsiv2-drivers-plugin-3707373368893515038.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/elxiscsi-7779274521910363945.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bmcal-407707237903790385.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/cpu-microcode-3874795112238877499.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-smartpqiv2-plugin--1654087741036243136.xml' +2026-04-04T00:01:00.819Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/i40en--7005967177474860220.xml' +2026-04-04T00:01:00.820Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/native-misc-drivers-esxio-6542998665911438665.xml' +2026-04-04T00:01:00.820Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ionic-en--3426907647270974127.xml' +2026-04-04T00:01:00.820Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qfle3f-2961494683449831764.xml' +2026-04-04T00:01:00.820Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedentv-7929314746453517149.xml' +2026-04-04T00:01:00.820Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/mtip32xx-native-781255057096333158.xml' +2026-04-04T00:01:00.821Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qfle3i--5072165982467884688.xml' +2026-04-04T00:01:00.821Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlx5-cc-esxio--2924112524489379977.xml' +2026-04-04T00:01:00.821Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/irdman--5252481207035399808.xml' +2026-04-04T00:01:00.821Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/dwi2c-3155320132838243204.xml' +2026-04-04T00:01:00.821Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/elx-esx-libelxima.so-4614560044973845893.xml' +2026-04-04T00:01:00.821Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qedrntv--2050234672095986300.xml' +2026-04-04T00:01:00.822Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/esx-update--596325549009631859.xml' schemaId='esx-update-8.0.3_0.70.24677879' +2026-04-04T00:01:00.822Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmkata-689454865869777299.xml' +2026-04-04T00:01:00.822Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lpfc-4107835131937698196.xml' +2026-04-04T00:01:00.822Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lsuv2-hpv2-hpsa-plugin-1891047060682121621.xml' +2026-04-04T00:01:00.822Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qflge-8159324616431210900.xml' +2026-04-04T00:01:00.822Z In(14) backup-check[133948]: xmlfile='/var/db/esximg/vibs/esxio--7380652109566714472.xml' schemaId='esxio-8.0.3_0.70.24677879' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/qlnativefc-5721695675842464164.xml' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vmksdhci-esxio-7183246134909510565.xml' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/brcmfcoe--7077156919334518355.xml' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/drivervm-gpu-base-7060616953794939310.xml' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nfnic--5839761048527452742.xml' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/pengpio-esxio-3957121686344348094.xml' +2026-04-04T00:01:00.823Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/sfvmk--7386550714053122617.xml' +2026-04-04T00:01:00.824Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/ixgben-7203919405131732427.xml' +2026-04-04T00:01:00.824Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/nmlxbf-pmc-esxio-1867198322714335691.xml' +2026-04-04T00:01:00.824Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/lpnic--6166021181119968307.xml' +2026-04-04T00:01:00.824Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vds-vsip--1884423871890520101.xml' +2026-04-04T00:01:00.824Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bcm-mpi3--8667463485179117088.xml' +2026-04-04T00:01:00.824Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/bnxtroce--4646501423496497182.xml' +2026-04-04T00:01:00.825Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/smartpqi--7747802190966660115.xml' +2026-04-04T00:01:00.825Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/gc-esxio-732539502187435506.xml' +2026-04-04T00:01:00.825Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/vsan-7350783098597441531.xml' +2026-04-04T00:01:00.825Z Db(15) backup-check[133948]: No ConfigSchema: xmlfile='/var/db/esximg/vibs/rshim-6097877412238171135.xml' +2026-04-04T00:01:00.825Z In(14) backup-check[133948]: Esx imagedb unlocked. +2026-04-04T00:01:00.866Z In(14) backup.sh[133926]: Locking esx.conf +2026-04-04T00:01:00.919Z In(14) backup.sh[133926]: Creating archive +2026-04-04T00:01:01.061Z In(14) backup.sh[133926]: Unlocked esx.conf +2026-04-04T00:01:01.089Z In(14) backup.sh[133926]: Using key ID 83820410-63af-4466-9270-279814d9120e to encrypt +2026-04-04T00:03:39.922Z In(38) sshd-session[134023]: Connection from 192.168.124.1 port 36422 +2026-04-04T00:03:43.846Z In(38) sshd-session[134023]: Accepted keyboard-interactive/pam for root from 192.168.124.1 port 36422 ssh2 +2026-04-04T00:03:43.862Z In(14) addvob[134027]: Log for VMware ESXi version=8.0.3 build=build-24677879 option=Release +2026-04-04T00:03:43.862Z In(14) addvob[134027]: Could not expand environment variable HOME. +2026-04-04T00:03:43.862Z In(14) addvob[134027]: Could not expand environment variable HOME. +2026-04-04T00:03:43.862Z In(14) addvob[134027]: Using VMware ESXi syslog APIs +2026-04-04T00:03:43.871Z In(86) sshd-session[134023]: pam_unix(sshd:session): session opened for user root by (uid=0) +2026-04-04T00:03:43.922Z In(38) sshd-session[134023]: User 'root' running command '/usr/lib/vmware/openssh/bin/sftp-server -f LOCAL5 -l INFO' +2026-04-04T00:03:43.930Z In(174) sftp-server[134030]: session opened for local user root from [192.168.124.1] +2026-04-04T00:03:43.934Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:43.935Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:43.936Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:43.936Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:43.937Z In(174) sftp-server[134030]: opendir "/" +2026-04-04T00:03:43.939Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:43.943Z In(174) sftp-server[134030]: closedir "/" +2026-04-04T00:03:43.951Z In(174) sftp-server[134030]: opendir "/var" +2026-04-04T00:03:43.952Z In(174) sftp-server[134030]: opendir "/var/lib" +2026-04-04T00:03:43.953Z In(174) sftp-server[134030]: closedir "/var/lib" +2026-04-04T00:03:43.955Z In(174) sftp-server[134030]: closedir "/var" +2026-04-04T00:03:43.960Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:43.970Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:43.971Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:47.286Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:47.293Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:47.294Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:47.295Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:47.296Z In(174) sftp-server[134030]: statvfs "/" +2026-04-04T00:03:47.302Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:47.302Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:47.303Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:47.305Z In(174) sftp-server[134030]: open "/.mtoolsrc" flags READ mode 00 +2026-04-04T00:03:47.306Z In(174) sftp-server[134030]: close "/.mtoolsrc" bytes read 20 written 0 +2026-04-04T00:03:47.307Z In(174) sftp-server[134030]: open "/local.tgz.ve" flags READ mode 00 +2026-04-04T00:03:47.309Z In(174) sftp-server[134030]: close "/local.tgz.ve" bytes read 32768 written 0 +2026-04-04T00:03:47.309Z In(174) sftp-server[134030]: open "/.#encryption.info" flags READ mode 00 +2026-04-04T00:03:47.310Z In(174) sftp-server[134030]: close "/.#encryption.info" bytes read 193 written 0 +2026-04-04T00:03:47.310Z In(174) sftp-server[134030]: open "/.runonce" flags READ mode 00 +2026-04-04T00:03:47.311Z In(174) sftp-server[134030]: close "/.runonce" bytes read 107 written 0 +2026-04-04T00:03:47.312Z In(174) sftp-server[134030]: open "/.ash_history" flags READ mode 00 +2026-04-04T00:03:47.313Z In(174) sftp-server[134030]: close "/.ash_history" bytes read 2813 written 0 +2026-04-04T00:03:47.355Z In(174) sftp-server[134030]: statvfs "/sbin" +2026-04-04T00:03:47.356Z In(174) sftp-server[134030]: statvfs "/sbin" +2026-04-04T00:03:55.224Z In(174) sftp-server[134030]: statvfs "/vmfs" +2026-04-04T00:03:55.230Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:55.231Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:55.232Z In(174) sftp-server[134030]: opendir "/vmfs" +2026-04-04T00:03:55.235Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:55.241Z In(174) sftp-server[134030]: closedir "/vmfs" +2026-04-04T00:03:55.241Z In(174) sftp-server[134030]: statvfs "/vmfs/devices" +2026-04-04T00:03:55.242Z In(174) sftp-server[134030]: statvfs "/vmfs/devices" +2026-04-04T00:03:55.250Z In(174) sftp-server[134030]: opendir "/vmfs/volumes" +2026-04-04T00:03:55.256Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:55.258Z In(174) sftp-server[134030]: closedir "/vmfs/volumes" +2026-04-04T00:03:56.674Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:56.675Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:56.680Z In(174) sftp-server[134030]: open "/.mtoolsrc" flags READ mode 00 +2026-04-04T00:03:56.682Z In(174) sftp-server[134030]: close "/.mtoolsrc" bytes read 20 written 0 +2026-04-04T00:03:56.682Z In(174) sftp-server[134030]: open "/local.tgz.ve" flags READ mode 00 +2026-04-04T00:03:56.684Z In(174) sftp-server[134030]: close "/local.tgz.ve" bytes read 32768 written 0 +2026-04-04T00:03:56.685Z In(174) sftp-server[134030]: open "/.#encryption.info" flags READ mode 00 +2026-04-04T00:03:56.686Z In(174) sftp-server[134030]: close "/.#encryption.info" bytes read 193 written 0 +2026-04-04T00:03:56.686Z In(174) sftp-server[134030]: open "/.runonce" flags READ mode 00 +2026-04-04T00:03:56.687Z In(174) sftp-server[134030]: close "/.runonce" bytes read 107 written 0 +2026-04-04T00:03:56.688Z In(174) sftp-server[134030]: open "/.ash_history" flags READ mode 00 +2026-04-04T00:03:56.689Z In(174) sftp-server[134030]: close "/.ash_history" bytes read 2813 written 0 +2026-04-04T00:03:56.741Z In(174) sftp-server[134030]: statvfs "/sbin" +2026-04-04T00:03:56.742Z In(174) sftp-server[134030]: statvfs "/sbin" +2026-04-04T00:03:58.693Z In(174) sftp-server[134030]: statvfs "/vmfs" +2026-04-04T00:03:58.801Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/fa49a770-2a48a074-9dbf-47e478068aac" +2026-04-04T00:03:58.825Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.829Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/fa49a770-2a48a074-9dbf-47e478068aac" +2026-04-04T00:03:58.829Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/3e9b04b6-4a441fdf-8a9d-dbcc6051d4b6" +2026-04-04T00:03:58.832Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.832Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/3e9b04b6-4a441fdf-8a9d-dbcc6051d4b6" +2026-04-04T00:03:58.832Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0" +2026-04-04T00:03:58.842Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.843Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0" +2026-04-04T00:03:58.843Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/.sdd.sf" +2026-04-04T00:03:58.846Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/.sdd.sf" +2026-04-04T00:03:58.846Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker" +2026-04-04T00:03:58.858Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.858Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker" +2026-04-04T00:03:58.859Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages" +2026-04-04T00:03:58.863Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.863Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages" +2026-04-04T00:03:58.863Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var" +2026-04-04T00:03:58.867Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.867Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var" +2026-04-04T00:03:58.868Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db" +2026-04-04T00:03:58.871Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.872Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db" +2026-04-04T00:03:58.872Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker" +2026-04-04T00:03:58.877Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.878Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker" +2026-04-04T00:03:58.878Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/vibs" +2026-04-04T00:03:58.883Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.884Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/vibs" +2026-04-04T00:03:58.884Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/bulletins" +2026-04-04T00:03:58.888Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.889Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/bulletins" +2026-04-04T00:03:58.889Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/profiles" +2026-04-04T00:03:58.892Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/profiles" +2026-04-04T00:03:58.892Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/baseimages" +2026-04-04T00:03:58.894Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/baseimages" +2026-04-04T00:03:58.894Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/addons" +2026-04-04T00:03:58.897Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/addons" +2026-04-04T00:03:58.897Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/solutions" +2026-04-04T00:03:58.899Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/solutions" +2026-04-04T00:03:58.899Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/manifests" +2026-04-04T00:03:58.901Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/manifests" +2026-04-04T00:03:58.901Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/reservedComponents" +2026-04-04T00:03:58.903Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/reservedComponents" +2026-04-04T00:03:58.903Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/reservedVibs" +2026-04-04T00:03:58.905Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/var/db/locker/reservedVibs" +2026-04-04T00:03:58.905Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/vmtoolsRepo" +2026-04-04T00:03:58.909Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.909Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/vmtoolsRepo" +2026-04-04T00:03:58.909Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/vmtoolsRepo/floppies" +2026-04-04T00:03:58.914Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.914Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/vmtoolsRepo/floppies" +2026-04-04T00:03:58.915Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/vmtoolsRepo/vmtools" +2026-04-04T00:03:58.921Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.922Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/locker/packages/vmtoolsRepo/vmtools" +2026-04-04T00:03:58.922Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware" +2026-04-04T00:03:58.926Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.926Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware" +2026-04-04T00:03:58.927Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware/lifecycle" +2026-04-04T00:03:58.931Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.931Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware/lifecycle" +2026-04-04T00:03:58.931Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware/lifecycle/hostSeed" +2026-04-04T00:03:58.936Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.936Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware/lifecycle/hostSeed" +2026-04-04T00:03:58.936Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware/lifecycle/hostSeed/esxioCachedVibs" +2026-04-04T00:03:58.946Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.948Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmware/lifecycle/hostSeed/esxioCachedVibs" +2026-04-04T00:03:58.948Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/cache" +2026-04-04T00:03:58.951Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.952Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/cache" +2026-04-04T00:03:58.952Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/cache/loadESX" +2026-04-04T00:03:58.956Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.957Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/cache/loadESX" +2026-04-04T00:03:58.957Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/core" +2026-04-04T00:03:58.960Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/core" +2026-04-04T00:03:58.960Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmkdump" +2026-04-04T00:03:58.964Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.965Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vmkdump" +2026-04-04T00:03:58.965Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/var" +2026-04-04T00:03:58.968Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.968Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/var" +2026-04-04T00:03:58.969Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/var/tmp" +2026-04-04T00:03:58.971Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/var/tmp" +2026-04-04T00:03:58.971Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/downloads" +2026-04-04T00:03:58.981Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/downloads" +2026-04-04T00:03:58.981Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log" +2026-04-04T00:03:58.989Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:58.992Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/log" +2026-04-04T00:03:58.992Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/store" +2026-04-04T00:03:58.995Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/store" +2026-04-04T00:03:58.995Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vdtc" +2026-04-04T00:03:58.998Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:59.001Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/vdtc" +2026-04-04T00:03:59.001Z In(174) sftp-server[134030]: opendir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/healthd" +2026-04-04T00:03:59.008Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:03:59.014Z In(174) sftp-server[134030]: closedir "/vmfs/volumes/69d0473d-ded27d57-be04-52540075d1a0/healthd" +2026-04-04T00:04:03.206Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:04:03.208Z In(174) sftp-server[134030]: sent status No such file +2026-04-04T00:04:03.217Z In(174) sftp-server[134030]: statvfs "/vmfs/devices" +2026-04-04T00:04:03.218Z In(174) sftp-server[134030]: statvfs "/vmfs/devices" diff --git a/tests/test_data/esxi/vibs/artemis--2860263652605340392.xml b/tests/test_data/esxi/vibs/artemis--2860263652605340392.xml new file mode 100644 index 00000000..e9a633de --- /dev/null +++ b/tests/test_data/esxi/vibs/artemis--2860263652605340392.xml @@ -0,0 +1 @@ +bootbankartemis0.19.0puffycidA small DFIR tool for ESXiA small DFIR tool for ESXi written in Rust2026-04-04T14:02:15+00:00https://puffycid.github.io/artemis-api2026-04-04T19:06:39.106766+00:00falsebin/artemiscommunitytruetruefalsetruefalsecad042f8016ca73b89b425eaf051a06f47e5e3f62bcba6701be452abd7cdccedcde8226f88d2165c8ba204a820acb8f19aa25750fcaadb95b6601bbe7594d4c6a2213c7c8ee6b96376aa4d1f0df11fcfa7dcd2cd \ No newline at end of file diff --git a/tests/test_data/firefox/v148.0.2/addons.json b/tests/test_data/firefox/v148.0.2/addons.json new file mode 100644 index 00000000..ca92a59c --- /dev/null +++ b/tests/test_data/firefox/v148.0.2/addons.json @@ -0,0 +1 @@ +{"schema":6,"addons":[{"id":"uBlock0@raymondhill.net","icons":{"32":"https://addons.mozilla.org/user-media/addon_icons/607/607454-32.png?modified=mcrushed","64":"https://addons.mozilla.org/user-media/addon_icons/607/607454-64.png?modified=mcrushed","128":"https://addons.mozilla.org/user-media/addon_icons/607/607454-128.png?modified=mcrushed"},"name":"uBlock Origin","version":"1.70.0","sourceURI":"https://addons.mozilla.org/firefox/downloads/file/4721638/ublock_origin-1.70.0.xpi","homepageURL":"https://github.com/gorhill/uBlock#ublock-origin","supportURL":"https://old.reddit.com/r/uBlockOrigin/","amoListingURL":"https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/","description":"Finally, an efficient wide-spectrum content blocker. Easy on CPU and memory.","fullDescription":"uBlock Origin is not an \"ad blocker\", it's a wide-spectrum content blocker with CPU and memory efficiency as a primary feature.\n\n\n\nOut of the box, uBO blocks ads, trackers, coin miners, popups, etc. through the following lists of filters, enabled by default:\n\n EasyList (ads)\n EasyPrivacy (tracking)\n Peter Lowe’s Ad server list (ads and tracking)\n Online Malicious URL Blocklist\n uBO's own lists\n\n\nMore lists are available for you to select if you wish:\n\n EasyList Cookie\n Fanboy Annoyances\n AdGuard Annoyances\n Dan Pollock’s hosts file\n And many others\n\n\nAdditionally, you can point-and-click to block JavaScript locally or globally, create your own global or local rules to override entries from filter lists, and many more advanced features.\n\n\n\nFree.\nOpen source with public license (GPLv3)\nFor users by users.\n\nIf ever you really do want to contribute something, think about the people working hard to maintain the filter lists you are using, which were made available to use by all for free.\n\n\n\n","weeklyDownloads":160239,"type":"extension","creator":{"name":"Raymond Hill","url":"https://addons.mozilla.org/en-US/firefox/user/11423598/"},"developers":[],"screenshots":[{"url":"https://addons.mozilla.org/user-media/previews/full/238/238546.png?modified=1622132421","width":1011,"height":758,"thumbnailURL":"https://addons.mozilla.org/user-media/previews/thumbs/238/238546.jpg?modified=1622132421","thumbnailWidth":533,"thumbnailHeight":400,"caption":"The popup panel: default mode"},{"url":"https://addons.mozilla.org/user-media/previews/full/238/238548.png?modified=1622132423","width":1011,"height":758,"thumbnailURL":"https://addons.mozilla.org/user-media/previews/thumbs/238/238548.jpg?modified=1622132423","thumbnailWidth":533,"thumbnailHeight":400,"caption":"The dashboard: stock filter lists"},{"url":"https://addons.mozilla.org/user-media/previews/full/238/238547.png?modified=1622132425","width":1011,"height":758,"thumbnailURL":"https://addons.mozilla.org/user-media/previews/thumbs/238/238547.jpg?modified=1622132425","thumbnailWidth":533,"thumbnailHeight":400,"caption":"The popup panel: default-deny mode"},{"url":"https://addons.mozilla.org/user-media/previews/full/238/238549.png?modified=1622132426","width":1011,"height":758,"thumbnailURL":"https://addons.mozilla.org/user-media/previews/thumbs/238/238549.jpg?modified=1622132426","thumbnailWidth":533,"thumbnailHeight":400,"caption":"The dashboard: settings"},{"url":"https://addons.mozilla.org/user-media/previews/full/238/238552.png?modified=1622132430","width":970,"height":1800,"thumbnailURL":"https://addons.mozilla.org/user-media/previews/thumbs/238/238552.jpg?modified=1622132430","thumbnailWidth":216,"thumbnailHeight":400,"caption":"The popup panel in Firefox Preview: default mode with more blocking options revealed"},{"url":"https://addons.mozilla.org/user-media/previews/full/230/230370.png?modified=1622132432","width":800,"height":600,"thumbnailURL":"https://addons.mozilla.org/user-media/previews/thumbs/230/230370.jpg?modified=1622132432","thumbnailWidth":533,"thumbnailHeight":400,"caption":"The unified logger tells you all that uBO is seeing and doing"}],"contributionURL":"","averageRating":4.7985,"reviewCount":5649,"reviewURL":"https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/reviews/","updateDate":1773409902000}]} \ No newline at end of file diff --git a/tests/test_data/firefox/v148.0.2/bookmarkbackups/bookmarks-2026-03-16_13_ClmxhYyn2y0lKuJm70IqmsKUdDOsXkkGsi04FbUGJOo=.jsonlz4 b/tests/test_data/firefox/v148.0.2/bookmarkbackups/bookmarks-2026-03-16_13_ClmxhYyn2y0lKuJm70IqmsKUdDOsXkkGsi04FbUGJOo=.jsonlz4 new file mode 100644 index 00000000..1dbd2590 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/bookmarkbackups/bookmarks-2026-03-16_13_ClmxhYyn2y0lKuJm70IqmsKUdDOsXkkGsi04FbUGJOo=.jsonlz4 differ diff --git a/tests/test_data/firefox/v148.0.2/cookies.sqlite b/tests/test_data/firefox/v148.0.2/cookies.sqlite new file mode 100644 index 00000000..90544d5a Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/cookies.sqlite differ diff --git a/tests/test_data/firefox/v148.0.2/cookies.sqlite-shm b/tests/test_data/firefox/v148.0.2/cookies.sqlite-shm new file mode 100644 index 00000000..b848be40 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/cookies.sqlite-shm differ diff --git a/tests/test_data/firefox/v148.0.2/cookies.sqlite-wal b/tests/test_data/firefox/v148.0.2/cookies.sqlite-wal new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_data/firefox/v148.0.2/extensions.json b/tests/test_data/firefox/v148.0.2/extensions.json new file mode 100644 index 00000000..73c22e2f --- /dev/null +++ b/tests/test_data/firefox/v148.0.2/extensions.json @@ -0,0 +1 @@ +{"schemaVersion":37,"addons":[{"id":"data-leak-blocker@mozilla.com","syncGUID":"{d4264d49-1e42-459e-8b5b-add2e9ea70a5}","version":"144.0.0","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Data Leak Blocker","description":"","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"139.0","maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":["mozillaAddons"],"origins":[],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/data-leak-blocker/","location":"app-builtin-addons"},{"id":"formautofill@mozilla.org","syncGUID":"{85d6a924-bb56-4ab3-9169-5cdffd6b6b18}","version":"1.0.1","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Form Autofill","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":null,"maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":[],"origins":[],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/formautofill/","location":"app-builtin-addons"},{"id":"ipp-activator@mozilla.com","syncGUID":"{52f9eca9-f914-43fb-bd25-add9393b2702}","version":"0.1","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"IPP Activator","description":"A system add-on to activate IPP in 143","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"143.0","maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":["tabs","cookies","webRequest"],"origins":[""],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/ipp-activator/","location":"app-builtin-addons"},{"id":"newtab@mozilla.org","syncGUID":"{6a64b538-408c-4909-8f87-0190286fe530}","version":"148.1.0","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"New Tab","description":"","creator":null,"developers":null,"translators":null,"contributors":null},"visible":false,"active":false,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"142.0a1","maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":[],"origins":[],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/newtab/","location":"app-builtin-addons"},{"id":"pictureinpicture@mozilla.org","syncGUID":"{9fcb88f5-6745-4988-b0bc-e8006497d51d}","version":"1.0.0","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Picture-In-Picture","description":"Fixes for web compatibility with Picture-in-Picture","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"88.0a1","maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":[],"origins":[],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/pictureinpicture/","location":"app-builtin-addons"},{"id":"addons-search-detection@mozilla.com","syncGUID":"{444b5a63-f84f-4fbe-807d-3ec57c1dffae}","version":"3.0.0","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Add-ons Search Detection","description":"","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":null,"maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":["telemetry","webRequest","webRequestBlocking"],"origins":[""],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/search-detection/","location":"app-builtin-addons"},{"id":"webcompat@mozilla.org","syncGUID":"{01bf7a30-c6e2-4559-bf61-1d1710fcb995}","version":"148.7.0","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Web Compatibility Interventions","description":"Urgent post-release fixes for web compatibility.","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"applyBackgroundUpdates":1,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"102.0","maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":["cookies","mozillaAddons","scripting","tabs","webNavigation","webRequest","webRequestBlocking"],"origins":[""],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":true,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-addons/webcompat/","location":"app-builtin-addons"},{"id":"default-theme@mozilla.org","syncGUID":"{b010615c-c81d-4eeb-ad53-6005288badbc}","version":"1.4.2","type":"theme","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"System theme — auto","description":"Follow the operating system setting for buttons, menus, and windows.","creator":"Mozilla","developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"installDate":1773713108092,"applyBackgroundUpdates":1,"path":null,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":null,"maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"userPermissions":null,"optionalPermissions":null,"requestedPermissions":null,"icons":{"32":"icon.svg"},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":false,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://default-theme/","location":"app-builtin"},{"id":"firefox-compact-light@mozilla.org","syncGUID":"{5b1f3cb1-c60f-4c39-aa79-d804ac438508}","version":"1.3.4","type":"theme","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Light","description":"A theme with a light color scheme.","creator":"Mozilla","developers":null,"translators":null,"contributors":null},"visible":true,"active":false,"userDisabled":true,"appDisabled":false,"embedderDisabled":false,"installDate":1773713109660,"applyBackgroundUpdates":1,"path":null,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":null,"maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"userPermissions":null,"optionalPermissions":null,"requestedPermissions":null,"icons":{"32":"icon.svg"},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":false,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-themes/light/","location":"app-builtin"},{"id":"firefox-alpenglow@mozilla.org","syncGUID":"{68e3c439-432a-4c6e-a7b2-6c3543ad4ba5}","version":"1.5.1","type":"theme","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Firefox Alpenglow","description":"Use a colorful appearance for buttons, menus, and windows.","creator":"Mozilla","developers":null,"translators":null,"contributors":null},"visible":true,"active":false,"userDisabled":true,"appDisabled":false,"embedderDisabled":false,"installDate":1773713109661,"applyBackgroundUpdates":1,"path":null,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":null,"maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"userPermissions":null,"optionalPermissions":null,"requestedPermissions":null,"icons":{"32":"icon.svg"},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":false,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-themes/alpenglow/","location":"app-builtin"},{"id":"firefox-compact-dark@mozilla.org","syncGUID":"{7313e27f-5c72-4138-9397-6719890fdd85}","version":"1.3.4","type":"theme","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"Dark","description":"A theme with a dark color scheme.","creator":"Mozilla","developers":null,"translators":null,"contributors":null},"visible":true,"active":false,"userDisabled":true,"appDisabled":false,"embedderDisabled":false,"installDate":1773713109661,"applyBackgroundUpdates":1,"path":null,"skinnable":false,"sourceURI":null,"releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":null,"maxVersion":null}],"targetPlatforms":[],"signedDate":null,"seen":true,"dependencies":[],"userPermissions":null,"optionalPermissions":null,"requestedPermissions":null,"icons":{"32":"icon.svg"},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":false,"installTelemetryInfo":null,"recommendationState":null,"rootURI":"resource://builtin-themes/dark/","location":"app-builtin"},{"id":"uBlock0@raymondhill.net","syncGUID":"{f0a60aa5-7c14-4a9c-99c9-60ce6b6c5c31}","version":"1.70.0","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":"dashboard.html","optionsType":3,"optionsBrowserStyle":false,"aboutURL":null,"defaultLocale":{"name":"uBlock Origin","description":"Finally, an efficient blocker. Easy on CPU and memory.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"installDate":1773713151619,"updateDate":1773713151619,"applyBackgroundUpdates":1,"path":"C:\\Users\\azur3\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\l94x8m7v.default-release\\extensions\\uBlock0@raymondhill.net.xpi","skinnable":false,"sourceURI":"https://addons.mozilla.org/firefox/downloads/file/4721638/ublock_origin-1.70.0.xpi","releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[{"name":"uBlock Origin","description":"Finally, an efficient blocker. Easy on CPU and memory.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["en"]},{"name":"uBlock Origin","description":"高効率ブロッカーついに登場。CPU とメモリーに負担をかけません。","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ja"]},{"name":"uBlock Origin","description":"Lõpuks on valminud tõhus blokeerija. Protsessori- ja mälusõbralik.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["et"]},{"name":"uBlock Origin","description":"Finalmente, um bloqueador eficiente. Leve para a CPU e a memória.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["pt-PT"]},{"name":"uBlock Origin","description":"Enfin un blocador eficaç. Sollicita pauc lo CPU e la memòria","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["oc"]},{"name":"uBlock Origin","description":"మొత్తానికి RAM ఇంకా CPU పై తేలికయిన, ఒక సమర్థవంతమైన నిరోధిని.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["te"]},{"name":"uBlock Origin","description":"Un bloqueur de nuisances efficace, qui ménagera votre processeur et votre mémoire vive.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["fr"]},{"name":"uBlock Origin","description":"O'r diwedd, rhwystrydd effeithlon sy'n gwell ar y CPU a'r cof.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["cy"]},{"name":"uBlock Origin","description":"Einlik, in effisjinte adblocker. Brûkt hast gjin prosessorkrêft of ûnthâld.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["fy"]},{"name":"uBlock Origin","description":"وأخيراً, مانع اعلانات كفوء. خفيف على المعالج و الذاكرة.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ar"]},{"name":"uBlock Origin","description":"Най-накрая, ефективен блокер. Щадящ процесора и паметта.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["bg"]},{"name":"uBlock Origin","description":"이 부가 기능은 효율적인 차단기입니다. CPU와 메모리에 주는 부담이 적습니다.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ko"]},{"name":"uBlock Origin","description":"Finalmente, un blocker efficiente. Leggero sulla CPU e sulla memoria.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["it"]},{"name":"uBlock Origin","description":"Endelig en effektiv blokkeringsutvidelse. Lavt CPU- og minnebruk.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["no"]},{"name":"uBlock Origin","description":"Pagaliau, efektyvus blokatorius, neapkraunantis nei procesoriaus, nei darbinės atminties.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["lt"]},{"name":"uBlock Origin","description":"Më në fund, një bllokues efikas që nuk e rëndon procesorin dhe memorien.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["sq"]},{"name":"uBlock Origin","description":"आख़िरकार, क्रोमियम-बेस्ड ब्राउज़रों के लिए एक कुशल अवरोधक। सीपीयू और मेमोरी पर कम भार के साथ।\n","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["hi"]},{"name":"uBlock Origin","description":"இறுதியாக, ஒரு திறமையான விளம்பரத் தடுப்பான். கணினியின் மையச் செயற்பகுதியின் மேலும் நினைவகத்தின் மேலும் இலகுவானது.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ta"]},{"name":"uBlock Origin","description":"Axır ki, prosessor və yaddaş yükünü azaldan səmərəli bir əngəlləyici var.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["az"]},{"name":"uBlock Origin","description":"มาแล้ว! โปรแกรมบล็อกโฆษณาได้อย่างมีประสิทธิภาพ โดยที่ไม่กินซีพียูหรือแรม","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["th"]},{"name":"uBlock Origin","description":"Конечно, ефикасен блокер. Лесен на CPU и меморија.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["mk"]},{"name":"uBlock Origin","description":"Ein effizienter Blocker mit geringer CPU- und Speicherauslastung.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["de"]},{"name":"uBlock Origin","description":"शेवटी, एक कार्यक्षम ब्लॉकर क्रोमियम आधारित ब्राउझरांसाठी. सीपीयू आणि मेमरी वर सोपे जातो.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["mr"]},{"name":"uBlock Origin","description":"Finally, an efficient blocker. Easy on CPU and memory.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["en-GB"]},{"name":"uBlock Origin","description":"Endelig en effektiv blokkeringsutvidelse. Lavt CPU- og minnebruk.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["nb"]},{"name":"uBlock Origin","description":"Ó fin, un bloqueador eficiente que non chupa toda a memoria e o procesador.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["gl"]},{"name":"uBlock Origin","description":"Finalment, un blocador eficient que utilitza pocs recursos de memòria i processador.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ca"]},{"name":"uBlock Origin","description":"Konečne efektívny blokovač. Nezaťažuje CPU ani pamäť.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["sk"]},{"name":"uBlock Origin","description":"Коначно, ефикасан блокатор. Ниски процесорски и меморијски захтеви.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["sr"]},{"name":"uBlock Origin","description":"Тинех Интернет тишкерӳҫӗ валли хӑвӑрт та витӗмлӗ чаркӑч пур.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["cv"]},{"name":"uBlock Origin","description":"بالاخره، یک بلاکر کارآمد. کم حجم بر روی پردازنده و حافظه.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["fa"]},{"name":"uBlock Origin","description":"അവസാനം, ഒരു കാര്യക്ഷമമായ ബ്ലോക്കര്‍. ലഘുവായ CPU, memory ഉപയോഗം.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ml"]},{"name":"uBlock Origin","description":"Akhirnya, penyekat yang cekap. Tidak membebankan CPU dan memori.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ms"]},{"name":"uBlock Origin","description":"一款高效的网络请求过滤工具,占用极低的内存和 CPU。","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["zh-CN"]},{"name":"uBlock Origin","description":"Končno, učinkovita, procesorju in pomnilniku prijazna razširitev za blokiranje oglasov.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["sl"]},{"name":"uBlock Origin","description":"Наконец-то, быстрый и эффективный блокировщик для браузеров.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ru"]},{"name":"uBlock Origin","description":"Nareszcie skuteczny bloker charakteryzujący się niskim użyciem procesora i pamięci.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["pl"]},{"name":"uBlock Origin","description":"Beidzot, efektīvs bloķētājs. Nepārslogo procesoru un atmiņu.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["lv"]},{"name":"uBlock Origin","description":"Erfin, ur stanker saotradurioù efedus hag a zouj d'ho reizhiad korvoiñ ha d'ho memor.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["br-FR"]},{"name":"uBlock Origin","description":"Mainam na pangharang sa content. Magaan sa CPU at memorya.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["fil"]},{"name":"uBlock Origin","description":"Konačno, efikasan blokator. Lak na CPU i memoriji.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["hr"]},{"name":"uBlock Origin","description":"Viimeinkin tehokas estotyökalu, joka ei kuormita prosessoria ja muistia.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["fi"]},{"name":"uBlock Origin","description":"Վերջապե՛ս, արդյունավետ արգելափակիչ։ Խնայում է մշակիչը և հիշողությունը։","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["hy"]},{"name":"uBlock Origin","description":"Жарнамаларды жақсы өшіретін Addon'дардың бірі. Компьютердің қуатың аз алады.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["kk"]},{"name":"uBlock Origin","description":"Нарэшце, эфектыўны блакавальнік. Не нагружае працэсар і памяць.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["be"]},{"name":"uBlock Origin","description":"În sfârșit, un blocant eficient. Are un impact mic asupra procesorului și memoriei.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ro"]},{"name":"uBlock Origin","description":"Konačno, efikasan bloker. Štedljiv na procesoru i memoriji.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["bs"]},{"name":"uBlock Origin","description":"અંતે, એક કાર્યક્ષમ અવરોધક. સીપીયુ અને મેમરી પર સરળ.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["gu"]},{"name":"uBlock Origin","description":"Επιτέλους, ένας αποτελεσματικός blocker. Ελαφρύς για τον επεξεργαστή και τη μνήμη.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["el"]},{"name":"uBlock Origin","description":"Até que enfim, um bloqueador eficiente. Bem otimizado para CPU e memória.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["pt-BR"]},{"name":"uBlock Origin","description":"Por fin, un bloqueador eficiente con uso mínimo de procesador y memoria.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["es"]},{"name":"uBlock Origin","description":"終於有一款高效能的封鎖工具。對 CPU 和記憶體的占用極低。","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["zh-TW"]},{"name":"uBlock Origin","description":"סוף סוף, חוסם יעיל. קל על המעבד והזיכרון.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["he"]},{"name":"uBlock Origin","description":"Eindelijk, een efficiënte blokker. Gebruikt weinig processorkracht en geheugen.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["nl"]},{"name":"uBlock Origin","description":"Endelig en effektiv blocker. Lavt CPU- og hukommelsesforbrug.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["da"]},{"name":"uBlock Origin","description":"Sonunda, etkili bir engelleyici. İşlemciyi ve belleği yormaz.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["tr"]},{"name":"uBlock Origin","description":"ਆਖਰਕਾਰ ਪ੍ਰਭਾਵੀ ਬਲੌਕਰ ਹੈ। CPU ਅਤੇ ਮੈਮੋਰੀ ਲਈ ਸੌਖਾ।","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["pa"]},{"name":"uBlock Origin","description":"Akhirnya, pemblokir iklan yang efisien. Ringan penggunaan CPU dan memori.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["id"]},{"name":"uBlock Origin","description":"একটি শক্তিশালী বিজ্ঞাপন প্রতিরোধক, অবশেষে তৈরী হল। যা সিপিইউ এবং মেমরির জন্য সহনীয়।","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["bn"]},{"name":"uBlock Origin","description":"Ugu dambeyntii, xannibaado hufan. Ku fudud oo ku saabsan CPU iyo xusuusta.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["so"]},{"name":"uBlock Origin","description":"Behingoz, blokeatzaile eraginkor bat. PUZ eta memorian arina.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["eu"]},{"name":"uBlock Origin","description":"Äntligen en effektiv blockerare. Lätt för både processor och minne.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["sv"]},{"name":"uBlock Origin","description":"Konečně efektivní blokovač. Nezatěžuje CPU a paměť.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["cs"]},{"name":"uBlock Origin","description":"Végre egy hatékony reklám- és követésblokkoló böngészőkhöz, amely kíméletes a processzorral és a memóriával.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["hu"]},{"name":"uBlock Origin","description":"ಕೊನೆಗೆ, ಒಂದು ದಕ್ಷ ನಿರ್ಬಂಧಕ. ಮಿತವಾದ ಸಿಪಿಯೂ ಹಾಗು ಮೆಮೊರಿ ಬಳಿಕೇಒಂದಿಗೆ .","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["kn"]},{"name":"uBlock Origin","description":"Ефективний блокувальник реклами таки з’явився. Не навантажує процесор та пам'ять.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["uk"]},{"name":"uBlock Origin","description":"آخر کار، ایک مؤثر اشتہار کو روکنے والا، یہ کم cpu اور میموری لیتا ہے.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ur"]},{"name":"uBlock Origin","description":"අවසානයේදී, මධ්‍ය සැකසුම් ඒකකය සහ මතකය අඩුවෙන් භාවිතා කරන කාර්යක්‍ෂම අවහිරකයක් ඇත.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["si"]},{"name":"uBlock Origin","description":"Finfine rendimenta reklamoblokilo. Afabla por ĉefprocesoro kaj memoro.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["eo"]},{"name":"uBlock Origin","description":"Hatimaye, kizuizi kinachofaa. Nyepesi kwenye CPU na kumbukumbu.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["sw"]},{"name":"uBlock Origin","description":"Cuối cùng, đã có một công cụ chặn quảng cáo hiệu quả, tiêu tốn ít CPU và bộ nhớ.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["vi"]},{"name":"uBlock Origin","description":"როგორც იქნა, მძლავრი და შედეგიანი რეკლამების შემზღუდავი. ზოგავს CPU-ს და მეხსიერებას.","creator":"Raymond Hill & contributors","developers":null,"translators":null,"contributors":null,"locales":["ka"]}],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"115.0","maxVersion":null}],"targetPlatforms":[],"signedState":2,"signedTypes":[2,1],"signedDate":1773407355000,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":["alarms","dns","menus","privacy","storage","tabs","unlimitedStorage","webNavigation","webRequest","webRequestBlocking"],"origins":["","http://*/*","https://*/*","file://*/*","https://easylist.to/*","https://*.fanboy.co.nz/*","https://filterlists.com/*","https://forums.lanik.us/*","https://github.com/*","https://*.github.io/*","https://github.com/uBlockOrigin/*","https://ublockorigin.github.io/*","https://*.reddit.com/r/uBlockOrigin/*"],"data_collection":["none"]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{"16":"img/ublock.svg","32":"img/ublock.svg","48":"img/ublock.svg","64":"img/ublock.svg","96":"img/ublock.svg","128":"img/ublock.svg"},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":null,"hidden":false,"installTelemetryInfo":{"source":"amo","sourceURL":"https://addons.mozilla.org/en-US/firefox/addon/ublock-origin/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search","method":"amWebAPI"},"recommendationState":{"validNotAfter":1931195354000,"validNotBefore":1773407354000,"states":["recommended","recommended-android"]},"rootURI":"jar:file:///C:/Users/azur3/AppData/Roaming/Mozilla/Firefox/Profiles/l94x8m7v.default-release/extensions/uBlock0@raymondhill.net.xpi!/","location":"app-profile"},{"id":"newtab@mozilla.org","syncGUID":"{63d191b2-83c3-4a89-b4b2-6217c5519fa3}","version":"150.1.20260304.231049","type":"extension","loader":null,"updateURL":null,"installOrigins":null,"manifestVersion":2,"optionsURL":null,"optionsType":null,"optionsBrowserStyle":true,"aboutURL":null,"defaultLocale":{"name":"New Tab","description":"","creator":null,"developers":null,"translators":null,"contributors":null},"visible":true,"active":true,"userDisabled":false,"appDisabled":false,"embedderDisabled":false,"installDate":1773713445153,"updateDate":1773713445153,"applyBackgroundUpdates":1,"path":"C:\\Users\\azur3\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\l94x8m7v.default-release\\extensions\\newtab@mozilla.org.xpi","skinnable":false,"sourceURI":"https://archive.mozilla.org/pub/system-addons/newtab/newtab-150.1.0-build1/newtab.xpi","releaseNotesURI":null,"softDisabled":false,"foreignInstall":false,"strictCompatibility":true,"locales":[],"targetApplications":[{"id":"toolkit@mozilla.org","minVersion":"142.0a1","maxVersion":null}],"targetPlatforms":[],"signedState":3,"signedTypes":[2,1],"signedDate":1772666472000,"seen":true,"dependencies":[],"incognito":"spanning","userPermissions":{"permissions":[],"origins":[],"data_collection":[]},"optionalPermissions":{"permissions":[],"origins":[],"data_collection":[]},"requestedPermissions":{"permissions":[],"origins":[],"data_collection":[]},"icons":{},"iconURL":null,"blocklistAttentionDismissed":false,"blocklistState":0,"blocklistURL":null,"startupData":{},"hidden":true,"installTelemetryInfo":{"source":"nimbus-newtabTrainhopAddon"},"recommendationState":null,"rootURI":"jar:file:///C:/Users/azur3/AppData/Roaming/Mozilla/Firefox/Profiles/l94x8m7v.default-release/extensions/newtab@mozilla.org.xpi!/","location":"app-profile"}]} \ No newline at end of file diff --git a/tests/test_data/firefox/v148.0.2/favicons.sqlite b/tests/test_data/firefox/v148.0.2/favicons.sqlite new file mode 100644 index 00000000..5a9a53d4 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/favicons.sqlite differ diff --git a/tests/test_data/firefox/v148.0.2/favicons.sqlite-shm b/tests/test_data/firefox/v148.0.2/favicons.sqlite-shm new file mode 100644 index 00000000..148791f2 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/favicons.sqlite-shm differ diff --git a/tests/test_data/firefox/v148.0.2/favicons.sqlite-wal b/tests/test_data/firefox/v148.0.2/favicons.sqlite-wal new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_data/firefox/v148.0.2/formhistory.sqlite b/tests/test_data/firefox/v148.0.2/formhistory.sqlite new file mode 100644 index 00000000..a1694316 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/formhistory.sqlite differ diff --git a/tests/test_data/firefox/v148.0.2/places.sqlite b/tests/test_data/firefox/v148.0.2/places.sqlite new file mode 100644 index 00000000..0d811f2c Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/places.sqlite differ diff --git a/tests/test_data/firefox/v148.0.2/places.sqlite-shm b/tests/test_data/firefox/v148.0.2/places.sqlite-shm new file mode 100644 index 00000000..6ef136a0 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/places.sqlite-shm differ diff --git a/tests/test_data/firefox/v148.0.2/places.sqlite-wal b/tests/test_data/firefox/v148.0.2/places.sqlite-wal new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_data/firefox/v148.0.2/sessionstore-backups/previous.jsonlz4 b/tests/test_data/firefox/v148.0.2/sessionstore-backups/previous.jsonlz4 new file mode 100644 index 00000000..d1db4ea4 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/sessionstore-backups/previous.jsonlz4 differ diff --git a/tests/test_data/firefox/v148.0.2/sessionstore-backups/upgrade.jsonlz4-20260309125808 b/tests/test_data/firefox/v148.0.2/sessionstore-backups/upgrade.jsonlz4-20260309125808 new file mode 100644 index 00000000..0f145d60 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/sessionstore-backups/upgrade.jsonlz4-20260309125808 differ diff --git a/tests/test_data/firefox/v148.0.2/sessionstore.jsonlz4 b/tests/test_data/firefox/v148.0.2/sessionstore.jsonlz4 new file mode 100644 index 00000000..5423c2d4 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/sessionstore.jsonlz4 differ diff --git a/tests/test_data/firefox/v148.0.2/storage.sqlite b/tests/test_data/firefox/v148.0.2/storage.sqlite new file mode 100644 index 00000000..2b1cee72 Binary files /dev/null and b/tests/test_data/firefox/v148.0.2/storage.sqlite differ diff --git a/tests/test_data/macos/lulu/rules.plist b/tests/test_data/macos/lulu/rules.plist new file mode 100644 index 00000000..019ef694 Binary files /dev/null and b/tests/test_data/macos/lulu/rules.plist differ diff --git a/tests/test_data/rustdesk/1.4.6/16197628.toml b/tests/test_data/rustdesk/1.4.6/16197628.toml new file mode 100644 index 00000000..49ab3cb2 --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/16197628.toml @@ -0,0 +1,62 @@ +password = [] +size = [ + 0, + 0, + 0, + 0, +] +size_ft = [ + 0, + 0, + 0, + 0, +] +size_pf = [ + 0, + 0, + 0, + 0, +] +view_style = 'original' +scroll_style = 'scrollauto' +edge_scroll_edge_thickness = 100 +image_quality = 'balanced' +custom_image_quality = [50] +show_remote_cursor = false +lock_after_session_end = false +terminal-persistent = false +privacy_mode = false +allow_swap_key = false +port_forwards = [] +direct_failures = 0 +disable_audio = false +disable_clipboard = false +enable-file-copy-paste = true +show_quality_monitor = false +follow_remote_cursor = false +follow_remote_window = false +keyboard_mode = 'map' +view_only = false +show_my_cursor = false +sync-init-clipboard = false +trackpad-speed = 100 + +[options] +codec-preference = 'auto' +custom-fps = '30' +zoom-cursor = '' +swap-left-right-mouse = '' +i444 = '' +collapse_toolbar = '' + +[ui_flutter] +wm_RemoteDesktop = '{"width":1300.0,"height":740.0,"offsetWidth":0.0,"offsetHeight":0.0,"isMaximized":false,"isFullscreen":false}' + +[info] +username = 'centos' +hostname = 'localhost.localdomain' +platform = 'Linux' + +[transfer] +write_jobs = [] +read_jobs = [] diff --git a/tests/test_data/rustdesk/1.4.6/RustDesk.toml b/tests/test_data/rustdesk/1.4.6/RustDesk.toml new file mode 100644 index 00000000..44f24b3d --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/RustDesk.toml @@ -0,0 +1,109 @@ +enc_id = '00zuLkYB/lhZyfs5rO4QPYQ+U0DdXwPfxb' +password = '' +salt = 'csahhv' +key_pair = [ + [ + 181, + 78, + 220, + 48, + 118, + 199, + 184, + 21, + 27, + 18, + 219, + 150, + 73, + 228, + 145, + 134, + 20, + 239, + 136, + 238, + 102, + 217, + 210, + 101, + 69, + 216, + 12, + 138, + 207, + 174, + 204, + 112, + 105, + 222, + 53, + 161, + 148, + 139, + 224, + 30, + 60, + 86, + 20, + 175, + 210, + 83, + 36, + 84, + 232, + 39, + 84, + 226, + 193, + 72, + 15, + 5, + 84, + 52, + 183, + 166, + 213, + 173, + 214, + 148, +], + [ + 105, + 222, + 53, + 161, + 148, + 139, + 224, + 30, + 60, + 86, + 20, + 175, + 210, + 83, + 36, + 84, + 232, + 39, + 84, + 226, + 193, + 72, + 15, + 5, + 84, + 52, + 183, + 166, + 213, + 173, + 214, + 148, +], +] +key_confirmed = true + +[keys_confirmed] +rs-ny = true diff --git a/tests/test_data/rustdesk/1.4.6/RustDesk2.toml b/tests/test_data/rustdesk/1.4.6/RustDesk2.toml new file mode 100644 index 00000000..8f786750 --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/RustDesk2.toml @@ -0,0 +1,9 @@ +rendezvous_server = 'rs-ny.rustdesk.com:21116' +nat_type = 1 +serial = 0 +unlock_pin = '' +trusted_devices = '' + +[options] +av1-test = 'Y' +local-ip-addr = '192.168.124.45' diff --git a/tests/test_data/rustdesk/1.4.6/RustDesk_local.toml b/tests/test_data/rustdesk/1.4.6/RustDesk_local.toml new file mode 100644 index 00000000..33eea1ae --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/RustDesk_local.toml @@ -0,0 +1,20 @@ +remote_id = '34573289457389sf' +kb_layout_type = '' +size = [ + 0, + 0, + 0, + 0, +] +fav = [] + +[options] +wayland-restore-token = '04381250-efab-4eee-a3f8-c77475480372' +remote-menubar-drag-right = '1.0' +remote-menubar-drag-left = '0.0' +show-selinux-help-tip = 'N' + +[ui_flutter] +wm_Main = '{"width":800.0,"height":600.0,"offsetWidth":0.0,"offsetHeight":0.0,"isMaximized":false,"isFullscreen":false}' +peer-sorting = 'Remote ID' +wm_RemoteDesktop = '{"width":1300.0,"height":740.0,"offsetWidth":0.0,"offsetHeight":0.0,"isMaximized":false,"isFullscreen":false}' diff --git a/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-47.log b/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-47.log new file mode 100644 index 00000000..8d41cec0 --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-47.log @@ -0,0 +1,29 @@ +[2025-11-23 19:44:47.656957 -05:00] INFO [src/core_main.rs:360] start --server with user centos +[2025-11-23 19:44:48.354301 -05:00] INFO [src/server.rs:545] DISPLAY=Ok(":0") +[2025-11-23 19:44:48.354351 -05:00] INFO [src/server.rs:546] XAUTHORITY=Ok("/run/user/1000/.mutter-Xwaylandauth.O4JRG3") +[2025-11-23 19:44:48.354788 -05:00] INFO [src/ipc.rs:339] Started ipc server at path: /tmp/RustDesk/ipc +[2025-11-23 19:44:48.357074 -05:00] INFO [src/server/input_service.rs:580] UInput keyboard created +[2025-11-23 19:44:48.358631 -05:00] INFO [src/server/input_service.rs:582] UInput mouse created +[2025-11-23 19:44:48.387600 -05:00] INFO [src/common.rs:592] Testing nat ... +[2025-11-23 19:44:48.387713 -05:00] DEBUG [src/server.rs:663] #tries of ipc service connection: 30 +[2025-11-23 19:44:48.417096 -05:00] WARN [src/common.rs:2116] Failed to bind IPv6 socket: Network is unreachable (os error 101) +[2025-11-23 19:44:48.442031 -05:00] INFO [src/rendezvous_mediator.rs:400] start rendezvous mediator of rs-ny.rustdesk.com +[2025-11-23 19:44:48.442459 -05:00] INFO [src/rendezvous_mediator.rs:158] start udp: rs-ny.rustdesk.com:21116 +[2025-11-23 19:44:48.442954 -05:00] INFO [src/lan.rs:31] lan discovery listener started +[2025-11-23 19:44:48.444237 -05:00] INFO [libs/scrap/src/common/codec.rs:1027] cpu num: 22, cpu loadavg: 1.7, available memory: 9G, codec thread: 4 +[2025-11-23 19:44:48.461418 -05:00] INFO [libs/scrap/src/common/hwcodec.rs:523] set hwcodec config +[2025-11-23 19:44:48.461439 -05:00] DEBUG [libs/scrap/src/common/hwcodec.rs:524] HwCodecConfig { signature: 0, ram_encode: [], ram_decode: [CodecInfo { name: "h264", mc_name: None, format: H264, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }, CodecInfo { name: "hevc", mc_name: None, format: H265, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }] } +[2025-11-23 19:44:48.511122 -05:00] ERROR [src/common.rs:2168] Failed to get public IPv6 address: UDP socket error +[2025-11-23 19:44:48.608904 -05:00] DEBUG [libs/hbb_common/src/udp.rs:36] Receive buf size of udp 0.0.0.0:0: Ok(212992) +[2025-11-23 19:44:48.610133 -05:00] INFO [src/rendezvous_mediator.rs:682] register_pk of rs-ny due to key not confirmed +[2025-11-23 19:44:48.610229 -05:00] INFO [libs/hbb_common/src/config.rs:958] Generated new keypair for id: +[2025-11-23 19:44:48.650595 -05:00] INFO [libs/scrap/src/common/codec.rs:1140] av1 time: key: 30.304564ms, non-key: 14.845264ms, consume: 203.045937ms +[2025-11-23 19:44:48.717954 -05:00] DEBUG [src/common.rs:620] Got nat response from rs-ny.rustdesk.com:21116: port=37535 +[2025-11-23 19:44:48.726009 -05:00] DEBUG [libs/hbb_common/src/config.rs:811] Update rendezvous_server in config to rs-ny.rustdesk.com:21116 +[2025-11-23 19:44:48.726038 -05:00] DEBUG [libs/hbb_common/src/config.rs:812] {"rs-ny.rustdesk.com:21116": 115890} +[2025-11-23 19:44:48.726274 -05:00] DEBUG [src/rendezvous_mediator.rs:204] Latency of rs-ny.rustdesk.com:21116: 115.89ms +[2025-11-23 19:44:48.950981 -05:00] DEBUG [src/common.rs:620] Got nat response from rs-ny.rustdesk.com:21115: port=37535 +[2025-11-23 19:44:48.951838 -05:00] INFO [src/common.rs:646] Tested nat type: ASYMMETRIC in 564.122562ms +[2025-11-23 19:44:48.990004 -05:00] INFO [src/server.rs:700] config updated, sync to root +[2025-11-23 19:44:49.417761 -05:00] INFO [libs/scrap/src/common/hwcodec.rs:742] Check hwcodec config, exit with: exit status: 0 +[2025-11-23 19:44:56.517233 -05:00] INFO [src/server.rs:700] config updated, sync to root diff --git a/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-48.log b/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-48.log new file mode 100644 index 00000000..1a25284a --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-48.log @@ -0,0 +1,20 @@ +[2025-11-23 19:44:48.453823 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/common.rs:118] Successfully set up parent death signal +[2025-11-23 19:44:48.454650 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/encode.rs:193] GPU support detected - NV: false, AMF: false, Intel: false +[2025-11-23 19:44:48.455401 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/encode.rs:310] Testing encoder: h264_vaapi +[2025-11-23 19:44:48.457336 -05:00] ERROR [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg.rs:34] Failed to initialise VAAPI connection: -1 (unknown libva error). + +[2025-11-23 19:44:48.457607 -05:00] ERROR [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/lib.rs:17] [FFMPEG_RAM_ENC] av_hwdevice_ctx_create failed +[2025-11-23 19:44:48.457633 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/encode.rs:386] Failed to create encoder h264_vaapi +[2025-11-23 19:44:48.458332 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/decode.rs:169] Linux GPU support detected - NV: false +[2025-11-23 19:44:48.458347 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/decode.rs:268] Testing decoder: h264 (hwdevice: AV_HWDEVICE_TYPE_VAAPI) +[2025-11-23 19:44:48.459589 -05:00] ERROR [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg.rs:34] Failed to initialise VAAPI connection: -1 (unknown libva error). + +[2025-11-23 19:44:48.459784 -05:00] ERROR [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/lib.rs:17] [FFMPEG_RAM_DEC] av_hwdevice_ctx_create failed, ret = Input/output error +[2025-11-23 19:44:48.459801 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/decode.rs:313] Failed to create decoder h264 +[2025-11-23 19:44:48.459807 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/decode.rs:268] Testing decoder: hevc (hwdevice: AV_HWDEVICE_TYPE_VAAPI) +[2025-11-23 19:44:48.460862 -05:00] ERROR [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg.rs:34] Failed to initialise VAAPI connection: -1 (unknown libva error). + +[2025-11-23 19:44:48.461071 -05:00] ERROR [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/lib.rs:17] [FFMPEG_RAM_DEC] av_hwdevice_ctx_create failed, ret = Input/output error +[2025-11-23 19:44:48.461090 -05:00] DEBUG [/root/.cargo/git/checkouts/hwcodec-74796a7f8f16bbb9/398e5a8/src/ffmpeg_ram/decode.rs:313] Failed to create decoder hevc +[2025-11-23 19:44:48.461098 -05:00] DEBUG [libs/scrap/src/common/hwcodec.rs:713] HwCodecConfig { signature: 0, ram_encode: [], ram_decode: [CodecInfo { name: "h264", mc_name: None, format: H264, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }, CodecInfo { name: "hevc", mc_name: None, format: H265, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }] } +[2025-11-23 19:44:48.461380 -05:00] INFO [src/ipc.rs:1377] send ok diff --git a/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-55.log b/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-55.log new file mode 100644 index 00000000..147a9330 --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/rustdesk_r2025-11-23_19-44-55.log @@ -0,0 +1,8 @@ +[2025-11-23 19:44:55.545353 -05:00] INFO [src/server.rs:545] DISPLAY=Ok(":0") +[2025-11-23 19:44:55.546613 -05:00] INFO [src/server.rs:546] XAUTHORITY=Ok("/run/user/1000/.mutter-Xwaylandauth.O4JRG3") +[2025-11-23 19:44:55.548920 -05:00] INFO [src/server.rs:585] config synced +[2025-11-23 19:44:56.549872 -05:00] INFO [libs/scrap/src/common/hwcodec.rs:523] set hwcodec config +[2025-11-23 19:44:56.549953 -05:00] DEBUG [libs/scrap/src/common/hwcodec.rs:524] HwCodecConfig { signature: 0, ram_encode: [], ram_decode: [CodecInfo { name: "h264", mc_name: None, format: H264, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }, CodecInfo { name: "hevc", mc_name: None, format: H265, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }] } +[2025-11-23 19:44:56.977749 -05:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:768] connecting to 49.12.46.241:443 +[2025-11-23 19:44:57.098781 -05:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:771] connected to 49.12.46.241:443 +[2025-11-23 19:44:57.346405 -05:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/pool.rs:395] pooling idle connection for ("https", api.rustdesk.com) diff --git a/tests/test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-11-54.log b/tests/test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-11-54.log new file mode 100644 index 00000000..5df20c0f --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-11-54.log @@ -0,0 +1,60 @@ +[2026-04-12 17:11:54.885411 -04:00] INFO [src/server.rs:582] DISPLAY=Ok(":0") +[2026-04-12 17:11:54.886185 -04:00] INFO [src/server.rs:583] XAUTHORITY=Ok("/run/user/1000/.mutter-Xwaylandauth.WM38M3") +[2026-04-12 17:11:55.889640 -04:00] INFO [libs/scrap/src/common/hwcodec.rs:523] set hwcodec config +[2026-04-12 17:11:55.889748 -04:00] DEBUG [libs/scrap/src/common/hwcodec.rs:524] HwCodecConfig { signature: 0, ram_encode: [], ram_decode: [CodecInfo { name: "h264", mc_name: None, format: H264, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }, CodecInfo { name: "hevc", mc_name: None, format: H265, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }] } +[2026-04-12 17:11:56.292792 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:768] connecting to 49.12.46.241:443 +[2026-04-12 17:11:56.442700 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:771] connected to 49.12.46.241:443 +[2026-04-12 17:11:56.914518 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/pool.rs:395] pooling idle connection for ("https", api.rustdesk.com) +[2026-04-12 17:12:04.147039 -04:00] INFO [src/flutter.rs:1408] Session 16197628 start, use texture render: true +[2026-04-12 17:12:04.563282 -04:00] DEBUG [src/client.rs:392] TCP connection establishment time used: 411.056234ms +[2026-04-12 17:12:04.563419 -04:00] INFO [src/client.rs:407] rendezvous server: rs-ny.rustdesk.com:21116 +[2026-04-12 17:12:04.563743 -04:00] INFO [src/client.rs:470] #1 TCP punch attempt with 192.168.124.45:38937, id: 16197628 +[2026-04-12 17:12:04.967757 -04:00] INFO [src/client.rs:527] TCP Hole Punched 16197628 = 192.168.124.45:38665 +[2026-04-12 17:12:04.967866 -04:00] INFO [src/client.rs:592] 815 ms used to TCP punch hole, relay_server: ovh-east2.rustdesk.com, is_local: true +[2026-04-12 17:12:04.967878 -04:00] INFO [src/client.rs:687] peer address: 192.168.124.45:38665, timeout: 1000 +[2026-04-12 17:12:04.968308 -04:00] INFO [src/client.rs:736] 421.158µs used to establish TCP connection with TCP punch +[2026-04-12 17:12:04.970369 -04:00] DEBUG [src/client.rs:750] TCP punch secure_connection ok +[2026-04-12 17:12:04.970512 -04:00] DEBUG [src/client/io_loop.rs:204] get cliprdr client for conn_id 1 +[2026-04-12 17:12:20.112745 -04:00] WARN [src/client/io_loop.rs:1966] Message box ignore link for security +[2026-04-12 17:12:23.463178 -04:00] DEBUG [src/ui_session_interface.rs:1775] handle_peer_info :PeerInfo { username: "centos", hostname: "localhost.localdomain", platform: "Linux", displays: [DisplayInfo { x: 0, y: 0, width: 3840, height: 2242, name: "", online: true, cursor_embedded: false, original_resolution: MessageField(Some(Resolution { width: 3840, height: 2242, special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } })), scale: 1.0, special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } }], current_display: 0, sas_enabled: false, version: "1.4.6", features: MessageField(Some(Features { privacy_mode: false, terminal: true, special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } })), encoding: MessageField(Some(SupportedEncoding { h264: false, h265: false, vp8: true, av1: true, i444: MessageField(Some(CodecAbility { vp8: false, vp9: true, av1: true, h264: false, h265: false, special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } })), special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } })), resolutions: MessageField(Some(SupportedResolutions { resolutions: [], special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } })), platform_additions: "{\"has_file_clipboard\":true,\"is_wayland\":true,\"support_view_camera\":true}", windows_sessions: MessageField(None), special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } } +[2026-04-12 17:12:23.464859 -04:00] INFO [src/client.rs:2589] peer info supported_encoding:SupportedEncoding { h264: false, h265: false, vp8: true, av1: true, i444: MessageField(Some(CodecAbility { vp8: false, vp9: true, av1: true, h264: false, h265: false, special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } })), special_fields: SpecialFields { unknown_fields: UnknownFields { fields: None }, cached_size: CachedSize { size: 0 } } } +[2026-04-12 17:12:23.465002 -04:00] INFO [src/clipboard.rs:803] Subscribe clipboard listener: client-clipboard +[2026-04-12 17:12:23.465016 -04:00] INFO [src/clipboard.rs:812] Start clipboard listener thread +[2026-04-12 17:12:23.465177 -04:00] INFO [src/clipboard.rs:829] Clipboard listener thread started +[2026-04-12 17:12:23.465188 -04:00] INFO [src/clipboard.rs:832] Clipboard listener subscribed: client-clipboard +[2026-04-12 17:12:23.465195 -04:00] INFO [src/client.rs:982] Start client clipboard loop +[2026-04-12 17:12:23.465163 -04:00] DEBUG [src/clipboard.rs:867] Clipboard listener started +[2026-04-12 17:12:23.595739 -04:00] DEBUG [src/client.rs:2961] recved audio format, sample rate=48000 +[2026-04-12 17:12:23.864858 -04:00] INFO [src/client.rs:3022] 51.845µs used to get hwcodec config +[2026-04-12 17:12:23.864954 -04:00] INFO [src/client.rs:1562] new video handler for display #0, format: AV1, luid: None +[2026-04-12 17:12:23.864960 -04:00] INFO [libs/scrap/src/common/codec.rs:502] try create new decoder, format: AV1, _luid: None +[2026-04-12 17:12:23.865789 -04:00] INFO [libs/scrap/src/common/codec.rs:1027] cpu num: 22, cpu loadavg: 4.81, available memory: 9G, codec thread: 4 +[2026-04-12 17:12:23.865823 -04:00] INFO [libs/scrap/src/common/codec.rs:597] create AV1 decoder success +[2026-04-12 17:12:26.971419 -04:00] INFO [src/client/io_loop.rs:1223] Set fps to 13 +[2026-04-12 17:12:29.975392 -04:00] INFO [src/client/io_loop.rs:1223] Set fps to 16 +[2026-04-12 17:12:31.976053 -04:00] INFO [src/client/io_loop.rs:1223] Set fps to 19 +[2026-04-12 17:12:36.971857 -04:00] INFO [src/client/io_loop.rs:1223] Set fps to 29 +[2026-04-12 17:12:42.922386 -04:00] DEBUG [src/client/io_loop.rs:321] Exit io_loop of id=16197628 +[2026-04-12 17:12:42.922487 -04:00] INFO [src/clipboard.rs:837] Unsubscribe clipboard listener: client-clipboard +[2026-04-12 17:12:42.922503 -04:00] INFO [src/clipboard.rs:848] Stop clipboard listener thread +[2026-04-12 17:12:42.922538 -04:00] DEBUG [src/client.rs:1000] Clipboard listener stopped +[2026-04-12 17:12:42.922594 -04:00] INFO [src/client.rs:1014] Stop client clipboard loop +[2026-04-12 17:12:42.923661 -04:00] DEBUG [src/clipboard.rs:871] Clipboard listener stopped +[2026-04-12 17:12:42.923724 -04:00] INFO [src/clipboard.rs:851] Clipboard listener thread stopped +[2026-04-12 17:12:42.923733 -04:00] INFO [src/clipboard.rs:854] Clipboard listener unsubscribed: client-clipboard +[2026-04-12 17:12:42.924244 -04:00] INFO [src/client/io_loop.rs:1070] meta: TransferSerde { write_jobs: [], read_jobs: [] } +[2026-04-12 17:12:42.924287 -04:00] INFO [src/client.rs:2970] Audio decoder loop exits +[2026-04-12 17:12:42.925526 -04:00] WARN [/root/.cargo/git/checkouts/arboard-19a3844fd9940437/85be121/src/platform/linux/mod.rs:96] Tried to initialize the wayland data control protocol clipboard, but failed. Falling back to the X11 clipboard protocol. The error was: arboard: failed to check is_primary_selection_supported, A required Wayland protocol (zwlr_data_control_manager_v1 version 1) is not supported by the compositor +[2026-04-12 17:12:42.938030 -04:00] INFO [src/client.rs:2944] Video decoder loop exits +[2026-04-12 17:12:43.420984 -04:00] ERROR [src/ui_interface.rs:1198] ipc connection closed: reset by the peer +[2026-04-12 17:12:45.583298 -04:00] ERROR [libs/hbb_common/src/platform/mod.rs:50] Got signal 11 and exit. stack: +gtk_window_is_maximized +_ZL40window_manager_plugin_handle_method_callP20_WindowManagerPluginP13_FlMethodCall +g_main_context_iteration +g_application_run +main +__libc_start_call_main +__libc_start_main@@GLIBC_2.34 +_start +[2026-04-12 17:12:45.605324 -04:00] INFO [libs/libxdo-sys-stub/src/lib.rs:114] libxdo-sys Loaded libxdo.so.3 +[2026-04-12 17:12:45.607313 -04:00] INFO [libs/enigo/src/linux/xdo.rs:54] xdo context created successfully diff --git a/tests/test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-12-43.log b/tests/test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-12-43.log new file mode 100644 index 00000000..bae82923 --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/rustdesk_r2026-04-12_17-12-43.log @@ -0,0 +1,28 @@ +[2026-04-12 17:12:43.826602 -04:00] INFO [src/core_main.rs:375] start --server with user centos +[2026-04-12 17:12:43.868187 -04:00] INFO [src/server.rs:582] DISPLAY=Ok(":0") +[2026-04-12 17:12:43.868251 -04:00] INFO [src/server.rs:583] XAUTHORITY=Ok("/run/user/1000/.mutter-Xwaylandauth.WM38M3") +[2026-04-12 17:12:43.874747 -04:00] INFO [src/server/input_service.rs:613] UInput keyboard created +[2026-04-12 17:12:43.878274 -04:00] INFO [src/server/input_service.rs:615] UInput mouse created +[2026-04-12 17:12:43.969040 -04:00] INFO [libs/libxdo-sys-stub/src/lib.rs:114] libxdo-sys Loaded libxdo.so.3 +[2026-04-12 17:12:43.970851 -04:00] INFO [libs/enigo/src/linux/xdo.rs:54] xdo context created successfully +[2026-04-12 17:12:43.971441 -04:00] DEBUG [src/server.rs:730] #tries of ipc service connection: 30 +[2026-04-12 17:12:43.976858 -04:00] INFO [src/ipc.rs:440] Started ipc server at path: /tmp/RustDesk/ipc +[2026-04-12 17:12:44.274137 -04:00] INFO [src/common.rs:627] Testing nat ... +[2026-04-12 17:12:44.322185 -04:00] INFO [libs/scrap/src/common/codec.rs:1046] skip test av1 +[2026-04-12 17:12:44.322356 -04:00] INFO [src/rendezvous_mediator.rs:401] start rendezvous mediator of rs-ny.rustdesk.com +[2026-04-12 17:12:44.322467 -04:00] INFO [src/lan.rs:31] lan discovery listener started +[2026-04-12 17:12:44.323172 -04:00] INFO [src/rendezvous_mediator.rs:159] start udp: rs-ny.rustdesk.com:21116 +[2026-04-12 17:12:44.325337 -04:00] WARN [src/common.rs:2146] Failed to bind IPv6 socket: Network is unreachable (os error 101) +[2026-04-12 17:12:44.327350 -04:00] ERROR [src/common.rs:2198] Failed to get public IPv6 address: UDP socket error +[2026-04-12 17:12:44.330939 -04:00] INFO [libs/scrap/src/common/hwcodec.rs:523] set hwcodec config +[2026-04-12 17:12:44.330965 -04:00] DEBUG [libs/scrap/src/common/hwcodec.rs:524] HwCodecConfig { signature: 0, ram_encode: [], ram_decode: [CodecInfo { name: "h264", mc_name: None, format: H264, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }, CodecInfo { name: "hevc", mc_name: None, format: H265, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }] } +[2026-04-12 17:12:44.459541 -04:00] DEBUG [libs/hbb_common/src/udp.rs:36] Receive buf size of udp 0.0.0.0:0: Ok(212992) +[2026-04-12 17:12:44.541399 -04:00] DEBUG [src/common.rs:655] Got nat response from rs-ny.rustdesk.com:21116: port=35913 +[2026-04-12 17:12:44.595993 -04:00] DEBUG [src/rendezvous_mediator.rs:205] Latency of rs-ny.rustdesk.com:21116: 135.041ms +[2026-04-12 17:12:44.806230 -04:00] DEBUG [src/common.rs:655] Got nat response from rs-ny.rustdesk.com:21115: port=35913 +[2026-04-12 17:12:44.806734 -04:00] INFO [src/common.rs:681] Tested nat type: ASYMMETRIC in 532.451029ms +[2026-04-12 17:12:45.304258 -04:00] INFO [libs/scrap/src/common/hwcodec.rs:742] Check hwcodec config, exit with: exit status: 0 +[2026-04-12 17:24:25.832716 -04:00] DEBUG [src/rendezvous_mediator.rs:205] Latency of rs-ny.rustdesk.com:21116: 172.372ms +[2026-04-12 17:34:20.711587 -04:00] DEBUG [src/rendezvous_mediator.rs:205] Latency of rs-ny.rustdesk.com:21116: 179.851ms +[2026-04-12 17:37:51.834971 -04:00] DEBUG [src/rendezvous_mediator.rs:205] Latency of rs-ny.rustdesk.com:21116: 227.789ms +[2026-04-12 17:40:19.906726 -04:00] DEBUG [src/rendezvous_mediator.rs:205] Latency of rs-ny.rustdesk.com:21116: 285.641ms diff --git a/tests/test_data/rustdesk/1.4.6/rustdesk_rCURRENT.log b/tests/test_data/rustdesk/1.4.6/rustdesk_rCURRENT.log new file mode 100644 index 00000000..f04ddda9 --- /dev/null +++ b/tests/test_data/rustdesk/1.4.6/rustdesk_rCURRENT.log @@ -0,0 +1,24 @@ +[2026-04-25 12:19:19.390687 -04:00] INFO [src/server.rs:582] DISPLAY=Ok(":0") +[2026-04-25 12:19:19.391295 -04:00] INFO [src/server.rs:583] XAUTHORITY=Ok("/run/user/1000/.mutter-Xwaylandauth.L5PBO3") +[2026-04-25 12:19:20.393224 -04:00] INFO [libs/scrap/src/common/hwcodec.rs:523] set hwcodec config +[2026-04-25 12:19:20.393328 -04:00] DEBUG [libs/scrap/src/common/hwcodec.rs:524] HwCodecConfig { signature: 0, ram_encode: [], ram_decode: [CodecInfo { name: "h264", mc_name: None, format: H264, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }, CodecInfo { name: "hevc", mc_name: None, format: H265, priority: 3, hwdevice: AV_HWDEVICE_TYPE_NONE }] } +[2026-04-25 12:19:20.879101 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:768] connecting to 49.12.46.241:443 +[2026-04-25 12:19:21.148718 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:771] connected to 49.12.46.241:443 +[2026-04-25 12:19:21.763834 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/pool.rs:395] pooling idle connection for ("https", api.rustdesk.com) +[2026-04-25 12:19:33.767976 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:768] connecting to 104.20.19.94:443 +[2026-04-25 12:19:33.833518 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:771] connected to 104.20.19.94:443 +[2026-04-25 12:19:40.566305 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:768] connecting to 104.20.19.94:443 +[2026-04-25 12:19:40.626372 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:771] connected to 104.20.19.94:443 +[2026-04-25 12:19:47.672763 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:768] connecting to 172.66.167.80:443 +[2026-04-25 12:19:47.821856 -04:00] DEBUG [/root/.cargo/registry/src/github.com-1ecc6299db9ec823/hyper-util-0.1.17/src/client/legacy/connect/http.rs:771] connected to 172.66.167.80:443 +[2026-04-25 12:19:56.526538 -04:00] ERROR [libs/hbb_common/src/platform/mod.rs:50] Got signal 11 and exit. stack: +gtk_window_is_maximized +_ZL40window_manager_plugin_handle_method_callP20_WindowManagerPluginP13_FlMethodCall +g_main_context_iteration +g_application_run +main +__libc_start_call_main +__libc_start_main_alias_1 +_start +[2026-04-25 12:19:56.549685 -04:00] INFO [libs/libxdo-sys-stub/src/lib.rs:114] libxdo-sys Loaded libxdo.so.3 +[2026-04-25 12:19:56.552500 -04:00] INFO [libs/enigo/src/linux/xdo.rs:54] xdo context created successfully diff --git a/tests/test_data/windows/appcrashes/Report.wer b/tests/test_data/windows/appcrashes/Report.wer new file mode 100644 index 00000000..2668d1b1 Binary files /dev/null and b/tests/test_data/windows/appcrashes/Report.wer differ diff --git a/tests/windows/appcrash/main.ts b/tests/windows/appcrash/main.ts new file mode 100644 index 00000000..d0f9c84c --- /dev/null +++ b/tests/windows/appcrash/main.ts @@ -0,0 +1,23 @@ +import { extractAppCrash } from "../../../mod"; +import { WindowsError } from "../../../src/windows/errors"; +import { testExtractAppCrash } from "../../test"; + +function main() { + console.log('Running Windows AppCrash tests....'); + console.log(' Starting live test....'); + + const results = extractAppCrash(); + if (results instanceof WindowsError) { + throw results; + } + + console.log(' Live test passed! 🥳\n'); + + console.log(' Starting Windows AppCrash test....'); + testExtractAppCrash(); + console.log(' Windows AppCrash test passed! 🥳'); + + console.log('All Windows AppCrash tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/windows/logons/main.ts b/tests/windows/logons/main.ts index f9c4213a..b8665299 100644 --- a/tests/windows/logons/main.ts +++ b/tests/windows/logons/main.ts @@ -1,21 +1,21 @@ -import { WindowsError } from "../../../src/windows/errors"; -import { logonsWindows } from "../../../src/windows/eventlogs/logons"; -import { testLogonsWindows } from "../../test"; - -function main() { - console.log('Running Windows Logons tests....'); - console.log(' Starting live test....'); - const results = logonsWindows("C:\\Windows\\System32\\winevt\\Logs\\Security.evtx"); - if (results instanceof WindowsError) { - throw results; - } - console.log(' Live test passed! 🥳\n'); - - console.log(' Starting Windows Logons test....'); - testLogonsWindows(); - - console.log(' Windows Logons test passed! 🥳'); - console.log('All Windows Logons tests passed! 🥳💃🕺'); -} - -main(); +import { WindowsError } from "../../../src/windows/errors"; +import { logonsWindows } from "../../../src/windows/eventlogs/logons"; +import { testLogonsWindows } from "../../test"; + +function main() { + console.log('Running Windows Logons tests....'); + console.log(' Starting live test....'); + const results = logonsWindows("C:\\Windows\\System32\\winevt\\Logs\\Security.evtx"); + if (results instanceof WindowsError) { + throw results; + } + console.log(' Live test passed! 🥳\n'); + + console.log(' Starting Windows Logons test....'); + testLogonsWindows(); + + console.log(' Windows Logons test passed! 🥳'); + console.log('All Windows Logons tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/windows/mru/main.ts b/tests/windows/mru/main.ts index 089828ef..612a6383 100644 --- a/tests/windows/mru/main.ts +++ b/tests/windows/mru/main.ts @@ -1,36 +1,35 @@ -import { FileError } from "../../../src/filesystem/errors"; -import { glob } from "../../../src/filesystem/files"; -import { WindowsError } from "../../../src/windows/errors"; -import { parseMru } from "../../../src/windows/registry/recently_used"; -import { testParseMru } from "../../test"; - -function main() { - console.log('Running Windows MRU tests....'); - console.log(' Starting live test....'); - - const paths = glob("C:\\Users\\*\\NTUSER.*"); - if (paths instanceof FileError) { - throw paths; - } - for (const entry of paths) { - if (!entry.is_file || entry.full_path.includes("Default") || !entry.filename.toLocaleLowerCase().endsWith("dat") || entry.full_path.includes("WsiAccount")) { - continue; - } - console.log(entry.full_path); - const results = parseMru(entry.full_path); - if (results instanceof WindowsError) { - throw results; - } - if (results.length === 0) { - throw "no entries?"; - } - } - console.log(' Live test passed! 🥳\n'); - - console.log(' Starting Windows MRU test....'); - testParseMru(); - - console.log('All Windows MRU tests passed! 🥳💃🕺'); -} - -main(); +import { FileError } from "../../../src/filesystem/errors"; +import { glob } from "../../../src/filesystem/files"; +import { WindowsError } from "../../../src/windows/errors"; +import { parseMru } from "../../../src/windows/registry/recently_used"; +import { testParseMru } from "../../test"; + +function main() { + console.log('Running Windows MRU tests....'); + console.log(' Starting live test....'); + + const paths = glob("C:\\Users\\*\\NTUSER.*"); + if (paths instanceof FileError) { + throw paths; + } + for (const entry of paths) { + if (!entry.is_file || entry.full_path.includes("Default") || !entry.filename.toLocaleLowerCase().endsWith("dat") || entry.full_path.includes("WsiAccount")) { + continue; + } + const results = parseMru(entry.full_path); + if (results instanceof WindowsError) { + throw results; + } + if (results.length === 0) { + throw "no entries?"; + } + } + console.log(' Live test passed! 🥳\n'); + + console.log(' Starting Windows MRU test....'); + testParseMru(); + + console.log('All Windows MRU tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/windows/msi/main.ts b/tests/windows/msi/main.ts new file mode 100644 index 00000000..1596b27f --- /dev/null +++ b/tests/windows/msi/main.ts @@ -0,0 +1,21 @@ +import { msiInstalled } from "../../../mod"; +import { WindowsError } from "../../../src/windows/errors"; +import { testMsiInstalled } from "../../test"; + +function main() { + console.log('Running Windows MSI Installed EventLogs tests....'); + console.log(' Starting live test....'); + const results = msiInstalled(); + if (results instanceof WindowsError) { + throw results; + } + console.log(' Live test passed! 🥳\n'); + + console.log(' Starting Windows MSI Installed test....'); + testMsiInstalled(); + + console.log(' Windows Windows MSI Installed EventLogs test passed! 🥳'); + console.log('All Windows MSI Installed EventLogs tests passed! 🥳💃🕺'); +} + +main(); \ No newline at end of file diff --git a/tests/windows/ual/main.ts b/tests/windows/ual/main.ts new file mode 100644 index 00000000..d4eb6b46 --- /dev/null +++ b/tests/windows/ual/main.ts @@ -0,0 +1,50 @@ +import { glob, UserAccessLogging } from "../../../mod"; +import { FileError } from "../../../src/filesystem/errors"; +import { WindowsError } from "../../../src/windows/errors"; + +function main() { + console.log('Running Windows UAL tests....'); + console.log(' Starting live test....'); + + const glob_path = "C:\\System32\\LogFiles\\Sum\\*.mdb"; + const paths = glob(glob_path); + if (paths instanceof FileError) { + throw paths; + } + let role = undefined; + for (const path of paths) { + if (path.filename !== "SystemIdentity.mdb") { + continue; + } + + const ual = new UserAccessLogging(path.full_path); + role = ual; + } + + if (role === undefined) { + return; + } + + for (const path of paths) { + if (path.filename === "SystemIdentity.mdb") { + continue; + } + console.log(`Parsing: ${path.full_path}`); + + const clients = new UserAccessLogging(path.full_path); + + const data = clients.getUserAccessLog(clients.pages, role); + if (data instanceof WindowsError) { + throw data; + } + + if(data.length === 0) { + throw `Got empty UAL?`; + } + } + console.log(' Live test passed! 🥳\n'); + + console.log('All Windows UAL tests passed! 🥳💃🕺'); +} + +main(); diff --git a/tests/windows/usbs/main.ts b/tests/windows/usbs/main.ts index 3ed63bb1..c919035c 100644 --- a/tests/windows/usbs/main.ts +++ b/tests/windows/usbs/main.ts @@ -1,16 +1,16 @@ -import { listUsbDevices } from "../../../mod"; -import { WindowsError } from "../../../src/windows/errors"; - -function main() { - console.log('Running Windows USB devices tests....'); - console.log(' Starting live test....'); - const results = listUsbDevices(); - - if (results instanceof WindowsError) { - throw results; - } - console.log(' Live test passed! 🥳\n'); - console.log('All Windows USB devices tests passed! 🥳💃🕺'); -} - -main(); +import { listUsbDevices } from "../../../mod"; +import { WindowsError } from "../../../src/windows/errors"; + +function main() { + console.log('Running Windows USB devices tests....'); + console.log(' Starting live test....'); + const results = listUsbDevices(); + + if (results instanceof WindowsError) { + throw results; + } + console.log(' Live test passed! 🥳\n'); + console.log('All Windows USB devices tests passed! 🥳💃🕺'); +} + +main(); diff --git a/types/applications/anydesk.ts b/types/applications/anydesk.ts index 324c6370..80704df5 100644 --- a/types/applications/anydesk.ts +++ b/types/applications/anydesk.ts @@ -1,42 +1,43 @@ -export interface AnyDeskUsers { - user_path: string; - version: string; - account: string; - id: string; -} - -/** - * Object representing a Trace log entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface TraceEntry { - message: string; - datetime: string; - timestamp_desc: "Trace Entry"; - artifact: "AnyDesk Trace Log"; - data_type: "applications:anydesk:trace:entry"; - path: string; - level: string; - entry_timestamp: string; - component: string; - code_function: string; - pid: number; - ppid: number; - subfunction: string; - log_message: string; - account: string; - version: string; - id: string; -} - -export interface Config { - message: string; - datetime: string; - timestamp_desc: "Config Created"; - artifact: "AnyDesk Config"; - data_type: "applications:anydesk:config:entry"; - [ key: string ]: string; - account: string; - version: string; - id: string; +export interface AnyDeskUsers { + user_path: string; + version: string; + account: string; + id: string; +} + +/** + * Object representing a Trace log entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface TraceEntry { + message: string; + datetime: string; + timestamp_desc: "Trace Entry"; + artifact: "AnyDesk Trace Log"; + data_type: "applications:anydesk:trace:entry"; + evidence: string; + level: string; + entry_timestamp: string; + component: string; + code_function: string; + pid: number; + ppid: number; + subfunction: string; + log_message: string; + account: string; + version: string; + id: string; +} + +export interface Config { + message: string; + datetime: string; + timestamp_desc: "Config Created"; + artifact: "AnyDesk Config"; + data_type: "applications:anydesk:config:entry"; + [ key: string ]: string; + account: string; + version: string; + id: string; + evidence: string; } \ No newline at end of file diff --git a/types/applications/chromium.ts b/types/applications/chromium.ts index 3760a9eb..fd8c8773 100644 --- a/types/applications/chromium.ts +++ b/types/applications/chromium.ts @@ -1,526 +1,560 @@ -import { Url } from "../http/unfold"; -import { LevelDbEntry } from "./level"; - -/** - * Chromium history is stored in a SQLITE file. - * `artemis` uses the `sqlite` crate to read the SQLITE file. It can even read the file if Chromium is running. - * - * References: - * - https://en.wikiversity.org/wiki/Chromium_browsing_history_database - * - https://gist.github.com/dropmeaword/9372cbeb29e8390521c2 - */ - -/** - * An interface representing the Chromium SQLITE tables: `urls` and `visits` - */ -export interface ChromiumHistory { - /**Row ID value */ - id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**Page visit count */ - visit_count: number; - /**Typed count value */ - typed_count: number; - /**Last visit time*/ - last_visit_time: string; - /**Hidden value */ - hidden: number; - /**Visits ID value */ - visits_id: number; - /**From visit value */ - from_visit: number; - /**Transition value */ - transition: number; - /**Segment ID value */ - segment_id: number; - /**Visit duration value */ - visit_duration: number; - /**Opener visit value */ - opener_visit: number; - unfold: Url | undefined; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: string; - browser: BrowserType; -} - -/** - * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` - */ -export interface ChromiumDownloads { - /**Row ID */ - id: number; - /**GUID for download */ - guid: string; - /**Path to download */ - current_path: string; - /**Target path to download */ - target_path: string; - /**Download start time */ - start_time: string; - /**Bytes downloaded */ - received_bytes: number; - /**Total bytes downloaded */ - total_bytes: number; - /**State value */ - state: number; - /**Danger type value */ - danger_type: number; - /**Interrupt reason value */ - interrupt_reason: number; - /**Raw byte hash value */ - hash: number[]; - /**Download end time */ - end_time: string; - /**Opened value */ - opened: number; - /**Last access time */ - last_access_time: string; - /**Transient value */ - transient: number; - /**Referer URL */ - referrer: string; - /**Download source URL */ - site_url: string; - /**Tab URL */ - tab_url: string; - /**Tab referrer URL */ - tab_referrer_url: string; - /**HTTP method used */ - http_method: string; - /**By ext ID value */ - by_ext_id: string; - /**By ext name value */ - by_ext_name: string; - /**Etag value */ - etag: string; - /**Last modified time */ - last_modified: string; - /**MIME type value */ - mime_type: string; - /**Original mime type value */ - original_mime_type: string; - /**Downloads URL chain ID value */ - downloads_url_chain_id: number; - /**Chain index value */ - chain_index: number; - /**URL for download */ - url: string; - /**Path to the HISTORY sqlite file */ - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumCookies { - creation: string; - host_key: string; - top_frame_site_key: string; - name: string; - value: string; - /**This value is currently Base64 encoded */ - encrypted_value: string; - path: string; - expires: string; - is_secure: boolean; - is_httponly: boolean; - last_access: string; - has_expires: boolean; - is_persistent: boolean; - priority: number; - samesite: number; - source_scheme: number; - source_port: number; - is_same_party: number; - last_update: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumFavicons { - last_update: string; - url: string; - page_url: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Favicon Updated"; - artifact: "Website Favicon"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumShortcuts { - last_update: string; - url: string; - text: string; - contents: string; - fill_into_edit: string; - shortcut_id: string; - keyword: string; - shortcut_type: ShortcutType; - db_path: string; - description: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Shortcut Last Access"; - artifact: "Website Shortcut"; - data_type: string; - browser: BrowserType; -} - -/// From: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/omnibox/browser/autocomplete_match_type.h#30 -export enum ShortcutType { - Typed = "URL Typed", - History = "URL History", - HistoryTitle = "History Title", - HistoryBody = "History Body", - HistoryKeyword = "History Keyword", - Suggest = "Suggested URL", - SearchUrl = "URL Searched", - SearchHistory = "Search History", - SearchSuggest = "Search Suggestion", - SearchTail = "Search Suggestion Tail", - SearchPersonalized = "Search Personalization", - SearchProfile = "Search Google+", // lol - SearchEngine = "Search with other Engine", - ExtensionApp = "Extension Application", - UserContact = "User Contact", - Bookmark = "Browser bookmark", - SuggestPersonalized = "Personlized Suggestion", - Calculator = "Calculator", - Clipboard = "Clipboard URL", - Voice = "Voice Suggestion", - Physical = "Physical Web", - PhysicalOverflow = "Multiple physical web", - Tab = "Tab Search", - Document = "Document Suggestion", - Pedal = "Pedal Match", - ClipboardText = "Clipboard text", - ClipboardImage = "Clipboard image", - TitleSuggest = "Suggested query title", - TitleNavSuggest = "Suggested navigation title", - OpenTab = "Opened Tab", - HistoryCluster = "History cluster suggestion", - Null = "Null suggestion", - Starter = "URL suggestion for starter keyword", - MostVisited = "Most visited site", - Repeatable = "Organic Repeatable Query", - HistoryEmbed = "Past page embedded in query", - Enterprise = "Search Enterprise policy", - TabGroup = "Tab group", - HistoryEmbedAnswers = "History embedded answers", - SearchSuggestEntity = "Search suggestion for entity", - Unkonwn = "Unknown", -} - -export interface ChromiumAutofill { - name?: string; - value?: string; - value_lower?: string; - date_created: string; - date_last_used: string; - /**Default is 1 */ - count: number; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Autofill Created"; - artifact: "Website Autofill"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumBookmarks { - date_added: string; - date_last_used: string; - guid: string; - id: number; - name: string; - type: string; - url: string; - bookmark_type: BookmarkType; - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Bookmark Added"; - artifact: "Browser Bookmark"; - data_type: string; - browser: BrowserType; -} - -export enum BookmarkType { - Bar = "Bookmark Bar", - Sync = "Synced", - Other = "Other", - Unknown = "Unknown", -} - -export interface ChromiumLogins { - origin_url: string; - action_url?: string; - username_element?: string; - username_value?: string; - password_element?: string; - password_value?: string; - submit_element?: string; - signon_realm: string; - date_created: string; - blacklisted_by_user: number; - scheme: number; - password_type?: number; - times_used?: number; - form_data?: string; - display_name?: string; - icon_url?: string; - federation_url?: string; - skip_zero_click?: number; - generation_upload_status?: number; - possible_username_pairs?: string; - id: number; - date_last_used: string; - moving_blocked_for?: string; - date_password_modified: string; - sender_email?: string; - sender_name?: string; - date_received?: string; - sharing_notification_display: number; - keychain_identifier?: string; - sender_profile_image_url?: string; - db_path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "Last Login"; - artifact: "Website Login"; - data_type: string; - browser: BrowserType; -} - -/** - * Detect Incidental Party State (DIPS) collects metrics on websites - */ -export interface ChromiumDips { - site: string; - first_site_storage?: string | null; - last_site_storage?: string | null; - first_user_interaction?: string | null; - last_user_interaction?: string | null; - first_stateful_bounce?: string | null; - last_stateful_bounce?: string | null; - first_bounce?: string | null; - last_bounce?: string | null; - first_web_authn_assertion: string | null; - last_web_authn_assertion: string | null; - /**Path to DIPS database */ - path: string; - /**Browser version */ - version: string; - message: string; - datetime: string; - timestamp_desc: "First Interaction"; - artifact: "Browser DIPS"; - data_type: string; - browser: BrowserType; -} - -export interface ChromiumProfiles { - full_path: string; - version: string; - browser: BrowserType; -} - -export enum BrowserType { - CHROME = "Google Chrome", - EDGE = "Microsoft Edge", - CHROMIUM = "Google Chromium", - COMET = "Perplexity Comet", - BRAVE = "Brave", -} - -export enum ChromiumCookieType { - Unknown = "Unknown", - Http = "HTTP", - Script = "Script", - Other = "Other", -} - -/** - * Object representing a Local Storage LevelDb entry. - * This object is Timesketch compatible. It does **not** need to be timelined - */ -export interface ChromiumLocalStorage extends LevelDbEntry { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; - artifact: "Level Database"; - data_type: "applications:leveldb:entry"; -} - -export interface ChromiumSession { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Active"; - artifact: "Browser Session"; - data_type: string; - session_id: string; - last_active: string; - url: string; - title: string; - session_type: SessionType; - path: string; -} - -export enum SessionType { - Session = "Session", - Tab = "Tab", -} - -/** - * Browsers developers can add more - * Default list is at: https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_service_commands.cc;l=28;drc=38321ee39cd73ac2d9d4400c56b90613dee5fe29 - */ -export enum SessionCommand { - WindowType = "WindowType", - UpdateTabNavigation = "UpdateTabNavigation", - TabWindow = "TabWindow", - WindowBounds = "WindowBounds", - TabIndexInWindow = "TabIndexInWindow", - TabNavigationPathPrunedFromBack = "TabNavigationPathPrunedFromBack", - SelectedNavigationIndex = "SelectedNavigationIndex", - SelectedTabInIndex = "SelectedTabInIndex", - WindowBounds2 = "WindowBounds2", - TabNavigationPathPrunedFromFront = "TabNavigationPathPrunedFromFront", - PinnedState = "PinnedState", - ExtensionAppID = "ExtensionAppID", - WindowBounds3 = "WindowBounds3", - WindowAppName = "WindowAppName", - TabClosed = "TabClosed", - WindowClosed = "WindowClosed", - TabUserAgentOverride = "TabUserAgentOverride", - SessionStorageAssociated = "SessionStorageAssociated", - ActiveWindow = "ActiveWindow", - LastActiveTime = "LastActiveTime", - WindowWorkspace = "WindowWorkspace", - WindowWorkspace2 = "WindowWorkspace2", - TabNavigationPathPruned = "TabNavigationPathPruned", - TabGroup = "TabGroup", - TabGroupMetadata = "TabGroupMetadata", - TabGroupMetadata2 = "TabGroupMetadata2", - TabGuid = "TabGuid", - TabUserAgentOverride2 = "TabUserAgentOverride2", - TabData = "TabData", - WindowUserTitle = "WindowUserTitle", - WindowVisibleOnAllWorkspaces = "WindowVisibleOnAllWorkspaces", - AddTabExtraData = "AddTabExtraData", - AddWindowExtraData = "AddWindowExtraData", - PlatformSessionId = "PlatformSessionId", - SplitTab = "SplitTab", - SplitTabData = "SplitTabData", - Unknown = "Unknown", - CommandStorageBackend = "CommandStorageBackend", - EdgeCommand = "EdgeCommand", - EdgeCommand2 = "EdgeCommand2", - EdgeCommand3 = "EdgeCommand3", - EdgeCommand4 = "EdgeCommand4", -} - -/// https://github.com/cclgroupltd/ccl_chromium_reader/blob/552516720761397c4d482908b6b8b08130b313a1/ccl_chromium_reader/ccl_chromium_snss2.py#L39 -export enum SessionTabCommand { - SelectedNavigtionInTab = "SelectedNavigationInTab", - UpdateTabNavigation = "UpdateTabNavigation", - RestoredEntry = "RestoredEntry", - WindowDeprecated = "WindowDeprecated", - PinnedState = "PinnedState", - ExtensionAppID = "ExtensionAppID", - WindowAppName = "WindowAppName", - TabUserAgentOverride = "TabUserAgentOverride", - Window = "Window", - TabGroupData = "TabGroupData", - TabUserAgentOverride2 = "TabUserAgentOverride2", - WindowUserTitle = "WindowUserTitle", - CreateGroup = "CreateGroup", - AddTabExtraData = "AddTabExtraData", - Unknown = "Unknown", - CommandStorageBackend = "CommandStorageBackend", -} - -export interface Preferences { - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Last Modified"; - artifact: "User Preferences"; - data_type: string; - path: string; - exception_category: ExceptionCategory; - created_version: string; - profile_id: string; - preferences_created: string; - name: string; - url: string; - last_modified: string; -} - -export enum ExceptionCategory { - AppBanner = "App Banner", - ClientHints = "Client Hints", - CookieControls = "Cookie Controls", - HttpsEnforced = "HTTPS Enforced", - MediaEngagement = "Media Engagement", - SiteEngagement = "Site Engagement", - SslCert = "SSL Cert Desicions", - Zoom = "Zoom Level", -} - -export interface Extension { - /**Browser version */ - version: string; - message: string; - datetime: string; - browser: BrowserType; - timestamp_desc: "Extension Created"; - artifact: "Browser Extension"; - data_type: string; - name: string; - author: string; - description: string; - manifest: string; - extension_version: string; +import { Url } from "../http/unfold"; +import { LevelDbEntry } from "./level"; + +/** + * Chromium history is stored in a SQLITE file. + * `artemis` uses the `sqlite` crate to read the SQLITE file. It can even read the file if Chromium is running. + * + * References: + * - https://en.wikiversity.org/wiki/Chromium_browsing_history_database + * - https://gist.github.com/dropmeaword/9372cbeb29e8390521c2 + */ + +/** + * An interface representing the Chromium SQLITE tables: `urls` and `visits` + */ +export interface ChromiumHistory { + /**Row ID value */ + id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**Page visit count */ + visit_count: number; + /**Typed count value */ + typed_count: number; + /**Last visit time*/ + last_visit_time: string; + /**Hidden value */ + hidden: number; + /**Visits ID value */ + visits_id: number; + /**From visit value */ + from_visit: number; + /**Transition value */ + transition: number; + /**Segment ID value */ + segment_id: number; + /**Visit duration value */ + visit_duration: number; + /**Opener visit value */ + opener_visit: number; + unfold: Url | undefined; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: string; + browser: BrowserType; +} + +/** + * An interface representing the Chromium SQLITE tables: `downloads` and `downloads_url_chains` + */ +export interface ChromiumDownloads { + /**Row ID */ + id: number; + /**GUID for download */ + guid: string; + /**Path to download */ + current_path: string; + /**Target path to download */ + target_path: string; + /**Download start time */ + start_time: string; + /**Bytes downloaded */ + received_bytes: number; + /**Total bytes downloaded */ + total_bytes: number; + /**State value */ + state: number; + /**Danger type value */ + danger_type: number; + /**Interrupt reason value */ + interrupt_reason: number; + /**Raw byte hash value */ + hash: number[]; + /**Download end time */ + end_time: string; + /**Opened value */ + opened: number; + /**Last access time */ + last_access_time: string; + /**Transient value */ + transient: number; + /**Referrer URL */ + referrer: string; + /**Download source URL */ + site_url: string; + /**Tab URL */ + tab_url: string; + /**Tab referrer URL */ + tab_referrer_url: string; + /**HTTP method used */ + http_method: string; + /**By ext ID value */ + by_ext_id: string; + /**By ext name value */ + by_ext_name: string; + /**Etag value */ + etag: string; + /**Last modified time */ + last_modified: string; + /**MIME type value */ + mime_type: string; + /**Original mime type value */ + original_mime_type: string; + /**Downloads URL chain ID value */ + downloads_url_chain_id: number; + /**Chain index value */ + chain_index: number; + /**URL for download */ + url: string; + /**Path to the HISTORY sqlite file */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumCookies { + creation: string; + host_key: string; + top_frame_site_key: string; + name: string; + value: string; + /**This value is currently Base64 encoded */ + encrypted_value: string; + path: string; + expires: string; + is_secure: boolean; + is_httponly: boolean; + last_access: string; + has_expires: boolean; + is_persistent: boolean; + priority: number; + samesite: number; + source_scheme: number; + source_port: number; + is_same_party: number; + last_update: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumFavicons { + last_update: string; + url: string; + page_url: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Favicon Updated"; + artifact: "Website Favicon"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumShortcuts { + last_update: string; + url: string; + text: string; + contents: string; + fill_into_edit: string; + shortcut_id: string; + keyword: string; + shortcut_type: ShortcutType; + evidence: string; + description: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Shortcut Last Access"; + artifact: "Website Shortcut"; + data_type: string; + browser: BrowserType; +} + +/// From: https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/omnibox/browser/autocomplete_match_type.h#30 +export enum ShortcutType { + Typed = "URL Typed", + History = "URL History", + HistoryTitle = "History Title", + HistoryBody = "History Body", + HistoryKeyword = "History Keyword", + Suggest = "Suggested URL", + SearchUrl = "URL Searched", + SearchHistory = "Search History", + SearchSuggest = "Search Suggestion", + SearchTail = "Search Suggestion Tail", + SearchPersonalized = "Search Personalization", + SearchProfile = "Search Google+", // lol + SearchEngine = "Search with other Engine", + ExtensionApp = "Extension Application", + UserContact = "User Contact", + Bookmark = "Browser bookmark", + SuggestPersonalized = "Personlized Suggestion", + Calculator = "Calculator", + Clipboard = "Clipboard URL", + Voice = "Voice Suggestion", + Physical = "Physical Web", + PhysicalOverflow = "Multiple physical web", + Tab = "Tab Search", + Document = "Document Suggestion", + Pedal = "Pedal Match", + ClipboardText = "Clipboard text", + ClipboardImage = "Clipboard image", + TitleSuggest = "Suggested query title", + TitleNavSuggest = "Suggested navigation title", + OpenTab = "Opened Tab", + HistoryCluster = "History cluster suggestion", + Null = "Null suggestion", + Starter = "URL suggestion for starter keyword", + MostVisited = "Most visited site", + Repeatable = "Organic Repeatable Query", + HistoryEmbed = "Past page embedded in query", + Enterprise = "Search Enterprise policy", + TabGroup = "Tab group", + HistoryEmbedAnswers = "History embedded answers", + SearchSuggestEntity = "Search suggestion for entity", + Unknown = "Unknown", +} + +export interface ChromiumAutofill { + name?: string; + value?: string; + value_lower?: string; + date_created: string; + date_last_used: string; + /**Default is 1 */ + count: number; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Autofill Created"; + artifact: "Website Autofill"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumBookmarks { + date_added: string; + date_last_used: string; + guid: string; + id: number; + name: string; + type: string; + url: string; + bookmark_type: BookmarkType; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Bookmark Added"; + artifact: "Browser Bookmark"; + data_type: string; + browser: BrowserType; +} + +export enum BookmarkType { + Bar = "Bookmark Bar", + Sync = "Synced", + Other = "Other", + Unknown = "Unknown", +} + +export interface ChromiumLogins { + origin_url: string; + action_url?: string; + username_element?: string; + username_value?: string; + password_element?: string; + password_value?: string; + submit_element?: string; + signon_realm: string; + date_created: string; + blacklisted_by_user: number; + scheme: number; + password_type?: number; + times_used?: number; + form_data?: string; + display_name?: string; + icon_url?: string; + federation_url?: string; + skip_zero_click?: number; + generation_upload_status?: number; + possible_username_pairs?: string; + id: number; + date_last_used: string; + moving_blocked_for?: string; + date_password_modified: string; + sender_email?: string; + sender_name?: string; + date_received?: string; + sharing_notification_display: number; + keychain_identifier?: string; + sender_profile_image_url?: string; + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "Last Login"; + artifact: "Website Login"; + data_type: string; + browser: BrowserType; +} + +/** + * Detect Incidental Party State (DIPS) collects metrics on websites + */ +export interface ChromiumDips { + site: string; + first_site_storage?: string | null; + last_site_storage?: string | null; + first_user_interaction?: string | null; + last_user_interaction?: string | null; + first_stateful_bounce?: string | null; + last_stateful_bounce?: string | null; + first_bounce?: string | null; + last_bounce?: string | null; + first_web_authn_assertion: string | null; + last_web_authn_assertion: string | null; + /**Path to DIPS database */ + evidence: string; + /**Browser version */ + version: string; + message: string; + datetime: string; + timestamp_desc: "First Interaction"; + artifact: "Browser DIPS"; + data_type: string; + browser: BrowserType; +} + +export interface ChromiumProfiles { + full_path: string; + version: string; + browser: BrowserType; +} + +export enum BrowserType { + CHROME = "Google Chrome", + EDGE = "Microsoft Edge", + CHROMIUM = "Google Chromium", + COMET = "Perplexity Comet", + BRAVE = "Brave", +} + +export enum ChromiumCookieType { + Unknown = "Unknown", + Http = "HTTP", + Script = "Script", + Other = "Other", +} + +/** + * Object representing a Local Storage LevelDb entry. + * This object is Timesketch compatible. It does **not** need to be timelined + */ +export interface ChromiumLocalStorage extends LevelDbEntry { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Local Storage Entry Write" | "Local Storage Write Ahead Log"; + artifact: "Level Database"; + data_type: "applications:leveldb:entry"; +} + +export interface ChromiumSession { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Active"; + artifact: "Browser Session"; + data_type: string; + session_id: string; + last_active: string; + url: string; + title: string; + session_type: SessionType; + evidence: string; +} + +export enum SessionType { + Session = "Session", + Tab = "Tab", +} + +/** + * Browsers developers can add more + * Default list is at: https://source.chromium.org/chromium/chromium/src/+/main:components/sessions/core/session_service_commands.cc;l=28;drc=38321ee39cd73ac2d9d4400c56b90613dee5fe29 + */ +export enum SessionCommand { + WindowType = "WindowType", + UpdateTabNavigation = "UpdateTabNavigation", + TabWindow = "TabWindow", + WindowBounds = "WindowBounds", + TabIndexInWindow = "TabIndexInWindow", + TabNavigationPathPrunedFromBack = "TabNavigationPathPrunedFromBack", + SelectedNavigationIndex = "SelectedNavigationIndex", + SelectedTabInIndex = "SelectedTabInIndex", + WindowBounds2 = "WindowBounds2", + TabNavigationPathPrunedFromFront = "TabNavigationPathPrunedFromFront", + PinnedState = "PinnedState", + ExtensionAppID = "ExtensionAppID", + WindowBounds3 = "WindowBounds3", + WindowAppName = "WindowAppName", + TabClosed = "TabClosed", + WindowClosed = "WindowClosed", + TabUserAgentOverride = "TabUserAgentOverride", + SessionStorageAssociated = "SessionStorageAssociated", + ActiveWindow = "ActiveWindow", + LastActiveTime = "LastActiveTime", + WindowWorkspace = "WindowWorkspace", + WindowWorkspace2 = "WindowWorkspace2", + TabNavigationPathPruned = "TabNavigationPathPruned", + TabGroup = "TabGroup", + TabGroupMetadata = "TabGroupMetadata", + TabGroupMetadata2 = "TabGroupMetadata2", + TabGuid = "TabGuid", + TabUserAgentOverride2 = "TabUserAgentOverride2", + TabData = "TabData", + WindowUserTitle = "WindowUserTitle", + WindowVisibleOnAllWorkspaces = "WindowVisibleOnAllWorkspaces", + AddTabExtraData = "AddTabExtraData", + AddWindowExtraData = "AddWindowExtraData", + PlatformSessionId = "PlatformSessionId", + SplitTab = "SplitTab", + SplitTabData = "SplitTabData", + Unknown = "Unknown", + CommandStorageBackend = "CommandStorageBackend", + EdgeCommand = "EdgeCommand", + EdgeCommand2 = "EdgeCommand2", + EdgeCommand3 = "EdgeCommand3", + EdgeCommand4 = "EdgeCommand4", +} + +/// https://github.com/cclgroupltd/ccl_chromium_reader/blob/552516720761397c4d482908b6b8b08130b313a1/ccl_chromium_reader/ccl_chromium_snss2.py#L39 +export enum SessionTabCommand { + SelectedNavigtionInTab = "SelectedNavigationInTab", + UpdateTabNavigation = "UpdateTabNavigation", + RestoredEntry = "RestoredEntry", + WindowDeprecated = "WindowDeprecated", + PinnedState = "PinnedState", + ExtensionAppID = "ExtensionAppID", + WindowAppName = "WindowAppName", + TabUserAgentOverride = "TabUserAgentOverride", + Window = "Window", + TabGroupData = "TabGroupData", + TabUserAgentOverride2 = "TabUserAgentOverride2", + WindowUserTitle = "WindowUserTitle", + CreateGroup = "CreateGroup", + AddTabExtraData = "AddTabExtraData", + Unknown = "Unknown", + CommandStorageBackend = "CommandStorageBackend", +} + +export interface Preferences { + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Last Modified"; + artifact: "User Preferences"; + data_type: string; + evidence: string; + exception_category: ExceptionCategory; + created_version: string; + profile_id: string; + preferences_created: string; + name: string; + url: string; + last_modified: string; +} + +export enum ExceptionCategory { + AppBanner = "App Banner", + ClientHints = "Client Hints", + CookieControls = "Cookie Controls", + HttpsEnforced = "HTTPS Enforced", + MediaEngagement = "Media Engagement", + SiteEngagement = "Site Engagement", + SslCert = "SSL Cert Desicions", + Zoom = "Zoom Level", +} + +export interface Extension { + /**Browser version */ + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Extension Created"; + artifact: "Browser Extension"; + data_type: string; + name: string; + author: string; + description: string; + extension_version: string; + evidence: string; +} + +export interface ChromiumCache { + /**Browser version */ + version: string; + message: string; + datetime: string; + browser: BrowserType; + timestamp_desc: "Browser Cache Created"; + artifact: "Browser Cache"; + data_type: string; + hash: number; + cache_state: CacheState; + created: string; + url: string; + request: string; + response: string; + response2: string; + response_headers: string[]; + cache_key: string; + evidence: string; +} + +export enum CacheState { + Normal = "Normal", + Evicted = "Evicted", + Doomed = "Doomed", + Unknown = "Unknown", +} + +export enum CacheFlag { + Parent = "Parent", + Child = "Child", + Unknown = "Unknown", } \ No newline at end of file diff --git a/types/applications/firefox.ts b/types/applications/firefox.ts index 7ac52bb8..8ca718a2 100644 --- a/types/applications/firefox.ts +++ b/types/applications/firefox.ts @@ -1,221 +1,311 @@ -import { Url } from "../http/unfold"; - -/** - * Firefox history is stored in a SQLITE file. - * `artemis` uses the `sqlite` crate to read the SQLITE file. It can even read the file if Firefox is running. - * - * References: - * - https://kb.mozillazine.org/Places.sqlite - */ - -/** - * An interface representing the Firefox SQLITE tables: `moz_places` and `moz_origins` - */ -export interface FirefoxHistory { - /**SQLITE row id */ - moz_places_id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**URL in reverse */ - rev_host: string; - /**Page visit count */ - visit_count: number; - /**Hidden value */ - hidden: number; - /**Typed value */ - typed: number; - /**Frequency value */ - frequency: number; - /**Last visit time */ - last_visit_date: string; - /**GUID for entry */ - guid: string; - /**Foreign count value */ - foreign_count: number; - /**Hash of URL */ - url_hash: number; - /**Page description */ - description: string; - /**Preview image URL value */ - preview_image_url: string; - /**Prefix value (ex: https://) */ - prefix: string; - /** Host value */ - host: string; - unfold: Url | undefined; - db_path: string; - version: string; - message: string; - datetime: string; - timestamp_desc: "URL Visited"; - artifact: "URL History"; - data_type: "application:firefox:history:entry"; -} - -/** - * An interface representing the Firefox SQLITE tables: `moz_places`, `moz_origins`, `moz_annos`, `moz_anno_attributes` - */ -export interface FirefoxDownloads { - /**ID for SQLITE row */ - id: number; - /**ID to history entry */ - place_id: number; - /**ID to anno_attribute entry */ - anno_attribute_id: number; - /**Content value */ - content: string; - /**Flags value */ - flags: number; - /**Expiration value */ - expiration: number; - /**Download type value */ - download_type: number; - /**Date added */ - date_added: string; - /**Last modified */ - last_modified: string; - /**Downloaded file name */ - name: string; - /**SQLITE row id */ - moz_places_id: number; - /**Page URL */ - url: string; - /**Page title */ - title: string; - /**URL in reverse */ - rev_host: string; - /**Page visit count */ - visit_count: number; - /**Hidden value */ - hidden: number; - /**Typed value */ - typed: number; - /**Frequency value */ - frequency: number; - /**Last visit time */ - last_visit_date: string; - /**GUID for entry */ - guid: string; - /**Foreign count value */ - foreign_count: number; - /**Hash of URL */ - url_hash: number; - /**Page description */ - description: string; - /**Preview image URL value */ - preview_image_url: string; - db_path: string; - version: string; - message: string; - datetime: string; - timestamp_desc: "File Download Start"; - artifact: "File Download"; - data_type: "application:firefox:downloads:entry"; -} - -export interface FirefoxCookies { - id: number; - origin_attributes: string; - name: string; - value: string; - host: string; - path: string; - expiry: string; - last_accessed: string; - creation_time: string; - is_secure: boolean; - is_http_only: boolean; - in_browser_element: boolean; - same_site: boolean; - scheme_map: number; - db_path: string; - version: string; - message: string; - datetime: string; - timestamp_desc: "Cookie Expires"; - artifact: "Website Cookie"; - data_type: "application:firefox:cookies:entry"; -} - -export interface FirefoxFavicons { - icon_url: string; - expires: string; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "Favicon Expires"; - artifact: "URL Favicon"; - data_type: "application:firefox:favicons:entry"; - version: string; -} - -export interface FirefoxProfiles { - full_path: string; - version: string; -} - -export interface FirefoxStorage { - repository: Respository; - suffix?: string; - group: string; - origin: string; - client_usages: string; - last_access: string; - accessed: number; - persisted: number; - db_path: string; - message: string; - datetime: string; - version: string; - timestamp_desc: "Website Storage Last Accessed"; - artifact: "Website Storage"; - data_type: "application:firefox:storage:entry"; -} - -export enum Respository { - Persistent = "Persistent", - Default = "Default", - Private = "Private", - Unknown = "Unknown", - Temporary = "Temporary", -} - -export interface FirefoxAddons { - installed: string; - updated: string; - active: boolean; - visible: boolean; - author: string; - addon_version: string; - path: string; - db_path: string; - message: string; - datetime: string; - name: string; - description: string; - version: string; - creator: string; - timestamp_desc: "Extension Installed"; - artifact: "Browser Extension"; - data_type: "application:firefox:extension:entry"; -} - -export interface FirefoxFormHistory { - timestamp_desc: "Last Searched"; - artifact: "Form History"; - data_type: "application:firefox:formhistory:entry"; - datetime: string; - message: string; - version: string; - path: string; - db_path: string; - search_term: string; - last_used: string; - first_used: string; - fieldname: string; - guid: string; - times_used: number; - source: string; +import { Url } from "../http/unfold"; + +/** + * Firefox history is stored in a SQLITE file. + * `artemis` uses the `sqlite` crate to read the SQLITE file. It can even read the file if Firefox is running. + * + * References: + * - https://kb.mozillazine.org/Places.sqlite + */ + +/** + * An interface representing the Firefox SQLITE tables: `moz_places` and `moz_origins` + */ +export interface FirefoxHistory { + /**SQLITE row id */ + moz_places_id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**URL in reverse */ + rev_host: string; + /**Page visit count */ + visit_count: number; + /**Hidden value */ + hidden: number; + /**Typed value */ + typed: number; + /**Frequency value */ + frequency: number; + /**Last visit time */ + last_visit_date: string; + /**GUID for entry */ + guid: string; + /**Foreign count value */ + foreign_count: number; + /**Hash of URL */ + url_hash: number; + /**Page description */ + description: string; + /**Preview image URL value */ + preview_image_url: string; + /**Prefix value (ex: https://) */ + prefix: string; + /** Host value */ + host: string; + unfold: Url | undefined; + evidence: string; + version: string; + message: string; + datetime: string; + timestamp_desc: "URL Visited"; + artifact: "URL History"; + data_type: "application:firefox:history:entry"; +} + +/** + * An interface representing the Firefox SQLITE tables: `moz_places`, `moz_origins`, `moz_annos`, `moz_anno_attributes` + */ +export interface FirefoxDownloads { + /**ID for SQLITE row */ + id: number; + /**ID to history entry */ + place_id: number; + /**ID to anno_attribute entry */ + anno_attribute_id: number; + /**Content value */ + content: string; + /**Flags value */ + flags: number; + /**Expiration value */ + expiration: number; + /**Download type value */ + download_type: number; + /**Date added */ + date_added: string; + /**Last modified */ + last_modified: string; + /**Downloaded file name */ + name: string; + /**SQLITE row id */ + moz_places_id: number; + /**Page URL */ + url: string; + /**Page title */ + title: string; + /**URL in reverse */ + rev_host: string; + /**Page visit count */ + visit_count: number; + /**Hidden value */ + hidden: number; + /**Typed value */ + typed: number; + /**Frequency value */ + frequency: number; + /**Last visit time */ + last_visit_date: string; + /**GUID for entry */ + guid: string; + /**Foreign count value */ + foreign_count: number; + /**Hash of URL */ + url_hash: number; + /**Page description */ + description: string; + /**Preview image URL value */ + preview_image_url: string; + evidence: string; + version: string; + message: string; + datetime: string; + timestamp_desc: "File Download Start"; + artifact: "File Download"; + data_type: "application:firefox:downloads:entry"; +} + +export interface FirefoxCookies { + id: number; + origin_attributes: string; + name: string; + value: string; + host: string; + path: string; + expiry: string; + last_accessed: string; + creation_time: string; + is_secure: boolean; + is_http_only: boolean; + in_browser_element: boolean; + same_site: boolean; + scheme_map: number; + evidence: string; + version: string; + message: string; + datetime: string; + timestamp_desc: "Cookie Expires"; + artifact: "Website Cookie"; + data_type: "application:firefox:cookies:entry"; +} + +export interface FirefoxFavicons { + icon_url: string; + expires: string; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "Favicon Expires"; + artifact: "URL Favicon"; + data_type: "application:firefox:favicons:entry"; + version: string; +} + +export interface FirefoxProfiles { + full_path: string; + version: string; +} + +export interface FirefoxStorage { + repository: Respository; + suffix?: string; + group: string; + origin: string; + client_usages: string; + last_access: string; + accessed: number; + persisted: number; + evidence: string; + message: string; + datetime: string; + version: string; + timestamp_desc: "Website Storage Last Accessed"; + artifact: "Website Storage"; + data_type: "application:firefox:storage:entry"; +} + +export enum Respository { + Persistent = "Persistent", + Default = "Default", + Private = "Private", + Unknown = "Unknown", + Temporary = "Temporary", +} + +export interface FirefoxAddons { + installed: string; + updated: string; + active: boolean; + visible: boolean; + author: string; + addon_version: string; + path: string; + evidence: string; + message: string; + datetime: string; + name: string; + description: string; + version: string; + creator: string; + timestamp_desc: "Extension Installed"; + artifact: "Browser Extension"; + data_type: "application:firefox:extension:entry"; +} + +export interface FirefoxFormHistory { + timestamp_desc: "Last Searched"; + artifact: "Form History"; + data_type: "application:firefox:formhistory:entry"; + datetime: string; + message: string; + version: string; + path: string; + evidence: string; + search_term: string; + last_used: string; + first_used: string; + fieldname: string; + guid: string; + times_used: number; + source: string; +} + +export interface FirefoxBookmark { + timestamp_desc: "Bookmark Created"; + artifact: "Browser Bookmark"; + data_type: "application:firefox:bookmark:entry"; + datetime: string; + message: string; + version: string; + path: string; + evidence: string; + added: string; + last_modified: string; + title: string; + id: number; + guid: string; + icon: string; + uri: string; +} + +export interface FirefoxBookmarkRaw { + guid: string; + title: string; + index: number; + dateAdded: bigint; + lastModified: bigint; + id: number; + typeCode: number; + type: string; + root: string; + iconUri: string | undefined; + uri: string | undefined; + children: FirefoxBookmarkRaw[] | undefined +} + +export interface FirefoxSession { + timestamp_desc: "Session Started"; + artifact: "Browser Session"; + data_type: "application:firefox:session:entry"; + datetime: string; + message: string; + version: string; + path: string; + evidence: string; + last_accessed: string; + url: string; + title: string; + id: number; + tab_closed: string; + window_closed: string; + session_start: string; +} + +/** + * There is a **ton** of info in Firefox session JSON files + * Only getting a little bit right now. + * Other data: + * - Referrer URL + * - Image + * - Lots of GUIDs + * - Lots of base64 data + */ +export interface FirefoxSessionRaw { + version: (string | number)[]; + windows: { + tabs: { + lastAccessed: bigint; + entries: { + url: string; + title: string; + ID: number; + }[] + }[], + _closedTabs: { + state: { + entries: { + url: string; + title: string; + ID: number; + }[], + lastAccessed: bigint; + }, + closedAt: bigint; + }[], + closedAt: bigint | undefined; + }[], + session: { + lastUpdate: bigint; + startTime: bigint; + } } \ No newline at end of file diff --git a/types/applications/level.ts b/types/applications/level.ts index f784d802..88113f82 100644 --- a/types/applications/level.ts +++ b/types/applications/level.ts @@ -1,96 +1,96 @@ -import { ProtoTag } from "../encoding/protobuf"; - -export interface LevelDbEntry { - sequence: number; - key_type: number; - value_type: ValueType; - value: string | number | boolean | unknown[] | Record; - shared_key: string; - origin: string; - key: string; - path: string; - state: string; -} - -export interface LevelManifest { - crc: number; - record_type: RecordType; - records: LevelRecords[]; -} - -export interface WalData { - crc: number; - record_type: RecordType; - values: WalValue[]; -} - -export interface WalValue { - log_type: LogType; - key_data: Uint8Array; - key: string; - value_data: Uint8Array | null; - value: string | number | boolean | unknown[] | Record; - value_type: ValueType; - value_type_number: number; - path: string; -} - -export enum LogType { - Deletion = "Deletion", - Value = "Value", - Unknown = "Unknown", -} - -export enum ManifestTag { - Comparator = "Comparator", - LogNumber = "LogNumber", - NextFileNumber = "NextFileNumber", - LastSequence = "LastSequence", - CompactPointer = "CompactPointer", - DeletedFile = "DeletedFile", - NewFile = "NewFile", - PrevLogNumber = "PrevLogNumber", - Unknown = "Unknown", -} - -export enum ValueType { - String = "String", - Null = "Null", - Number = "Number", - Date = "Date", - Binary = "Binary", - Array = "Array", - /**Likely represents a deleted value */ - Unknown = "Unknown", - Protobuf = "Protobuf", - Utf16 = "Utf16", -} - -export enum RecordType { - Full = "Full", - First = "First", - Middle = "Middle", - Last = "Last", - Unknown = "Unknown", -} - -export interface LevelRecords { - [ key: string ]: string | number | boolean | Uint8Array | NewFileValue | CompactPoint | DeletedFile; -} -export interface NewFileValue { - level: number; - file_number: number; - size: number; - smallest_key: Uint8Array; - largest_key: Uint8Array; -} - -export interface CompactPoint { - level: number; - key: Uint8Array; -} - -export interface DeletedFile { - level: number; - file_number: number; +import { ProtoTag } from "../encoding/protobuf"; + +export interface LevelDbEntry { + sequence: number; + key_type: number; + value_type: ValueType; + value: string | number | boolean | unknown[] | Record; + shared_key: string; + origin: string; + key: string; + evidence: string; + state: string; +} + +export interface LevelManifest { + crc: number; + record_type: RecordType; + records: LevelRecords[]; +} + +export interface WalData { + crc: number; + record_type: RecordType; + values: WalValue[]; +} + +export interface WalValue { + log_type: LogType; + key_data: Uint8Array; + key: string; + value_data: Uint8Array | null; + value: string | number | boolean | unknown[] | Record; + value_type: ValueType; + value_type_number: number; + path: string; +} + +export enum LogType { + Deletion = "Deletion", + Value = "Value", + Unknown = "Unknown", +} + +export enum ManifestTag { + Comparator = "Comparator", + LogNumber = "LogNumber", + NextFileNumber = "NextFileNumber", + LastSequence = "LastSequence", + CompactPointer = "CompactPointer", + DeletedFile = "DeletedFile", + NewFile = "NewFile", + PrevLogNumber = "PrevLogNumber", + Unknown = "Unknown", +} + +export enum ValueType { + String = "String", + Null = "Null", + Number = "Number", + Date = "Date", + Binary = "Binary", + Array = "Array", + /**Likely represents a deleted value */ + Unknown = "Unknown", + Protobuf = "Protobuf", + Utf16 = "Utf16", +} + +export enum RecordType { + Full = "Full", + First = "First", + Middle = "Middle", + Last = "Last", + Unknown = "Unknown", +} + +export interface LevelRecords { + [ key: string ]: string | number | boolean | Uint8Array | NewFileValue | CompactPoint | DeletedFile; +} +export interface NewFileValue { + level: number; + file_number: number; + size: number; + smallest_key: Uint8Array; + largest_key: Uint8Array; +} + +export interface CompactPoint { + level: number; + key: Uint8Array; +} + +export interface DeletedFile { + level: number; + file_number: number; } \ No newline at end of file diff --git a/types/applications/libreoffice.ts b/types/applications/libreoffice.ts index 03fe09cb..e0bf3f13 100644 --- a/types/applications/libreoffice.ts +++ b/types/applications/libreoffice.ts @@ -17,7 +17,7 @@ export interface RecentFilesLibreOffice { /**Base64 encoded thumbnail of file */ thumbnail: string; /**Path to registrymodifications.xcu */ - source: string; + evidence: string; message: string; datetime: "1970-01-01T00:00:00.000Z"; timestamp_desc: "N/A"; diff --git a/types/applications/office.ts b/types/applications/office.ts index 9d79d964..b55269e9 100644 --- a/types/applications/office.ts +++ b/types/applications/office.ts @@ -1,16 +1,20 @@ -export interface OfficeRecentFilesWindows { - path: string; - last_opened: string; - application: string; - registry_file: string; - key_path: string; -} - -export enum OfficeApp { - WORD = "Word", - POWERPOINT = "PowerPoint", - EXCEL = "Excel", - ACCESS = "Access", - ONENOTE = "OneNote", - UNKNOWN = "Unknown", -} +export interface OfficeRecentFilesWindows { + path: string; + last_opened: string; + application: string; + key_path: string; + timestamp_desc: "Last Opened"; + artifact: "Office Recent File"; + data_type: "application:office:recent:entry"; + message: string; + evidence: string; +} + +export enum OfficeApp { + WORD = "Word", + POWERPOINT = "PowerPoint", + EXCEL = "Excel", + ACCESS = "Access", + ONENOTE = "OneNote", + UNKNOWN = "Unknown", +} diff --git a/types/applications/onedrive.ts b/types/applications/onedrive.ts index c3e5cf84..7bfd34de 100644 --- a/types/applications/onedrive.ts +++ b/types/applications/onedrive.ts @@ -1,88 +1,89 @@ -export interface OneDriveLog { - path: string; - filename: string; - created: string; - code_file: string; - function: string; - flags: number; - params: string; - version: string; - os_version: string; - description: string; - message: string; - datetime: string; - timestamp_desc: "OneDrive Log Entry Created"; - artifact: "OneDrive Log"; - data_type: "applications:onedrive:logs:entry"; -} - -export interface OneDriveSyncEngineRecord { - parent_resource_id: string; - resource_id: string; - etag: string; - filename: string; - path: string; - directory: string; - file_status: number | null; - permissions: number | null; - volume_id: number | null; - item_index: number | null; - last_change: string; - size: number | null; - hash_digest: string; - shared_item: string | null; - media_date_taken: string | null; - media_width: number | null; - media_height: number | null; - media_duration: number | null; - /**JSON string */ - graph_metadata: string; - created_by: string; - modified_by: string; - last_write_count: number; - db_path: string; - message: string; - datetime: string; - timestamp_desc: "OneDrive Sync Last Change"; - artifact: "OneDrive Sync Record"; - data_type: "applications:onedrive:sync:entry"; -} - -export interface OneDriveSyncEngineFolder { - parent_scope_id: string; - parent_resource_id: string; - resource_id: string; - etag: string; - folder: string; - folder_status: number | null; - permissions: number | null; - volume_id: number | null; - item_index: number | null; - parents: string[]; -} - -export interface OneDriveAccount { - email: string; - device_id: string; - account_id: string; - /**Not available on macOS */ - last_signin: string; - cid: string; - message: string; - datetime: string; - timestamp_desc: "OneDrive Last Signin"; - artifact: "OneDrive Account Info"; - data_type: "applications:onedrive:account:entry"; -} - -export interface KeyInfo { - path: string; - key: string; -} - -export interface OnedriveProfile { - sync_db: string[]; - odl_files: string[]; - key_file: string[]; - config_file: string[]; +export interface OneDriveLog { + filename: string; + created: string; + code_file: string; + function: string; + flags: number; + params: string; + version: string; + os_version: string; + description: string; + message: string; + datetime: string; + timestamp_desc: "OneDrive Log Entry Created"; + artifact: "OneDrive Log"; + data_type: "applications:onedrive:logs:entry"; + evidence: string; +} + +export interface OneDriveSyncEngineRecord { + parent_resource_id: string; + resource_id: string; + etag: string; + filename: string; + path: string; + directory: string; + file_status: number | null; + permissions: number | null; + volume_id: number | null; + item_index: number | null; + last_change: string; + size: number | null; + hash_digest: string; + shared_item: string | null; + media_date_taken: string | null; + media_width: number | null; + media_height: number | null; + media_duration: number | null; + /**JSON string */ + graph_metadata: string; + created_by: string; + modified_by: string; + last_write_count: number; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "OneDrive Sync Last Change"; + artifact: "OneDrive Sync Record"; + data_type: "applications:onedrive:sync:entry"; +} + +export interface OneDriveSyncEngineFolder { + parent_scope_id: string; + parent_resource_id: string; + resource_id: string; + etag: string; + folder: string; + folder_status: number | null; + permissions: number | null; + volume_id: number | null; + item_index: number | null; + parents: string[]; +} + +export interface OneDriveAccount { + email: string; + device_id: string; + account_id: string; + /**Not available on macOS */ + last_signin: string; + cid: string; + message: string; + datetime: string; + timestamp_desc: "OneDrive Last Signin"; + artifact: "OneDrive Account Info"; + data_type: "applications:onedrive:account:entry"; + evidence: string; +} + +export interface KeyInfo { + path: string; + key: string; +} + +export interface OnedriveProfile { + sync_db: string[]; + odl_files: string[]; + key_file: string[]; + config_file: string[]; } \ No newline at end of file diff --git a/types/applications/rustdesk.ts b/types/applications/rustdesk.ts new file mode 100644 index 00000000..97768502 --- /dev/null +++ b/types/applications/rustdesk.ts @@ -0,0 +1,20 @@ +export interface RustDeskUsers { + config_path: string; + logs_path: string; + version: string; + /** Remote ID associated with application */ + remote_id: string; +} + +export interface RustDeskLogs { + evidence: string; + message: string; + datetime: string; + level: string; + code_path: string; + local_time: string; + remote_id: string; + timestamp_desc: "Log Event"; + artifact: "RustDesk Log"; + data_type: "applications:rustdesk:log:entry"; +} \ No newline at end of file diff --git a/types/applications/syncthing.ts b/types/applications/syncthing.ts index ec42c670..e054fa26 100644 --- a/types/applications/syncthing.ts +++ b/types/applications/syncthing.ts @@ -1,14 +1,14 @@ -export interface SyncthingClient { - full_path: string; -} - -export interface SyncthingLogs { - full_path: string; - tag: string; - datetime: string; - timestamp_desc: "Syncthing Log Entry"; - level: string; - message: string; - artifact: "Syncthing Log"; - data_type: "application:syncthing:log:entry"; +export interface SyncthingClient { + full_path: string; +} + +export interface SyncthingLogs { + tag: string; + datetime: string; + timestamp_desc: "Syncthing Log Entry"; + level: string; + message: string; + artifact: "Syncthing Log"; + data_type: "application:syncthing:log:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/applications/vscode.ts b/types/applications/vscode.ts index 56942e10..e0517015 100644 --- a/types/applications/vscode.ts +++ b/types/applications/vscode.ts @@ -1,84 +1,84 @@ -/**History of files in VSCode */ -export interface FileHistory { - /**Version of History format */ - version: number; - /**To source file */ - path?: string; - /**Path to source file */ - resource: string; - /**Path to history source */ - history_path: string; - message: string; - datetime: string; - timestamp_desc: "File Saved"; - artifact: "File History"; - data_type: "applications:vscode:filehistory:entry"; - /**Name of history file */ - id: string; - /**Time when file was saved */ - file_saved: number | string; - /**Based64 encoded file content */ - content?: string; - source?: string; - sourceDescription?: string; - [ key: string ]: unknown; -} - -/** - * Metadata related to file history entry - */ -export interface Entries { - /**Name of history file */ - id: string; - /**Time when file was saved */ - timestamp: number | string; - /**Based64 encoded file content */ - content: string; - source?: string; - sourceDescription?: string; -} - -export interface Extensions { - path: string; - data: Record[]; -} - - -export interface RecentFiles { - path_type: RecentType; - path: string; - enabled: boolean; - label: string; - external: string; - storage_path: string; -} - -export enum RecentType { - File = "File", - Folder = "Folder" -} - -export interface VscodeStorage { - lastKnownMenubarData: { - menus: { - File: { - items: { - id: string; - label: string; - submenu?: { - items: { - id: string; - label?: string; - enabled?: boolean; - uri?: { - external: string; - path: string; - scheme: string; - }; - }[]; - }; - }[]; - }; - }; - }; +/**History of files in VSCode */ +export interface FileHistory { + /**Version of History format */ + version: number; + /**To source file */ + path?: string; + /**Path to source file */ + resource: string; + /**Path to history source */ + evidence: string; + message: string; + datetime: string; + timestamp_desc: "File Saved"; + artifact: "File History"; + data_type: "applications:vscode:filehistory:entry"; + /**Name of history file */ + id: string; + /**Time when file was saved */ + file_saved: number | string; + /**Based64 encoded file content */ + content?: string; + source?: string; + sourceDescription?: string; + [ key: string ]: unknown; +} + +/** + * Metadata related to file history entry + */ +export interface Entries { + /**Name of history file */ + id: string; + /**Time when file was saved */ + timestamp: number | string; + /**Based64 encoded file content */ + content: string; + source?: string; + sourceDescription?: string; +} + +export interface Extensions { + evidence: string; + data: Record[]; +} + + +export interface RecentFiles { + path_type: RecentType; + path: string; + enabled: boolean; + label: string; + external: string; + evidence: string; +} + +export enum RecentType { + File = "File", + Folder = "Folder" +} + +export interface VscodeStorage { + lastKnownMenubarData: { + menus: { + File: { + items: { + id: string; + label: string; + submenu?: { + items: { + id: string; + label?: string; + enabled?: boolean; + uri?: { + external: string; + path: string; + scheme: string; + }; + }[]; + }; + }[]; + }; + }; + }; } \ No newline at end of file diff --git a/types/esxi/accounts.ts b/types/esxi/accounts.ts new file mode 100644 index 00000000..5a700beb --- /dev/null +++ b/types/esxi/accounts.ts @@ -0,0 +1,13 @@ +export interface Accounts { + message: string; + datetime: string; + timestamp_desc: "Passwd File Modified"; + artifact: "ESXi User Account"; + data_type: "esxi:accounts:entry"; + evidence: string; + uid: number; + gid: number; + info: string; + shell: string; + home: string; +} \ No newline at end of file diff --git a/types/esxi/logs/shell.ts b/types/esxi/logs/shell.ts new file mode 100644 index 00000000..5bd03292 --- /dev/null +++ b/types/esxi/logs/shell.ts @@ -0,0 +1,12 @@ +export interface ShellHistory { + message: string; + datetime: string; + timestamp_desc: "Shell Command Execution"; + artifact: "ESXi Shell History"; + data_type: "esxi:shell:entry"; + pid: number; + account: string; + command: string; + evidence: string; + category: string; +} \ No newline at end of file diff --git a/types/esxi/logs/syslog.ts b/types/esxi/logs/syslog.ts new file mode 100644 index 00000000..7bec051f --- /dev/null +++ b/types/esxi/logs/syslog.ts @@ -0,0 +1,11 @@ +export interface Syslog { + message: string; + datetime: string; + timestamp_desc: "Syslog Entry Generated"; + artifact: "ESXi Syslog"; + data_type: "esxi:syslog:entry"; + pid: number; + evidence: string; + category: string; + process: string; +} \ No newline at end of file diff --git a/types/esxi/vib.ts b/types/esxi/vib.ts new file mode 100644 index 00000000..73d2e14a --- /dev/null +++ b/types/esxi/vib.ts @@ -0,0 +1,74 @@ +export interface VibInfo { + message: string; + datetime: string; + timestamp_desc: "VIB Package Installed" | "VIB Package Released"; + artifact: "ESXi VIB Package"; + data_type: "esxi:vib:entry"; + vib_version: number; + install_date: string; + name: string; + version: string; + vendor: string; + summary: string; + description: string; + /**Can be spoofed easily. No guarantee to be UTC*/ + release_date: string; + level: string; + vib_type: string; + payloads: VibPayload[]; + urls: string[]; + evidence: string; + installed: boolean; + timezone: string; +} + +export interface VibPayload { + payload_type: string; + size: number; + uncompressed_size: number | undefined; + sha1_compressed: string; + sha256_compressed: string; + sha256: string; +} + +export interface RawVibXml { + vib: { + "$": { + version: string; + }; + type: string[]; + name: string[]; + version: string[]; + vendor: string[]; + summary: string[]; + installdate: string[] | undefined; + description: string[]; + "release-date": string[]; + urls: { + url: { + "$": { + key: string; + }, + "_": string; + }[]; + }[] | string[]; + "acceptance-level": string[]; + payloads: { + payload: { + "$": { + name: string; + size: string; + type: string; + "uncompressed-size": string; + }, + checksum: { + "$": { + "checksum-type": string; + "verify-process": string | undefined; + }, + "_": string; + }[]; + }[]; + }[]; + }; +} \ No newline at end of file diff --git a/types/freebsd/packages.ts b/types/freebsd/packages.ts index 67555638..687073b1 100644 --- a/types/freebsd/packages.ts +++ b/types/freebsd/packages.ts @@ -28,4 +28,5 @@ export interface Pkg { timestamp_desc: "Package Installed"; artifact: "FreeBSD PKG"; data_type: "freebsd:pkg:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/linux/abrt.ts b/types/linux/abrt.ts index c94b12a3..7233178a 100644 --- a/types/linux/abrt.ts +++ b/types/linux/abrt.ts @@ -6,7 +6,6 @@ export interface Abrt { hostname: string; last_occurrence: string; user: string; - data_directory: string; backtrace: string | Record; environment: string; home: string; @@ -15,4 +14,5 @@ export interface Abrt { timestamp_desc: "Abrt Last Occurrence"; artifact: "Abrt"; data_type: "linux:abrt:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/linux/firmware.ts b/types/linux/firmware.ts index 9c75e9bd..c919452b 100644 --- a/types/linux/firmware.ts +++ b/types/linux/firmware.ts @@ -25,4 +25,5 @@ export interface FirmwareHistory { timestamp_desc: "Firmware Device Created"; artifact: "Firmware Updates"; data_type: "linux:firmware:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/linux/gnome/epiphany.ts b/types/linux/gnome/epiphany.ts index 80c72ed1..9a932fa9 100644 --- a/types/linux/gnome/epiphany.ts +++ b/types/linux/gnome/epiphany.ts @@ -13,7 +13,7 @@ export interface EpiphanyHistory { hidden_from_overview: boolean; visit_type: VisitType; referring_visit: string | null; - db_path: string; + evidence: string; unfold?: Url; message: string; datetime: string; @@ -36,7 +36,7 @@ export interface EpiphanyCookies { is_secure: boolean | null; is_http_only: boolean | null; same_site: boolean | null; - db_path: string; + evidence: string; message: string; datetime: string; timestamp_desc: "Cookie Expires"; @@ -47,7 +47,7 @@ export interface EpiphanyCookies { export interface EpiphanyPermissions { url: string; permissions: Record; - file_path: string; + evidence: string; message: string; datetime: "1970-01-01T00:00:00.000Z", timestamp_desc: "N/A"; @@ -79,7 +79,7 @@ export interface EpiphanyPrint { printer: string; pages: string | number; collate: boolean; - file_path: string; + evidence: string; message: string; datetime: "1970-01-01T00:00:00.000Z", timestamp_desc: "N/A"; @@ -92,7 +92,7 @@ export interface EpiphanySessions { title: string; /**Base64 blob */ history: string; - session_path: string; + evidence: string; message: string; datetime: "1970-01-01T00:00:00.000Z", timestamp_desc: "N/A"; diff --git a/types/linux/gnome/extensions.ts b/types/linux/gnome/extensions.ts index 617326d8..a691923c 100644 --- a/types/linux/gnome/extensions.ts +++ b/types/linux/gnome/extensions.ts @@ -3,7 +3,7 @@ */ export interface Extension { /**Path to extension metadata.json file */ - extension_path: string; + evidence: string; /**Name of the extension */ name: string; /**Extension description */ diff --git a/types/linux/gnome/gedit.ts b/types/linux/gnome/gedit.ts index 393542a6..9fe146cf 100644 --- a/types/linux/gnome/gedit.ts +++ b/types/linux/gnome/gedit.ts @@ -6,8 +6,8 @@ export interface RecentFiles { path: string; /**Last accessed */ accessed: string; - /**Path to `gedit-metdata.xml` */ - gedit_source: string; + /**Path to `gedit-metadata.xml` */ + evidence: string; message: string; datetime: string; timestamp_desc: "Last Accessed"; diff --git a/types/linux/gnome/gvfs.ts b/types/linux/gnome/gvfs.ts index 3b3aba21..b57d3470 100644 --- a/types/linux/gnome/gvfs.ts +++ b/types/linux/gnome/gvfs.ts @@ -24,7 +24,7 @@ export interface GvfsEntry { /**Last change timestamp of the **metadata** */ last_change: string; /**GFVS file source */ - source: string; + evidence: string; message: string; datetime: string; timestamp_desc: "Last Changed"; diff --git a/types/linux/gnome/usage.ts b/types/linux/gnome/usage.ts index 44894ed7..01b092b6 100644 --- a/types/linux/gnome/usage.ts +++ b/types/linux/gnome/usage.ts @@ -9,7 +9,7 @@ export interface AppUsage { /**Application last seen timestamp */ "last-seen": string; /**Path to the parsed application_state file */ - source: string; + evidence: string; message: string; datetime: string; timestamp_desc: "Last Seen"; diff --git a/types/linux/journal.ts b/types/linux/journal.ts index 972a325f..0c1a3682 100644 --- a/types/linux/journal.ts +++ b/types/linux/journal.ts @@ -1,96 +1,98 @@ -/** - * Journal files are the logs associated with the Systemd service - * Systemd is a popular system service that is common on most Linux distros - * The logs can contain data related to application activity, sudo commands, and more - * - * References: - * - https://systemd.io/JOURNAL_FILE_FORMAT/ - * - https://wiki.archlinux.org/title/Systemd/Journal - * - https://github.com/systemd/systemd/blob/main/src/libsystemd/sd-journal/journal-def.h - * - https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html - */ -export interface Journal { - /**User ID associated with entry */ - uid: number; - /**Group ID associated with entry */ - gid: number; - /**Process ID associated with entry */ - pid: number; - /**Thread ID associated with entry */ - thread_id: number; - /**Command associated with entry */ - comm: string; - /**Priority associated with entry */ - priority: string; - /**Syslog facility associated with entry */ - syslog_facility: string; - /**Executable file associated with entry */ - executable: string; - /**Cmdline args associated with entry */ - cmdline: string; - /**Effective capabilities of process associated with entry */ - cap_effective: string; - /**Session of the process associated with entry */ - audit_session: number; - /**Login UID of the process associated with entry */ - audit_loginuid: number; - /**Systemd Control Group associated with entry */ - systemd_cgroup: string; - /**Systemd owner UID associated with entry */ - systemd_owner_uid: number; - /**Systemd unit associated with entry */ - systemd_unit: string; - /**Systemd user unit associated with entry */ - systemd_user_unit: string; - /**Systemd slice associated with entry */ - systemd_slice: string; - /**Systemd user slice associated with entry */ - systemd_user_slice: string; - /**Systemd invocation ID associated with entry */ - systemd_invocation_id: string; - /**Kernel Boot ID associated with entry */ - boot_id: string; - /**Machine ID of host associated with entry */ - machine_id: string; - /**Hostname associated with entry */ - hostname: string; - /**Runtime scope associated with entry */ - runtime_scope: string; - /**Source timestamp associated with entry */ - source_realtime: string; - /**Timestamp associated with entry */ - realtime: string; - /**How entry was received by the Journal service */ - transport: string; - /**Journal message entry */ - message: string; - /**Message ID associated with Journal Catalog */ - message_id: string; - /**Unit result associated with entry */ - unit_result: string; - /**Code line for file associated with entry */ - code_line: number; - /**Code function for file associated with entry */ - code_function: string; - /**Code file associated with entry */ - code_file: string; - /**User invocation ID associated with entry */ - user_invocation_id: string; - /**User unit associated with entry */ - user_unit: string; - /** - * Custom fields associated with entry. - * Example: - * ``` - * "custom": { - * "_SOURCE_MONOTONIC_TIMESTAMP": "536995", - * "_UDEV_SYSNAME": "0000:00:1c.3", - * "_KERNEL_DEVICE": "+pci:0000:00:1c.3", - * "_KERNEL_SUBSYSTEM": "pci" - * } - * ``` - */ - custom: Record; - /**Sequence Number associated with entry */ - seqnum: number; -} +/** + * Journal files are the logs associated with the Systemd service + * Systemd is a popular system service that is common on most Linux distros + * The logs can contain data related to application activity, sudo commands, and more + * + * References: + * - https://systemd.io/JOURNAL_FILE_FORMAT/ + * - https://wiki.archlinux.org/title/Systemd/Journal + * - https://github.com/systemd/systemd/blob/main/src/libsystemd/sd-journal/journal-def.h + * - https://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html + */ +export interface Journal { + /**User ID associated with entry */ + uid: number; + /**Group ID associated with entry */ + gid: number; + /**Process ID associated with entry */ + pid: number; + /**Thread ID associated with entry */ + thread_id: number; + /**Command associated with entry */ + comm: string; + /**Priority associated with entry */ + priority: string; + /**Syslog facility associated with entry */ + syslog_facility: string; + /**Executable file associated with entry */ + executable: string; + /**Cmdline args associated with entry */ + cmdline: string; + /**Effective capabilities of process associated with entry */ + cap_effective: string; + /**Session of the process associated with entry */ + audit_session: number; + /**Login UID of the process associated with entry */ + audit_loginuid: number; + /**Systemd Control Group associated with entry */ + systemd_cgroup: string; + /**Systemd owner UID associated with entry */ + systemd_owner_uid: number; + /**Systemd unit associated with entry */ + systemd_unit: string; + /**Systemd user unit associated with entry */ + systemd_user_unit: string; + /**Systemd slice associated with entry */ + systemd_slice: string; + /**Systemd user slice associated with entry */ + systemd_user_slice: string; + /**Systemd invocation ID associated with entry */ + systemd_invocation_id: string; + /**Kernel Boot ID associated with entry */ + boot_id: string; + /**Machine ID of host associated with entry */ + machine_id: string; + /**Hostname associated with entry */ + hostname: string; + /**Runtime scope associated with entry */ + runtime_scope: string; + /**Source timestamp associated with entry */ + source_realtime: string; + /**Timestamp associated with entry */ + realtime: string; + /**How entry was received by the Journal service */ + transport: string; + /**Journal message entry */ + message: string; + /**Message ID associated with Journal Catalog */ + message_id: string; + /**Unit result associated with entry */ + unit_result: string; + /**Code line for file associated with entry */ + code_line: number; + /**Code function for file associated with entry */ + code_function: string; + /**Code file associated with entry */ + code_file: string; + /**User invocation ID associated with entry */ + user_invocation_id: string; + /**User unit associated with entry */ + user_unit: string; + /** + * Custom fields associated with entry. + * Example: + * ``` + * "custom": { + * "_SOURCE_MONOTONIC_TIMESTAMP": "536995", + * "_UDEV_SYSNAME": "0000:00:1c.3", + * "_KERNEL_DEVICE": "+pci:0000:00:1c.3", + * "_KERNEL_SUBSYSTEM": "pci" + * } + * ``` + */ + custom: Record; + /**Sequence Number associated with entry */ + seqnum: number; + /**Path to the Journal file */ + evidence: string; +} diff --git a/types/linux/kde/falkon.ts b/types/linux/kde/falkon.ts index d107bb7e..d646d618 100644 --- a/types/linux/kde/falkon.ts +++ b/types/linux/kde/falkon.ts @@ -11,7 +11,7 @@ export interface FalkonHistory { url: string; unfold: Url | undefined; /**Path to the browsedata.db sqlite file */ - db_path: string; + evidence: string; /**Browser version */ version: string; title: string | null; @@ -47,7 +47,7 @@ export interface FalkonCookie { has_cross_site_ancestor: boolean; message: string; /**Path to the Cookies sqlite file */ - db_path: string; + evidence: string; /**Browser version */ version: string; datetime: string; @@ -64,7 +64,7 @@ export interface FalkonBookmark { url: string; visit_count: number; /**Path to the bookmarks.json file */ - path: string; + evidence: string; /**Browser version */ version: string; message: string; diff --git a/types/linux/kde/kate.ts b/types/linux/kde/kate.ts index e2c64eb9..fcff1bea 100644 --- a/types/linux/kde/kate.ts +++ b/types/linux/kde/kate.ts @@ -1,6 +1,6 @@ export interface RecentFiles { /**Path Kate session file */ - session_file: string; + evidence: string; /**Path to recent file */ file: string; /**Session file created */ diff --git a/types/linux/logon.ts b/types/linux/logon.ts index 3fb75544..2ba7e04c 100644 --- a/types/linux/logon.ts +++ b/types/linux/logon.ts @@ -1,38 +1,40 @@ -/** - * Linux `Logon` entries are tracked in three (3) files: utmp, wtmp, and btmp - * - * - btmp - contains failed logons - * - wtmp - historical logons - * - utmp - active logons - * - * References: - * - https://github.com/libyal/dtformats/blob/main/documentation/Utmp%20login%20records%20format.asciidoc - */ -export interface Logon { - /**Logon type for logon entry */ - logon_type: string; - /**Process ID */ - pid: number; - /** Terminal info */ - terminal: string; - /**Terminal ID for logon entry */ - terminal_id: number; - /**Username for logon */ - username: string; - /**Hostname for logon source */ - hostname: string; - /**Termination status for logon entry */ - termination_status: number; - /**Exit status logon entry */ - exit_status: number; - /**Session for logon entry */ - session: number; - /**Timestamp for logon */ - timestamp: string; - /**Microseconds for logon */ - microseconds: number; - /**Source IP for logon entry */ - ip: string; - /**Status of logon entry: `Success` or `Failed` */ - status: string; -} +/** + * Linux `Logon` entries are tracked in three (3) files: utmp, wtmp, and btmp + * + * - btmp - contains failed logons + * - wtmp - historical logons + * - utmp - active logons + * + * References: + * - https://github.com/libyal/dtformats/blob/main/documentation/Utmp%20login%20records%20format.asciidoc + */ +export interface Logon { + /**Logon type for logon entry */ + logon_type: string; + /**Process ID */ + pid: number; + /** Terminal info */ + terminal: string; + /**Terminal ID for logon entry */ + terminal_id: number; + /**Username for logon */ + username: string; + /**Hostname for logon source */ + hostname: string; + /**Termination status for logon entry */ + termination_status: number; + /**Exit status logon entry */ + exit_status: number; + /**Session for logon entry */ + session: number; + /**Timestamp for logon */ + timestamp: string; + /**Microseconds for logon */ + microseconds: number; + /**Source IP for logon entry */ + ip: string; + /**Status of logon entry: `Success` or `Failed` */ + status: string; + /**Path to the logon file */ + evidence: string; +} diff --git a/types/linux/rpm.ts b/types/linux/rpm.ts index f3a2a032..570e743f 100644 --- a/types/linux/rpm.ts +++ b/types/linux/rpm.ts @@ -16,4 +16,5 @@ export interface RpmPackages { timestamp_desc: "RPM Package Installed"; artifact: "RPM Package"; data_type: "linux:rpm:entry"; + evidence: string } diff --git a/types/linux/sqlite/wtmpdb.ts b/types/linux/sqlite/wtmpdb.ts index a01667b4..cfc08e02 100644 --- a/types/linux/sqlite/wtmpdb.ts +++ b/types/linux/sqlite/wtmpdb.ts @@ -12,4 +12,5 @@ export interface LastLogons { timestamp_desc: "User Logon"; artifact: "wtmpdb Logons"; data_type: "linux:wtmpdb:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/macos/accounts.ts b/types/macos/accounts.ts index a4774b68..5c5d306c 100644 --- a/types/macos/accounts.ts +++ b/types/macos/accounts.ts @@ -1,45 +1,49 @@ -/** - * Local macOS `Users` information by parsing the plist files at `/var/db/dslocal/nodes/Default/users` - */ -export interface Users { - /**UID for the user */ - uid: string[]; - /**GID associated with the user */ - gid: string[]; - /**User name */ - name: string[]; - /**Real name associated with user */ - real_name: string[]; - /**Base64 encoded photo associated with user */ - account_photo: string[]; - /**Timestamp the user was created */ - account_created: string; - /**Password last changed for the user*/ - password_last_set: string; - /**Shell associated with the user */ - shell: string[]; - /**Unlock associated with the user */ - unlock_options: string[]; - /**Home path associated with user */ - home_path: string[]; - /**UUID associated with user */ - uuid: string[]; -} - -/** - * Local macOS `Groups` information by parsing the PLIST files at `/var/db/dslocal/nodes/Default/groups` - */ -export interface Groups { - /**GID for the group */ - gid: string[]; - /**Name of the group */ - name: string[]; - /**Real name associated with the group */ - real_name: string[]; - /**Users associated with group */ - users: string[]; - /**Group members in the group */ - groupmembers: string[]; - /**UUID associated with the group */ - uuid: string[]; -} +/** + * Local macOS `Users` information by parsing the plist files at `/var/db/dslocal/nodes/Default/users` + */ +export interface Users { + /**UID for the user */ + uid: string[]; + /**GID associated with the user */ + gid: string[]; + /**User name */ + name: string[]; + /**Real name associated with user */ + real_name: string[]; + /**Base64 encoded photo associated with user */ + account_photo: string[]; + /**Timestamp the user was created */ + account_created: string; + /**Password last changed for the user*/ + password_last_set: string; + /**Shell associated with the user */ + shell: string[]; + /**Unlock associated with the user */ + unlock_options: string[]; + /**Home path associated with user */ + home_path: string[]; + /**UUID associated with user */ + uuid: string[]; + /**Path to the users plist file */ + evidence: string; +} + +/** + * Local macOS `Groups` information by parsing the PLIST files at `/var/db/dslocal/nodes/Default/groups` + */ +export interface Groups { + /**GID for the group */ + gid: string[]; + /**Name of the group */ + name: string[]; + /**Real name associated with the group */ + real_name: string[]; + /**Users associated with group */ + users: string[]; + /**Group members in the group */ + groupmembers: string[]; + /**UUID associated with the group */ + uuid: string[]; + /**Path to the groups plist file */ + evidence: string; +} diff --git a/types/macos/bom.ts b/types/macos/bom.ts index 2778c8e0..21b70916 100644 --- a/types/macos/bom.ts +++ b/types/macos/bom.ts @@ -7,7 +7,7 @@ export interface Bom { install_prefix_path: string; path: string; /**Path to BOM file */ - bom_path: string; + evidence: string; files: BomFiles[]; } diff --git a/types/macos/bookmark.ts b/types/macos/bookmark.ts index 0d5277ee..7d8a3922 100644 --- a/types/macos/bookmark.ts +++ b/types/macos/bookmark.ts @@ -41,6 +41,12 @@ export interface BookmarkData { is_executable: boolean; /**Does target file have file reference flag */ file_ref_flag: boolean; + /**URL string to target */ + url_string: string; + /**Target filename */ + target_filename: string; + /**Volume depth associated with target */ + volume_depth: number; } export enum TargetFlags { diff --git a/types/macos/emond.ts b/types/macos/emond.ts index bf72c11c..03ed4bde 100644 --- a/types/macos/emond.ts +++ b/types/macos/emond.ts @@ -1,91 +1,93 @@ -/** - * macOS `Emond` (Event Monitor) can be used as persistence on a system. - * A user can create `Emond` rules to execute commands on macOS. - * - * Starting on Ventura `Emond` was removed - * - * References: - * - https://www.xorrior.com/emond-persistence/ - */ -export interface Emond { - /**Name of `Emond` rule */ - name: string; - /**Is rule enabled */ - enabled: boolean; - /**Event types associated with the rule */ - event_types: string[]; - /**Start time of the rule */ - start_time: string; - /**If partial criteria match should trigger the rule */ - allow_partial_criterion_match: boolean; - /**Array of command actions if rule is triggered */ - command_actions: Command[]; - /**Array of log actions if rule is triggered */ - log_actions: Log[]; - /**Array of send email actions if rule is triggered */ - send_email_actions: SendEmailSms[]; - /**Array of send sms actions if rule is triggered. Has same structure as send email */ - send_sms_actions: SendEmailSms[]; - /**Criteria for the `Emond` rule */ - criterion: Record[]; - /**Variables associated with the criterion */ - variables: Record[]; - /**If the emond client is enabled */ - emond_clients_enabled: boolean; - /**Emond plist created */ - plist_created: string; - /**Emond plist modified */ - plist_modifed: string; - /**Emond plist changed */ - plist_changed: string; - /**Emond plist accessed */ - plist_accessed: string; -} - -/** - * Commands to execute if rule is triggered - */ -interface Command { - /**Command name */ - command: string; - /**User associated with command */ - user: string; - /**Group associated with command */ - group: string; - /**Arguments associated with command */ - arguments: string[]; -} - -/** - * Log settings if rule is triggered - */ -interface Log { - /**Log message content */ - message: string; - /**Facility associated with log action */ - facility: string; - /**Level of log */ - log_level: string; - /**Log type */ - log_type: string; - /**Parameters associated with log action */ - parameters: Record; -} - -/** - * Email or SMS to send if rule is triggered - */ -interface SendEmailSms { - /**Content of the email/sms */ - message: string; - /**Subject of the email/sms */ - subject: string; - /**Path to local binary */ - localization_bundle_path: string; - /**Remote URL to send the message */ - relay_host: string; - /**Email associated with email/sms action */ - admin_email: string; - /**Targets to receive email/sms */ - recipient_addresses: string[]; -} +/** + * macOS `Emond` (Event Monitor) can be used as persistence on a system. + * A user can create `Emond` rules to execute commands on macOS. + * + * Starting on Ventura `Emond` was removed + * + * References: + * - https://www.xorrior.com/emond-persistence/ + */ +export interface Emond { + /**Name of `Emond` rule */ + name: string; + /**Is rule enabled */ + enabled: boolean; + /**Event types associated with the rule */ + event_types: string[]; + /**Start time of the rule */ + start_time: string; + /**If partial criteria match should trigger the rule */ + allow_partial_criterion_match: boolean; + /**Array of command actions if rule is triggered */ + command_actions: Command[]; + /**Array of log actions if rule is triggered */ + log_actions: Log[]; + /**Array of send email actions if rule is triggered */ + send_email_actions: SendEmailSms[]; + /**Array of send sms actions if rule is triggered. Has same structure as send email */ + send_sms_actions: SendEmailSms[]; + /**Criteria for the `Emond` rule */ + criterion: Record[]; + /**Variables associated with the criterion */ + variables: Record[]; + /**If the emond client is enabled */ + emond_clients_enabled: boolean; + /**Emond plist created */ + plist_created: string; + /**Emond plist modified */ + plist_modifed: string; + /**Emond plist changed */ + plist_changed: string; + /**Emond plist accessed */ + plist_accessed: string; + /**Path to emond plist */ + evidence: string; +} + +/** + * Commands to execute if rule is triggered + */ +interface Command { + /**Command name */ + command: string; + /**User associated with command */ + user: string; + /**Group associated with command */ + group: string; + /**Arguments associated with command */ + arguments: string[]; +} + +/** + * Log settings if rule is triggered + */ +interface Log { + /**Log message content */ + message: string; + /**Facility associated with log action */ + facility: string; + /**Level of log */ + log_level: string; + /**Log type */ + log_type: string; + /**Parameters associated with log action */ + parameters: Record; +} + +/** + * Email or SMS to send if rule is triggered + */ +interface SendEmailSms { + /**Content of the email/sms */ + message: string; + /**Subject of the email/sms */ + subject: string; + /**Path to local binary */ + localization_bundle_path: string; + /**Remote URL to send the message */ + relay_host: string; + /**Email associated with email/sms action */ + admin_email: string; + /**Targets to receive email/sms */ + recipient_addresses: string[]; +} diff --git a/types/macos/execpolicy.ts b/types/macos/execpolicy.ts index 3e4bc76e..141b16bf 100644 --- a/types/macos/execpolicy.ts +++ b/types/macos/execpolicy.ts @@ -1,70 +1,72 @@ -/** - * macOS `ExecPolicy` tracks application execution on a system. - * It only tracks execution of applications that are tracked by GateKeeper - * - * References: - * - https://eclecticlight.co/2023/03/13/ventura-has-changed-app-quarantine-with-a-new-xattr/ - * - https://knight.sc/reverse%20engineering/2019/02/20/syspolicyd-internals.html - */ -export interface ExecPolicy { - /**Is file signed */ - is_signed: number; - /**Name of executable */ - file_identifier: string; - /**App bundle ID for entry */ - bundle_identifier: string; - /**Bundle version for entry */ - bundle_version: string; - /**Team ID for entry */ - team_identifier: string; - /**Signing ID for entry */ - signing_identifier: string; - /**Code Directory hash if available otherwise SHA256 hash of executable*/ - cdhash: string; - /**SHA256 hash of executable */ - main_executable_hash: string; - /**Timestamp when the executable was inserted in ExecPolicy database */ - executable_timestamp: string; - /**Size of file */ - file_size: number; - /**Is library */ - is_library: number; - /**Is file used */ - is_used: number; - /**Parent Application File ID associated with entry. This is often the Parent Process */ - responsible_file_identifier: string; - /**Is valid entry */ - is_valid: number; - /**Is quarantined entry */ - is_quarantined: number; - /**Timestamp for executable measurements */ - executable_measurements_v2_timestamp: string; - /**Reported timestamp */ - reported_timstamp: string; - /**Primary key */ - pk: number; - /**Volume UUID for entry */ - volume_uuid: string; - /**Object ID for entry */ - object_id: number; - /**Filesystem type. Typically APFS */ - fs_type_name: string; - /**App Bundle ID. Should be same as `bundle_identifier` */ - bundle_id: string; - /**Policy match for entry */ - policy_match: number; - /**Malware result for entry */ - malware_result: number; - /**Flags for entry */ - flags: number; - /**Modified time */ - mod_time: string; - /**Policy scan cache timestamp */ - policy_scan_cache_timestamp: string; - /**Revocation check timestamp */ - revocation_check_time: string; - /**Scan version for entry */ - scan_version: number; - /**Top policy match for entry */ - top_policy_match: number; -} +/** + * macOS `ExecPolicy` tracks application execution on a system. + * It only tracks execution of applications that are tracked by GateKeeper + * + * References: + * - https://eclecticlight.co/2023/03/13/ventura-has-changed-app-quarantine-with-a-new-xattr/ + * - https://knight.sc/reverse%20engineering/2019/02/20/syspolicyd-internals.html + */ +export interface ExecPolicy { + /**Is file signed */ + is_signed: number; + /**Name of executable */ + file_identifier: string; + /**App bundle ID for entry */ + bundle_identifier: string; + /**Bundle version for entry */ + bundle_version: string; + /**Team ID for entry */ + team_identifier: string; + /**Signing ID for entry */ + signing_identifier: string; + /**Code Directory hash if available otherwise SHA256 hash of executable*/ + cdhash: string; + /**SHA256 hash of executable */ + main_executable_hash: string; + /**Timestamp when the executable was inserted in ExecPolicy database */ + executable_timestamp: string; + /**Size of file */ + file_size: number; + /**Is library */ + is_library: number; + /**Is file used */ + is_used: number; + /**Parent Application File ID associated with entry. This is often the Parent Process */ + responsible_file_identifier: string; + /**Is valid entry */ + is_valid: number; + /**Is quarantined entry */ + is_quarantined: number; + /**Timestamp for executable measurements */ + executable_measurements_v2_timestamp: string; + /**Reported timestamp */ + reported_timstamp: string; + /**Primary key */ + pk: number; + /**Volume UUID for entry */ + volume_uuid: string; + /**Object ID for entry */ + object_id: number; + /**Filesystem type. Typically APFS */ + fs_type_name: string; + /**App Bundle ID. Should be same as `bundle_identifier` */ + bundle_id: string; + /**Policy match for entry */ + policy_match: number; + /**Malware result for entry */ + malware_result: number; + /**Flags for entry */ + flags: number; + /**Modified time */ + mod_time: string; + /**Policy scan cache timestamp */ + policy_scan_cache_timestamp: string; + /**Revocation check timestamp */ + revocation_check_time: string; + /**Scan version for entry */ + scan_version: number; + /**Top policy match for entry */ + top_policy_match: number; + /**Path to ExecPolicy database */ + evidence: string; +} diff --git a/types/macos/fsevents.ts b/types/macos/fsevents.ts index 892647b8..11843a16 100644 --- a/types/macos/fsevents.ts +++ b/types/macos/fsevents.ts @@ -1,28 +1,28 @@ -/** - * `FsEvent` data track changes to files on a macOS system (similar to `UsnJrnl`). - * Resides at /System/Volumes/Data/.fseventsd/ or /.fseventsd on older systems - * - * References: - * - https://github.com/libyal/dtformats/blob/main/documentation/MacOS%20File%20System%20Events%20Disk%20Log%20Stream%20format.asciidoc - * - http://www.osdfcon.org/presentations/2017/Ibrahim-Understanding-MacOS-File-Ststem-Events-with-FSEvents-Parser.pdf - */ -export interface Fsevents { - /**Flags associated with FsEvent record */ - flags: string[]; - /**Full path to file associated with FsEvent record */ - path: string; - /**Node ID associated with FsEvent record */ - node: number; - /**Event ID associated with FsEvent record */ - event_id: number; - /**Path to the FsEvent file */ - source: string; - /**Created timestamp of the FsEvent source */ - source_created: string; - /**Modified timestamp of the FsEvent source */ - source_modified: string; - /**Changed timestamp of the FsEvent source */ - source_changed: string; - /**Accessed timestamp of the FsEvent source */ - source_accessed: string; -} +/** + * `FsEvent` data track changes to files on a macOS system (similar to `UsnJrnl`). + * Resides at /System/Volumes/Data/.fseventsd/ or /.fseventsd on older systems + * + * References: + * - https://github.com/libyal/dtformats/blob/main/documentation/MacOS%20File%20System%20Events%20Disk%20Log%20Stream%20format.asciidoc + * - http://www.osdfcon.org/presentations/2017/Ibrahim-Understanding-MacOS-File-Ststem-Events-with-FSEvents-Parser.pdf + */ +export interface Fsevents { + /**Flags associated with FsEvent record */ + flags: string[]; + /**Full path to file associated with FsEvent record */ + path: string; + /**Node ID associated with FsEvent record */ + node: number; + /**Event ID associated with FsEvent record */ + event_id: number; + /**Path to the FsEvent file */ + evidence: string; + /**Created timestamp of the FsEvent source */ + source_created: string; + /**Modified timestamp of the FsEvent source */ + source_modified: string; + /**Changed timestamp of the FsEvent source */ + source_changed: string; + /**Accessed timestamp of the FsEvent source */ + source_accessed: string; +} diff --git a/types/macos/launchd.ts b/types/macos/launchd.ts index 8333724a..8534d5a4 100644 --- a/types/macos/launchd.ts +++ b/types/macos/launchd.ts @@ -1,18 +1,18 @@ -/** - * Launchd plist files can contain a large amount of configuration options. - * See `man launchd.plist` or `https://www.launchd.info/` - */ -export interface Launchd { - /**JSON representation of launchd plist contents */ - launchd_data: Record; - /**Full path of the plist file */ - plist_path: string; - /**Created timestamp for plist file */ - created: string; - /**Modified timestamp for plist file */ - modified: string; - /**Accessed timestamp for plist file */ - accessed: string; - /**Changed timestamp for plist file */ - changed: string; -} +/** + * Launchd plist files can contain a large amount of configuration options. + * See `man launchd.plist` or `https://www.launchd.info/` + */ +export interface Launchd { + /**JSON representation of launchd plist contents */ + launchd_data: Record; + /**Full path of the plist file */ + evidence: string; + /**Created timestamp for plist file */ + created: string; + /**Modified timestamp for plist file */ + modified: string; + /**Accessed timestamp for plist file */ + accessed: string; + /**Changed timestamp for plist file */ + changed: string; +} diff --git a/types/macos/loginitems.ts b/types/macos/loginitems.ts index e65e2a63..88e50fb5 100644 --- a/types/macos/loginitems.ts +++ b/types/macos/loginitems.ts @@ -1,66 +1,66 @@ -import { CreationFlags, TargetFlags, VolumeFlags } from "./bookmark"; - -/** - * `LoginItems` are a form of persistence on macOS systems. - * They are triggered when a user logs on to the system. - * They are located at: - * - `/Users/%/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm` (pre-Ventura) - * - `/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v4.btm` (Ventura+) - * - * Both are plist files that are parsed to get the `LoginItem` data - * - * References: - * - https://www.sentinelone.com/blog/how-malware-persists-on-macos/ - */ -export interface LoginItems { - /**Path to file to run */ - path: string; - /**Path represented as Catalog Node ID */ - cnid_path: string; - /**Created timestamp of target file */ - created: string; - /**Path to the volume of target file */ - volume_path: string; - /**Target file URL type */ - volume_url: string; - /**Name of volume target file is on */ - volume_name: string; - /**Volume UUID */ - volume_uuid: string; - /**Size of target volume in bytes */ - volume_size: number; - /**Created timestamp of volume */ - volume_created: string; - /**Volume Property flags */ - volume_flags: VolumeFlags[]; - /**Flag if volume if the root filesystem */ - volume_root: boolean; - /**Localized name of target file */ - localized_name: string; - /**Read-Write security extension of target file */ - security_extension_rw: string; - /**Read-Only security extension of target file */ - security_extension_ro: string; - /**File property flags */ - target_flags: TargetFlags[]; - /**Username associated with `Bookmark` */ - username: string; - /**Folder index number associated with target file */ - folder_index: number; - /**UID associated with `LoginItem` */ - uid: number; - /**`LoginItem` creation flags */ - creation_options: CreationFlags[]; - /**Is `LoginItem` bundled in app */ - is_bundled: boolean; - /**App ID associated with `LoginItem` */ - app_id: string; - /**App binary name */ - app_binary: string; - /**Is target file executable */ - is_executable: boolean; - /**Does target file have file reference flag */ - file_ref_flag: boolean; - /**Path to `LoginItem` source */ - source_path: string; -} +import { CreationFlags, TargetFlags, VolumeFlags } from "./bookmark"; + +/** + * `LoginItems` are a form of persistence on macOS systems. + * They are triggered when a user logs on to the system. + * They are located at: + * - `/Users/%/Library/Application Support/com.apple.backgroundtaskmanagementagent/backgrounditems.btm` (pre-Ventura) + * - `/var/db/com.apple.backgroundtaskmanagement/BackgroundItems-v4.btm` (Ventura+) + * + * Both are plist files that are parsed to get the `LoginItem` data + * + * References: + * - https://www.sentinelone.com/blog/how-malware-persists-on-macos/ + */ +export interface LoginItems { + /**Path to file to run */ + path: string; + /**Path represented as Catalog Node ID */ + cnid_path: string; + /**Created timestamp of target file */ + created: string; + /**Path to the volume of target file */ + volume_path: string; + /**Target file URL type */ + volume_url: string; + /**Name of volume target file is on */ + volume_name: string; + /**Volume UUID */ + volume_uuid: string; + /**Size of target volume in bytes */ + volume_size: number; + /**Created timestamp of volume */ + volume_created: string; + /**Volume Property flags */ + volume_flags: VolumeFlags[]; + /**Flag if volume if the root filesystem */ + volume_root: boolean; + /**Localized name of target file */ + localized_name: string; + /**Read-Write security extension of target file */ + security_extension_rw: string; + /**Read-Only security extension of target file */ + security_extension_ro: string; + /**File property flags */ + target_flags: TargetFlags[]; + /**Username associated with `Bookmark` */ + username: string; + /**Folder index number associated with target file */ + folder_index: number; + /**UID associated with `LoginItem` */ + uid: number; + /**`LoginItem` creation flags */ + creation_options: CreationFlags[]; + /**Is `LoginItem` bundled in app */ + is_bundled: boolean; + /**App ID associated with `LoginItem` */ + app_id: string; + /**App binary name */ + app_binary: string; + /**Is target file executable */ + is_executable: boolean; + /**Does target file have file reference flag */ + file_ref_flag: boolean; + /**Path to `LoginItem` source */ + evidence: string; +} diff --git a/types/macos/plist/apps.ts b/types/macos/plist/apps.ts index a5d4557b..5b7ff6b7 100644 --- a/types/macos/plist/apps.ts +++ b/types/macos/plist/apps.ts @@ -10,5 +10,5 @@ export interface Applications { copyright: string; /**Base64 encoded PNG file */ icon: string; - info: string; + evidence: string; } diff --git a/types/macos/plist/lulu.ts b/types/macos/plist/lulu.ts index f1d71ae8..9a36a59d 100644 --- a/types/macos/plist/lulu.ts +++ b/types/macos/plist/lulu.ts @@ -1,20 +1,29 @@ -export interface LuluRules { - path: string; - rules: Rule[]; -} - export interface Rule { + /**Path to the rules.plist file */ + evidence: string; + /**Binary file allowed to make connection */ file: string; + /**UUID associated with the rule */ uuid: string; + /**Address associated with the rule */ endpoint_addr: string; + /**Is regex enabled */ is_regex: boolean; + /**Scope associated with the rule */ scope: string; + /**Rule action */ type: string; + /**Key associated with the rule */ key: string; + /**LuLu Action performed */ action: LuluAction; + /**Host associated with the rule */ endpoint_host: string; + /**Code Signing info associated with the binary */ code_signing_info: Record; + /**Process ID */ pid: number; + /**Port associated with the rule */ endpoint_port: number; } diff --git a/types/macos/plist/sharefilelist.ts b/types/macos/plist/sharefilelist.ts new file mode 100644 index 00000000..40f1704b --- /dev/null +++ b/types/macos/plist/sharefilelist.ts @@ -0,0 +1,32 @@ +export interface RecentFiles { + evidence: string; + shared_file_type: SharedFileType; + message: string; + datetime: string; + timestamp_desc: string; + artifact: "Recent Files"; + data_type: "macos:plist:recentfile:entry"; + plist_data_type: PlistDataType; + [ key: string ]: unknown; +} + +export interface SharedFilelistRaw { + "$version": number; + "$archiver": string; + "$objects": (string | number | boolean | Record | number[])[]; +} + +export enum SharedFileType { + FinderFavorite = "Finder Favorite", + VolumeFavorite = "Volume Favorite", + UnknownFavorite = "Unknown Favorite", + ApplicationRecentFiles = "Application Recent File", + ProjectFavorite = "Tag Favorite", + RecentApplication = "Recent Application", + RecentDocuments = "Recent Documents", +} + +export enum PlistDataType { + Bookmark = "Bookmark", + CodeSign = "Code Signing" +} \ No newline at end of file diff --git a/types/macos/plist/system_extensions.ts b/types/macos/plist/system_extensions.ts index ed71c19f..fae3ebb3 100644 --- a/types/macos/plist/system_extensions.ts +++ b/types/macos/plist/system_extensions.ts @@ -7,4 +7,5 @@ export interface SystemExtension { categories: string[]; bundle_path: string; team: string; + evidence: string; } diff --git a/types/macos/safari.ts b/types/macos/safari.ts index 448cbe17..3e368893 100644 --- a/types/macos/safari.ts +++ b/types/macos/safari.ts @@ -50,6 +50,7 @@ export interface SafariHistory { timestamp_desc: "URL Visited"; artifact: "URL History"; data_type: "macos:safari:history:entry"; + evidence: string; } /** @@ -126,6 +127,7 @@ export interface SafariDownloads { timestamp_desc: "File Download Start"; artifact: "File Download"; data_type: "macos:safari:downloads:entry"; + evidence: string; } export interface SafariProfile { @@ -148,6 +150,7 @@ export interface Cookie { artifact: "Website Cookie"; data_type: "macos:safari:cookies:entry"; [key: string]: unknown; + evidence: string; } export enum CookieFlag { @@ -170,6 +173,7 @@ export interface SafariBookmark { timestamp_desc: "Bookmark Created"; artifact: "Website Bookmark"; data_type: "macos:safari:bookmark:entry"; + evidence: string; } export interface SafariFavicon { @@ -186,6 +190,7 @@ export interface SafariFavicon { timestamp_desc: "Favicon Created"; artifact: "URL Favicon"; data_type: "macos:safari:favicons:entry"; + evidence: string; } export interface SafariExtensions { @@ -205,6 +210,7 @@ export interface SafariExtensions { timestamp_desc: "Extension Installed"; artifact: "Browser Extension"; data_type: "macos:safari:extension:entry"; + evidence: string; } export interface SafariPlistBookmark { diff --git a/types/macos/spotlight.ts b/types/macos/spotlight.ts index ff09c865..04ac3eb6 100644 --- a/types/macos/spotlight.ts +++ b/types/macos/spotlight.ts @@ -1,134 +1,134 @@ -/** - * macOS `Spotlight` is an indexing service for tracking files and content. - * The `Spotlight` database can contain a huge amount of metadata associated with the indexed content such as: - * - Timestamps - * - Partial file content - * - File type and much more - * - * References: - * - https://forensicsandsecurity.com/papers/SpotlightMacForensicsSlides.pdf - * - https://en.wikipedia.org/wiki/Spotlight_(Apple) - * - https://github.com/libyal/dtformats/blob/main/documentation/Apple%20Spotlight%20store%20database%20file%20format.asciidoc - */ -export interface Spotlight { - /**Inode number associated with indexed file */ - inode: bigint; - /**Parent inode associated with indexed file */ - parent_inode: bigint; - /**Flags associated with indexed entry */ - flags: number; - /**Store ID associated with indexed entry */ - store_id: number; - /**Last time Spotlight entry was updated */ - last_updated: string; - /**Array of properties associated with the entry */ - values: Record; - /**Location of the Spotlight database that was parsed */ - directory: string; -} - -/** - * Properties associated with the indexed entry - */ -interface SpotlightProperties { - /** - * Attribute type associated with the entry. - * Possible options are: - * - AttrBool - * - AttrUnknown - * - AttrVariableSizeInt - * - AttrUnknown2 - * - AttrUnknown3 - * - AttrUnknown4 - * - AttrVariableSizeInt2 - * - AttrVariableSizeIntMultiValue - * - AttrByte - * - AttrFloat32 - * - AttrFloat64 - * - AttrString - * - AttrDate (ISO RFC 3339 format) - * - AttrBinary - * - AttrList - * - Unknown - */ - attribute: DataAttribute; - /** - * Data associated with the property. Type can be determined based on the attribute. - * **Important** `value` may also be an array containing data associated with the `attribute` - */ - value: unknown; -} - -/** - * Metadata needed to parse the Spotlight database - */ -export interface StoreMeta { - /**Metadata associated with Spotlight properties */ - meta: SpotlightMeta; - /**Blocks to iterator through when parsing Spotlight */ - blocks: number[]; -} - -/** - * Metadata associated with the Spotlight properties - */ -export interface SpotlightMeta { - /**Property Metadata */ - props: Record; - /**Categories associated with some properties */ - categories: Record; - /**Index used to parse Spotlight block data */ - indexes1: Record; - /**Index used to parse Spotlight block data */ - indexes2: Record; -} - -/** - * Properties associated with the Spotlight entry - */ -interface DataProperties { - /**Attribute associated with property */ - attribute: DataAttribute; - /**Property type value */ - prop_type: number; - /**Name of property */ - name: string; -} - -/** - * All possible Attribute types - */ -enum DataAttribute { - /**Boolean value */ - AttrBool = "AttrBool", - /**This attribute is undocumented */ - AttrUnknown = "AttrUnknown", - /**A value of variable size */ - AttrVariableSizeInt = "AttrVariableSizeInt", - /**This attribute is undocumented */ - AttrUnknown2 = "AttrUnknown2", - /**This attribute is undocumented */ - AttrUnknown3 = "AttrUnknown3", - /**This attribute is undocumented */ - AttrUnknown4 = "AttrUnknown4", - /**A value of variable size */ - AttrVariableSizeInt2 = "AttrVariableSizeInt2", - /**A value of variable size (it may be in an array) */ - AttrVariableSizeIntMultiValue = "AttrVariableSizeIntMultiValue", - /**A value with a size of one byte */ - AttrByte = "AttrByte", - /**A 32-bit float value */ - AttrFloat32 = "AttrFloat32", - /**A 64-bit float value */ - AttrFloat64 = "AttrFloat64", - /**A string value */ - AttrString = "AttrString", - /**A date value in ISO RFC 3339 */ - AttrDate = "AttrDate", - /**Base64 encoded binary value. (Depending on the property this may actually be a normal string value (similar to `AttrString`) */ - AttrBinary = "AttrBinary", - /**An array of values */ - AttrList = "AttrList", - /**Artemis failed to determine the attribute */ - Unknown = "Unknown", -} +/** + * macOS `Spotlight` is an indexing service for tracking files and content. + * The `Spotlight` database can contain a huge amount of metadata associated with the indexed content such as: + * - Timestamps + * - Partial file content + * - File type and much more + * + * References: + * - https://forensicsandsecurity.com/papers/SpotlightMacForensicsSlides.pdf + * - https://en.wikipedia.org/wiki/Spotlight_(Apple) + * - https://github.com/libyal/dtformats/blob/main/documentation/Apple%20Spotlight%20store%20database%20file%20format.asciidoc + */ +export interface Spotlight { + /**Inode number associated with indexed file */ + inode: bigint; + /**Parent inode associated with indexed file */ + parent_inode: bigint; + /**Flags associated with indexed entry */ + flags: number; + /**Store ID associated with indexed entry */ + store_id: number; + /**Last time Spotlight entry was updated */ + last_updated: string; + /**Array of properties associated with the entry */ + values: Record; + /**Location of the Spotlight database that was parsed */ + evidence: string; +} + +/** + * Properties associated with the indexed entry + */ +interface SpotlightProperties { + /** + * Attribute type associated with the entry. + * Possible options are: + * - AttrBool + * - AttrUnknown + * - AttrVariableSizeInt + * - AttrUnknown2 + * - AttrUnknown3 + * - AttrUnknown4 + * - AttrVariableSizeInt2 + * - AttrVariableSizeIntMultiValue + * - AttrByte + * - AttrFloat32 + * - AttrFloat64 + * - AttrString + * - AttrDate (ISO RFC 3339 format) + * - AttrBinary + * - AttrList + * - Unknown + */ + attribute: DataAttribute; + /** + * Data associated with the property. Type can be determined based on the attribute. + * **Important** `value` may also be an array containing data associated with the `attribute` + */ + value: unknown; +} + +/** + * Metadata needed to parse the Spotlight database + */ +export interface StoreMeta { + /**Metadata associated with Spotlight properties */ + meta: SpotlightMeta; + /**Blocks to iterator through when parsing Spotlight */ + blocks: number[]; +} + +/** + * Metadata associated with the Spotlight properties + */ +export interface SpotlightMeta { + /**Property Metadata */ + props: Record; + /**Categories associated with some properties */ + categories: Record; + /**Index used to parse Spotlight block data */ + indexes1: Record; + /**Index used to parse Spotlight block data */ + indexes2: Record; +} + +/** + * Properties associated with the Spotlight entry + */ +interface DataProperties { + /**Attribute associated with property */ + attribute: DataAttribute; + /**Property type value */ + prop_type: number; + /**Name of property */ + name: string; +} + +/** + * All possible Attribute types + */ +enum DataAttribute { + /**Boolean value */ + AttrBool = "AttrBool", + /**This attribute is undocumented */ + AttrUnknown = "AttrUnknown", + /**A value of variable size */ + AttrVariableSizeInt = "AttrVariableSizeInt", + /**This attribute is undocumented */ + AttrUnknown2 = "AttrUnknown2", + /**This attribute is undocumented */ + AttrUnknown3 = "AttrUnknown3", + /**This attribute is undocumented */ + AttrUnknown4 = "AttrUnknown4", + /**A value of variable size */ + AttrVariableSizeInt2 = "AttrVariableSizeInt2", + /**A value of variable size (it may be in an array) */ + AttrVariableSizeIntMultiValue = "AttrVariableSizeIntMultiValue", + /**A value with a size of one byte */ + AttrByte = "AttrByte", + /**A 32-bit float value */ + AttrFloat32 = "AttrFloat32", + /**A 64-bit float value */ + AttrFloat64 = "AttrFloat64", + /**A string value */ + AttrString = "AttrString", + /**A date value in ISO RFC 3339 */ + AttrDate = "AttrDate", + /**Base64 encoded binary value. (Depending on the property this may actually be a normal string value (similar to `AttrString`) */ + AttrBinary = "AttrBinary", + /**An array of values */ + AttrList = "AttrList", + /**Artemis failed to determine the attribute */ + Unknown = "Unknown", +} diff --git a/types/macos/sqlite/tcc.ts b/types/macos/sqlite/tcc.ts index 22898151..fe634294 100644 --- a/types/macos/sqlite/tcc.ts +++ b/types/macos/sqlite/tcc.ts @@ -1,10 +1,5 @@ import { SingleRequirement } from "../codesigning"; -export interface TccValues { - db_path: string; - data: TccData[]; -} - export interface TccData { service: string; client: string; @@ -23,6 +18,12 @@ export interface TccData { pid_version: number | null | undefined; boot_uuid: string; last_reminded: string; + evidence: string; + message: string; + datetime: string; + timestamp_desc: "Last Modified"; + artifact: "TCC Database"; + data_type: "macos:sqlite:tcc:entry"; } export enum Reason { diff --git a/types/macos/systemstats.ts b/types/macos/systemstats.ts index 4450eaf2..857c03d6 100644 --- a/types/macos/systemstats.ts +++ b/types/macos/systemstats.ts @@ -1,6 +1,6 @@ export interface SystemStats { /**Path stats file */ - source_path: string; + evidence: string; /**Stats Filename */ source_file: string /**Stats version */ diff --git a/types/macos/unifiedlogs.ts b/types/macos/unifiedlogs.ts index 338198bf..abe3ef95 100644 --- a/types/macos/unifiedlogs.ts +++ b/types/macos/unifiedlogs.ts @@ -1,52 +1,54 @@ -/** - * macOS `unifiedlogs` are the primary files associated with logging with system activity. - * They are stored in a binary format at `/var/db/diagnostics/`. - * - * References: - * - https://eclecticlight.co/2018/03/19/macos-unified-log-1-why-what-and-how/ - * - https://www.mandiant.com/resources/blog/reviewing-macos-unified-logs - * - https://www.crowdstrike.com/blog/how-to-leverage-apple-unified-log-for-incident-response/ - */ -export interface UnifiedLog { - /**Subsystem used by the log entry */ - subsystem: string; - /**Library associated with the log entry */ - library: string; - /**Log entry category */ - category: string; - /**Process ID associated with log entry */ - pid: number; - /**Effective user ID associated with log entry */ - euid: number; - /**Thread ID associated with log entry */ - thread_id: number; - /**Activity ID associated with log entry */ - activity_id: number; - /**UUID of library associated with the log entry */ - library_uuid: string; - /**UNIXEPOCH timestamp of log entry in nanoseconds */ - time: bigint; - /**ISO RFC 3339 timestamp with nanosecond precision */ - timestamp: string; - /**Log entry event type */ - event_type: string; - /**Log entry log type */ - log_type: string; - /**Process associated with log entry */ - process: string; - /**UUID of process associated with log entry */ - process_uuid: string; - /**Raw string message associated with log entry*/ - raw_message: string; - /**Boot UUID associated with log entry */ - boot_uuid: string; - /**Timezone associated with log entry */ - timezone_name: string; - /**Strings associated with the log entry */ - message_entries: Record; - /** - * Resolved message entry associated log entry. - * Merge of `raw_message` and `message_entries` - */ - message: string; -} +/** + * macOS `unifiedlogs` are the primary files associated with logging with system activity. + * They are stored in a binary format at `/var/db/diagnostics/`. + * + * References: + * - https://eclecticlight.co/2018/03/19/macos-unified-log-1-why-what-and-how/ + * - https://www.mandiant.com/resources/blog/reviewing-macos-unified-logs + * - https://www.crowdstrike.com/blog/how-to-leverage-apple-unified-log-for-incident-response/ + */ +export interface UnifiedLog { + /**Subsystem used by the log entry */ + subsystem: string; + /**Library associated with the log entry */ + library: string; + /**Log entry category */ + category: string; + /**Process ID associated with log entry */ + pid: number; + /**Effective user ID associated with log entry */ + euid: number; + /**Thread ID associated with log entry */ + thread_id: number; + /**Activity ID associated with log entry */ + activity_id: number; + /**UUID of library associated with the log entry */ + library_uuid: string; + /**UNIXEPOCH timestamp of log entry in nanoseconds */ + time: bigint; + /**ISO RFC 3339 timestamp with nanosecond precision */ + timestamp: string; + /**Log entry event type */ + event_type: string; + /**Log entry log type */ + log_type: string; + /**Process associated with log entry */ + process: string; + /**UUID of process associated with log entry */ + process_uuid: string; + /**Raw string message associated with log entry*/ + raw_message: string; + /**Boot UUID associated with log entry */ + boot_uuid: string; + /**Timezone associated with log entry */ + timezone_name: string; + /**Strings associated with the log entry */ + message_entries: Record; + /** + * Resolved message entry associated log entry. + * Merge of `raw_message` and `message_entries` + */ + message: string; + /**Path to the UnifiedLog file */ + evidence: string; +} diff --git a/types/unix/shellhistory.ts b/types/unix/shellhistory.ts index 5cbf9541..04f31413 100644 --- a/types/unix/shellhistory.ts +++ b/types/unix/shellhistory.ts @@ -12,7 +12,7 @@ export interface BashHistory { /**Line number */ line: number; /**Path to `.bash_history` file */ - path: string; + evidence: string; } /** @@ -29,5 +29,14 @@ export interface ZshHistory { /**Line number */ line: number; /**Path to `.zsh_history` file */ - path: string; + evidence: string; +} + +export interface AshHistory { + /**Line entry */ + history: string; + /**Line number */ + line: number; + /**Path to `.zsh_history` file */ + evidence: string; } diff --git a/types/windows/amcache.ts b/types/windows/amcache.ts index 06097d80..8f72caf1 100644 --- a/types/windows/amcache.ts +++ b/types/windows/amcache.ts @@ -1,48 +1,48 @@ -/** - * Amcache stores metadata related to execution of Windows applications. - * Data is stored in the Amcache.hve Registry file. It also contains other metadata such as OS, hardware, and application info - * - * References: - * - https://github.com/libyal/dtformats/blob/main/documentation/AMCache%20file%20(AMCache.hve)%20format.asciidoc - * - https://www.ssi.gouv.fr/uploads/2019/01/anssi-coriin_2019-analysis_amcache.pdf - */ -export interface Amcache { - /**Last modified time for Registry key */ - last_modified: string; - /**Path to application */ - path: string; - /**Name of application */ - name: string; - /**Original name of application from PE metadata */ - original_name: string; - /**Version of application from PE metadata */ - version: string; - /**Executable type and arch information */ - binary_type: string; - /**Application product version from PE metadata */ - product_version: string; - /**Application product name from PE metadata */ - product_name: string; - /**Application language */ - language: string; - /**Application file ID. This is also the SHA1 hash */ - file_id: string; - /**Application linking timestamp as MM/DD/YYYY HH:mm:ss*/ - link_date: string; - /**Hash of application path */ - path_hash: string; - /**Program ID associated with the application */ - program_id: string; - /**Size of application */ - size: string; - /**Application publisher from PE metadata */ - publisher: string; - /**Application Update Sequence Number (USN) */ - usn: string; - /**SHA1 hash of the first ~31MBs of the application */ - sha1: string; - /**Path in the Amcache.hve file */ - reg_path: string; - /**Path to the Amcache.hve file */ - source_path: string; -} +/** + * Amcache stores metadata related to execution of Windows applications. + * Data is stored in the Amcache.hve Registry file. It also contains other metadata such as OS, hardware, and application info + * + * References: + * - https://github.com/libyal/dtformats/blob/main/documentation/AMCache%20file%20(AMCache.hve)%20format.asciidoc + * - https://www.ssi.gouv.fr/uploads/2019/01/anssi-coriin_2019-analysis_amcache.pdf + */ +export interface Amcache { + /**Last modified time for Registry key */ + last_modified: string; + /**Path to application */ + path: string; + /**Name of application */ + name: string; + /**Original name of application from PE metadata */ + original_name: string; + /**Version of application from PE metadata */ + version: string; + /**Executable type and arch information */ + binary_type: string; + /**Application product version from PE metadata */ + product_version: string; + /**Application product name from PE metadata */ + product_name: string; + /**Application language */ + language: string; + /**Application file ID. This is also the SHA1 hash */ + file_id: string; + /**Application linking timestamp as MM/DD/YYYY HH:mm:ss*/ + link_date: string; + /**Hash of application path */ + path_hash: string; + /**Program ID associated with the application */ + program_id: string; + /**Size of application */ + size: string; + /**Application publisher from PE metadata */ + publisher: string; + /**Application Update Sequence Number (USN) */ + usn: string; + /**SHA1 hash of the first ~31MBs of the application */ + sha1: string; + /**Path in the Amcache.hve file */ + reg_path: string; + /**Path to the Amcache.hve file */ + evidence: string; +} diff --git a/types/windows/appcrash.ts b/types/windows/appcrash.ts new file mode 100644 index 00000000..95a9daf7 --- /dev/null +++ b/types/windows/appcrash.ts @@ -0,0 +1,12 @@ +export interface AppCrash { + timestamp_desc: "Application Crash"; + artifact: "AppCrash File"; + data_type: "windows:app:crash:entry"; + evidence: string; + message: string; + path: string; + datetime: string; + report_id: string; + report_type: number; + application_name: string; +} \ No newline at end of file diff --git a/types/windows/bits.ts b/types/windows/bits.ts index 5d703aa0..4c36205e 100644 --- a/types/windows/bits.ts +++ b/types/windows/bits.ts @@ -1,79 +1,81 @@ -import { AccessControl } from "./acls"; - -/** - * Windows Background Intelligent Transfer Service (`BITS`) is a service that allows applications and users to register jobs to upload/download files. - * It is commonly used by applications to download updates. In addition, Windows Updates are downloaded through BITS. - * Starting on Windows 10 BITS data is stored in an ESE database. - * Pre-Win10 it is stored in a proprietary binary format. - * - * References: - * - https://ss64.com/nt/bitsadmin.html - * - https://en.wikipedia.org/wiki/Background_Intelligent_Transfer_Service - * - https://www.mandiant.com/resources/blog/attacker-use-of-windows-background-intelligent-transfer-service - */ -export interface BitsInfo { - /**ID for the Job */ - job_id: string; - /**ID for the File */ - file_id: string; - /**SID associated with the Job */ - owner_sid: string; - /**Timestamp when the Job was created */ - created: string; - /**Timestamp when the Job was modified */ - modified: string; - /**Timestamp when the Job was completed */ - completed: string; - /**Timestamp when the Job was expired */ - expiration: string; - /**Number of bytes downloaded */ - bytes_downloaded: number | bigint | string; - /**Number of bytes transferred */ - bytes_transferred: number | bigint | string; - /**Name associated with Job */ - job_name: string; - /**Description associated with Job */ - job_description: string; - /**Commands associated with Job */ - job_command: string; - /**Arguments associated with Job */ - job_arguments: string; - /**Error count with the Job */ - error_count: number; - /**BITS Job type */ - job_type: string; - /**BITS Job state */ - job_state: string; - /**Job priority */ - priority: string; - /**BITS Job flags */ - flags: string; - /**HTTP Method associated with Job */ - http_method: string; - /**Full file path associated with Job */ - full_path: string; - /**Filename associated with Job */ - filename: string; - /**Target file path associated with Job */ - target_path: string; - /**Volume path associated with the file */ - volume: string; - /**URL associated with the Job */ - url: string; - /**If the BITS info was carved */ - carved: boolean; - /**Transient error count with Job */ - transient_error_count: number; - /**Permissions associated with the Job */ - acls: AccessControl[]; - /**Job timeout in seconds */ - timeout: number; - /**Job retry delay in seconds */ - retry_delay: number; - /**Additional SIDs associated with Job */ - additional_sids: string[]; - /**Drive associated with the BITS Job */ - drive: string; - /**Temporary file path for the file download */ - tmp_fullpath: string; -} +import { AccessControl } from "./acls"; + +/** + * Windows Background Intelligent Transfer Service (`BITS`) is a service that allows applications and users to register jobs to upload/download files. + * It is commonly used by applications to download updates. In addition, Windows Updates are downloaded through BITS. + * Starting on Windows 10 BITS data is stored in an ESE database. + * Pre-Win10 it is stored in a proprietary binary format. + * + * References: + * - https://ss64.com/nt/bitsadmin.html + * - https://en.wikipedia.org/wiki/Background_Intelligent_Transfer_Service + * - https://www.mandiant.com/resources/blog/attacker-use-of-windows-background-intelligent-transfer-service + */ +export interface BitsInfo { + /**ID for the Job */ + job_id: string; + /**ID for the File */ + file_id: string; + /**SID associated with the Job */ + owner_sid: string; + /**Timestamp when the Job was created */ + created: string; + /**Timestamp when the Job was modified */ + modified: string; + /**Timestamp when the Job was completed */ + completed: string; + /**Timestamp when the Job was expired */ + expiration: string; + /**Number of bytes downloaded */ + bytes_downloaded: number | bigint | string; + /**Number of bytes transferred */ + bytes_transferred: number | bigint | string; + /**Name associated with Job */ + job_name: string; + /**Description associated with Job */ + job_description: string; + /**Commands associated with Job */ + job_command: string; + /**Arguments associated with Job */ + job_arguments: string; + /**Error count with the Job */ + error_count: number; + /**BITS Job type */ + job_type: string; + /**BITS Job state */ + job_state: string; + /**Job priority */ + priority: string; + /**BITS Job flags */ + flags: string; + /**HTTP Method associated with Job */ + http_method: string; + /**Full file path associated with Job */ + full_path: string; + /**Filename associated with Job */ + filename: string; + /**Target file path associated with Job */ + target_path: string; + /**Volume path associated with the file */ + volume: string; + /**URL associated with the Job */ + url: string; + /**If the BITS info was carved */ + carved: boolean; + /**Transient error count with Job */ + transient_error_count: number; + /**Permissions associated with the Job */ + acls: AccessControl[]; + /**Job timeout in seconds */ + timeout: number; + /**Job retry delay in seconds */ + retry_delay: number; + /**Additional SIDs associated with Job */ + additional_sids: string[]; + /**Drive associated with the BITS Job */ + drive: string; + /**Temporary file path for the file download */ + tmp_fullpath: string; + /**Path to the BITS database */ + evidence: string; +} diff --git a/types/windows/ese/updates.ts b/types/windows/ese/updates.ts index dcfbc4af..f37b2d96 100644 --- a/types/windows/ese/updates.ts +++ b/types/windows/ese/updates.ts @@ -15,7 +15,7 @@ export interface UpdateHistory { export interface UpdateHistoryV2 { provider_id: string; - update_id:string; + update_id: string; time: string | null; title: string | null; description: string | null; diff --git a/types/windows/eventlogs.ts b/types/windows/eventlogs.ts index eb0cfe9f..7772ec0a 100644 --- a/types/windows/eventlogs.ts +++ b/types/windows/eventlogs.ts @@ -1,244 +1,246 @@ -/** - * Windows `EventLogs` are the primary files associated with logging with system activity. - * They are stored in a binary format, typically at C:\Windows\System32\winevt\Logs - */ -export interface EventLogRecord { - /**Event record number */ - event_record_id: number; - /**Timestamp of eventlog message */ - timestamp: string; - /** - * JSON object representation of the Eventlog message - * Depending on the log the JSON object may have different types of keys - * Example entry: - * ``` - * "data": { - * "Event": { - * "#attributes": { - * "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" - * }, - * "System": { - * "Provider": { - * "#attributes": { - * "Name": "Microsoft-Windows-Bits-Client", - * "Guid": "EF1CC15B-46C1-414E-BB95-E76B077BD51E" - * } - * }, - * "EventID": 3, - * "Version": 3, - * "Level": 4, - * "Task": 0, - * "Opcode": 0, - * "Keywords": "0x4000000000000000", - * "TimeCreated": { - * "#attributes": { - * "SystemTime": "2022-10-31T04:24:19.946430Z" - * } - * }, - * "EventRecordID": 2, - * "Correlation": null, - * "Execution": { - * "#attributes": { - * "ProcessID": 1332, - * "ThreadID": 780 - * } - * }, - * "Channel": "Microsoft-Windows-Bits-Client/Operational", - * "Computer": "DESKTOP-EIS938N", - * "Security": { - * "#attributes": { - * "UserID": "S-1-5-18" - * } - * } - * }, - * "EventData": { - * "jobTitle": "Font Download", - * "jobId": "174718A5-F630-43D9-B378-728240ECE152", - * "jobOwner": "NT AUTHORITY\\LOCAL SERVICE", - * "processPath": "C:\\Windows\\System32\\svchost.exe", - * "processId": 1456, - * "ClientProcessStartKey": 844424930132016 - * } - * } - * } - * ``` - */ - data: Record; -} - -/** - * Parsed EventLog files with template strings included. A flatten version of `EventLogRecord` - */ -export interface EventLogMessage { - /**Full EventLog message rendered using both the template string and evtx data */ - message: string; - /**The raw template string */ - template_message: string; - /**The raw evtx event data. Ex: `EventData` or `UserData` */ - raw_event_data: Record; - /**EventID for the entry */ - event_id: bigint; - /**Qualifier ID for the entry */ - qualifier: bigint; - /**Version number for the entry */ - version: bigint; - /**GUID associated with the provider */ - guid: string; - /**EventLog provider name */ - provider: string; - /**Alternative provider name */ - source_name: string; - /**EventLog record number */ - record_id: bigint; - /**Task number for entry */ - task: bigint; - /**EventLog level value */ - level: string; - /**Opcode number for entry */ - opcode: bigint; - /**Keywords value for entry. Is a hex number */ - keywords: string; - /**Generated timestamp for entry */ - generated: string; - /**System timestamp for entry */ - system_time: string; - /**Activity ID for entry if available */ - activity_id: string; - /**Process ID for entry if available */ - process_id: bigint; - /**Thread ID for entry if available */ - thread_id: bigint; - /**SID value for entry if available */ - sid: string; - /**Channel name for entry */ - channel: string; - /**Hostname of system */ - computer: string; - /**Full path the evtx file that was parsed */ - source_file: string; - /**Full path to the PE file that was used to obtain the template string */ - message_file: string; - /**Full path to the PE file containing parameters for the entry */ - parameter_file: string; - /**Source Registry file used to get provider info */ - registry_file: string; - /**Registry key path to the provider info */ - registry_path: string; -} - -/**A complex structure that represents the parsed EventLog template strings. Can be used to create a full EventLog message */ -export interface TemplateStrings { - /**Object containing Template provider info. Key is the provider name or a GUID */ - providers: Record; - /**Object containing Template string info. Key is the path to the PE file */ - templates: Record; -} - -/**Information about the EventLog provider */ -export interface Provider { - /**Source Registry file used to get provider info */ - registry_file_path: string; - /**Registry key path to the provider info */ - registry_path: string; - /**Name of provider. Might be a GUID */ - name: string; - /**Array of PE files that point to the template strings*/ - message_file: string[]; - /**Array of PE files that point to the parameter values for the provider. So far this seems to always be one file */ - parameter_file: string[]; -} - -/**The parsed WEVT_TEMPLATE info */ -export interface TemplateInfo { - /** Path to PE file */ - path: string; - /**Internal data used by artemis when parsing the PE data. These arrays will always be empty */ - resource_data: { - mui_data: number[]; - wevt_data: number[]; - message_data: number[]; - /** Path to PE file */ - path: string; - }; - /**Info related to the template message string. Key is the `MessageTable` ID */ - message_table: Record; - /**Extreme details on the EventLog provider template. Key is a GUID */ - wevt_template: Record; -} - -export interface MessageTable { - id: bigint; - size: number; - flags: string; - message: string; -} - -export interface WevtTemplate { - offset: number; - element_offsets: number[]; - channels: TemplateData[]; - keywords: TemplateData[]; - opcodes: TemplateData[]; - levels: TemplateData[]; - maps: Maps[]; - tasks: TemplateData[]; - /**Information related to a EventID associated with a provider. Key is combination of the `Definition` ID and version. Ex: The key: `100_0` would be EventID 100 version 0 */ - definitions: Record; -} - -export interface Definition { - /**EventID. Makes up the `definitions` key along with the `version` */ - id: number; - /**Version number of the definition object. Makes up the `definitions` key along with the `id` */ - version: number; - level: number; - opcode: number; - task: number; - keywords: number; - message_id: bigint; - temp_offset: number; - template: Template | null; - opcode_offset: number; - level_offset: number; - task_offset: number; -} - -export interface Template { - template_id: string; - event_data_type: string; - elements: { - token: string; - token_number: number; - dependency_id: number; - size: number; - attribute_list: { - attribute_token: string; - attribute_token_number: number; - value: string; - value_token: string; - value_token_number: number; - name: string; - input_type: string; - substitution: string; - substitution_id: number; - }[] | null; - element_name: string; - input_type: string; - substitution: string; - substitution_id: 0; - }[]; - guid: string; -} - -export interface TemplateData { - message_id: bigint; - id: number; - value: string; - /**Only `Tasks` have this value */ - guid: string | undefined; -} - -export interface Maps { - name: string; - data: Record; -} +/** + * Windows `EventLogs` are the primary files associated with logging with system activity. + * They are stored in a binary format, typically at C:\Windows\System32\winevt\Logs + */ +export interface EventLogRecord { + /**Event record number */ + event_record_id: number; + /**Timestamp of eventlog message */ + timestamp: string; + /** + * JSON object representation of the Eventlog message + * Depending on the log the JSON object may have different types of keys + * Example entry: + * ``` + * "data": { + * "Event": { + * "#attributes": { + * "xmlns": "http://schemas.microsoft.com/win/2004/08/events/event" + * }, + * "System": { + * "Provider": { + * "#attributes": { + * "Name": "Microsoft-Windows-Bits-Client", + * "Guid": "EF1CC15B-46C1-414E-BB95-E76B077BD51E" + * } + * }, + * "EventID": 3, + * "Version": 3, + * "Level": 4, + * "Task": 0, + * "Opcode": 0, + * "Keywords": "0x4000000000000000", + * "TimeCreated": { + * "#attributes": { + * "SystemTime": "2022-10-31T04:24:19.946430Z" + * } + * }, + * "EventRecordID": 2, + * "Correlation": null, + * "Execution": { + * "#attributes": { + * "ProcessID": 1332, + * "ThreadID": 780 + * } + * }, + * "Channel": "Microsoft-Windows-Bits-Client/Operational", + * "Computer": "DESKTOP-EIS938N", + * "Security": { + * "#attributes": { + * "UserID": "S-1-5-18" + * } + * } + * }, + * "EventData": { + * "jobTitle": "Font Download", + * "jobId": "174718A5-F630-43D9-B378-728240ECE152", + * "jobOwner": "NT AUTHORITY\\LOCAL SERVICE", + * "processPath": "C:\\Windows\\System32\\svchost.exe", + * "processId": 1456, + * "ClientProcessStartKey": 844424930132016 + * } + * } + * } + * ``` + */ + data: Record; + /**Path to the EventLog file */ + evidence: string; +} + +/** + * Parsed EventLog files with template strings included. A flatten version of `EventLogRecord` + */ +export interface EventLogMessage { + /**Full EventLog message rendered using both the template string and evtx data */ + message: string; + /**The raw template string */ + template_message: string; + /**The raw evtx event data. Ex: `EventData` or `UserData` */ + raw_event_data: Record; + /**EventID for the entry */ + event_id: bigint; + /**Qualifier ID for the entry */ + qualifier: bigint; + /**Version number for the entry */ + version: bigint; + /**GUID associated with the provider */ + guid: string; + /**EventLog provider name */ + provider: string; + /**Alternative provider name */ + source_name: string; + /**EventLog record number */ + record_id: bigint; + /**Task number for entry */ + task: bigint; + /**EventLog level value */ + level: string; + /**Opcode number for entry */ + opcode: bigint; + /**Keywords value for entry. Is a hex number */ + keywords: string; + /**Generated timestamp for entry */ + generated: string; + /**System timestamp for entry */ + system_time: string; + /**Activity ID for entry if available */ + activity_id: string; + /**Process ID for entry if available */ + process_id: bigint; + /**Thread ID for entry if available */ + thread_id: bigint; + /**SID value for entry if available */ + sid: string; + /**Channel name for entry */ + channel: string; + /**Hostname of system */ + computer: string; + /**Full path the evtx file that was parsed */ + evidence: string; + /**Full path to the PE file that was used to obtain the template string */ + message_file: string; + /**Full path to the PE file containing parameters for the entry */ + parameter_file: string; + /**Source Registry file used to get provider info */ + registry_file: string; + /**Registry key path to the provider info */ + registry_path: string; +} + +/**A complex structure that represents the parsed EventLog template strings. Can be used to create a full EventLog message */ +export interface TemplateStrings { + /**Object containing Template provider info. Key is the provider name or a GUID */ + providers: Record; + /**Object containing Template string info. Key is the path to the PE file */ + templates: Record; +} + +/**Information about the EventLog provider */ +export interface Provider { + /**Source Registry file used to get provider info */ + registry_file_path: string; + /**Registry key path to the provider info */ + registry_path: string; + /**Name of provider. Might be a GUID */ + name: string; + /**Array of PE files that point to the template strings*/ + message_file: string[]; + /**Array of PE files that point to the parameter values for the provider. So far this seems to always be one file */ + parameter_file: string[]; +} + +/**The parsed WEVT_TEMPLATE info */ +export interface TemplateInfo { + /** Path to PE file */ + path: string; + /**Internal data used by artemis when parsing the PE data. These arrays will always be empty */ + resource_data: { + mui_data: number[]; + wevt_data: number[]; + message_data: number[]; + /** Path to PE file */ + path: string; + }; + /**Info related to the template message string. Key is the `MessageTable` ID */ + message_table: Record; + /**Extreme details on the EventLog provider template. Key is a GUID */ + wevt_template: Record; +} + +export interface MessageTable { + id: bigint; + size: number; + flags: string; + message: string; +} + +export interface WevtTemplate { + offset: number; + element_offsets: number[]; + channels: TemplateData[]; + keywords: TemplateData[]; + opcodes: TemplateData[]; + levels: TemplateData[]; + maps: Maps[]; + tasks: TemplateData[]; + /**Information related to a EventID associated with a provider. Key is combination of the `Definition` ID and version. Ex: The key: `100_0` would be EventID 100 version 0 */ + definitions: Record; +} + +export interface Definition { + /**EventID. Makes up the `definitions` key along with the `version` */ + id: number; + /**Version number of the definition object. Makes up the `definitions` key along with the `id` */ + version: number; + level: number; + opcode: number; + task: number; + keywords: number; + message_id: bigint; + temp_offset: number; + template: Template | null; + opcode_offset: number; + level_offset: number; + task_offset: number; +} + +export interface Template { + template_id: string; + event_data_type: string; + elements: { + token: string; + token_number: number; + dependency_id: number; + size: number; + attribute_list: { + attribute_token: string; + attribute_token_number: number; + value: string; + value_token: string; + value_token_number: number; + name: string; + input_type: string; + substitution: string; + substitution_id: number; + }[] | null; + element_name: string; + input_type: string; + substitution: string; + substitution_id: 0; + }[]; + guid: string; +} + +export interface TemplateData { + message_id: bigint; + id: number; + value: string; + /**Only `Tasks` have this value */ + guid: string | undefined; +} + +export interface Maps { + name: string; + data: Record; +} diff --git a/types/windows/eventlogs/bits.ts b/types/windows/eventlogs/bits.ts new file mode 100644 index 00000000..5973bf4f --- /dev/null +++ b/types/windows/eventlogs/bits.ts @@ -0,0 +1,144 @@ +export interface BitsEvent { + status: BitsState; + evidence: string; + job_id: string; + process: string; + pid: number; + user: string; + title: string; + message: string; + datetime: string; + file_count: number; + provider: string; + event_id: number; + bits_event_time: string; + activity_id: string; + thread_id: number; + bytes_transferred: number; + timestamp_desc: "BITS Job Created" | "BITS Job Completed"; + artifact: "BITS EventLog"; + data_type: "windows:eventlogs:bits:entry"; +} + +export enum BitsState { + Completed = "Complete", + Created = "Created", +} + +export interface RawBitsCreate { + event_record_id: number, + timestamp: string, + data: { + Event: { + "#attributes": { + xmlns: string + }, + System: { + Provider: { + "#attributes": { + Name: string, + Guid: string + } + }, + EventID: number, + Version: number, + Level: number, + Task: number, + Opcode: number, + Keywords: string, + TimeCreated: { + "#attributes": { + SystemTime: string, + } + }, + EventRecordID: number, + Correlation: { + "#attributes": { + ActivityID: string, + } + }, + Execution: { + "#attributes": { + ProcessID: number, + ThreadID: number + } + }, + Channel: string, + Computer: string, + Security: { + "#attributes": { + UserID: string + } + } + }, + EventData: { + jobTitle: string, + jobId: string, + jobOwner: string, + processPath: string, + processId: number, + ClientProcessStartKey: number + } + } + }, + evidence: string; +} + +export interface RawBitsComplete { + event_record_id: number, + timestamp: string, + data: { + Event: { + "#attributes": { + xmlns: string + }, + System: { + Provider: { + "#attributes": { + Name: string, + Guid: string + } + }, + EventID: number, + Version: number, + Level: number, + Task: number, + Opcode: number, + Keywords: string, + TimeCreated: { + "#attributes": { + SystemTime: string, + } + }, + EventRecordID: number, + Correlation: { + "#attributes": { + ActivityID: string, + } + }, + Execution: { + "#attributes": { + ProcessID: number, + ThreadID: number + } + }, + Channel: string, + Computer: string, + Security: { + "#attributes": { + UserID: string + } + } + }, + EventData: { + jobTitle: string, + jobId: string, + jobOwner: string, + user: string, + fileCount: number, + bytesTransferred: number, + } + } + }, + evidence: string; +} \ No newline at end of file diff --git a/types/windows/eventlogs/crash.ts b/types/windows/eventlogs/crash.ts new file mode 100644 index 00000000..a2884655 --- /dev/null +++ b/types/windows/eventlogs/crash.ts @@ -0,0 +1,79 @@ +export interface CrashEvent { + evidence: string; + pid: number; + path: string; + application_start: string; + crash_time: string; + crash_time_from_start: number; + hostname: string; + provider: string; + guid: string; + channel: string; + sid: string; + trigger: string; + message: string; + datetime: string; + timestamp_desc: "Application Crash"; + artifact: "Crash EventLog"; + data_type: "windows:eventlogs:crash:entry"; +} + +export interface RawCrash { + event_record_id: number, + timestamp: string, + data: { + Event: { + "#attributes": { + xmlns: string + }, + System: { + Provider: { + "#attributes": { + Name: string, + Guid: string + } + }, + EventID: number, + Version: number, + Level: number, + Task: number, + Opcode: number, + Keywords: string, + TimeCreated: { + "#attributes": { + SystemTime: string, + } + }, + EventRecordID: number, + Correlation: { + "#attributes": { + ActivityID: string, + } + }, + Execution: { + "#attributes": { + ProcessID: number, + ThreadID: number + } + }, + Channel: string, + Computer: string, + Security: { + "#attributes": { + UserID: string + } + } + }, + EventData: { + "#attributes": { + Name: string + } + ProcessId: string, + ModuleName: string, + StartTime: bigint, + CrashTimeFromStart: string, + } + } + }, + evidence: string; +} \ No newline at end of file diff --git a/types/windows/eventlogs/logons.ts b/types/windows/eventlogs/logons.ts index 3d5d97ab..e7d11369 100644 --- a/types/windows/eventlogs/logons.ts +++ b/types/windows/eventlogs/logons.ts @@ -1,148 +1,222 @@ -export interface LogonsWindows { - logon_type: LogonType; - sid: string; - account_name: string; - account_domain: string; - logon_id: string; - logon_process: string; - authentication_package: string; - source_ip: string; - source_workstation: string; - eventlog_generated: string; - message: string; - datetime: string; - timestamp_desc: "Account Logon" | "Account Logoff"; - artifact: "Logon EventLog" | "Logoff EventLog"; - data_type: "windows:eventlogs:logon:entry" | "windows:eventlogs:logoff:entry"; -} - -export interface Raw4624Logons { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: number; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - }; - Channel: string; - Computer: string; - Security: unknown; - }; - EventData: { - SubjectUserSid: string; - SubjectUserName: string; - SubjectDomainName: string; - SubjectLogonId: string; - TargetUserSid: string; - TargetUserName: string; - TargetDomainName: string; - TargetLogonId: string; - LogonType: number; - LogonProcessName: string; - AuthenticationPackageName: string; - WorkstationName: string; - LogonGuid: string; - TransmittedServices: string; - LmPackageName: string; - KeyLength: number; - ProcessId: string; - ProcessName: string; - IpAddress: string; - IpPort: string; - ImpersonationLevel: string; - RestrictedAdminMode: string; - TargetOutboundUserName: string; - TargetOutboundDomainName: string; - VirtualAccount: string; - TargetLinkedLogonId: string; - ElevatedToken: string; - }; - }; - }; -} - -export interface Raw4634Logoffs { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: number; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: unknown; - }; - Execution: { - "#attributes": { - ProcessID: number; - ThreadID: number; - }; - }; - Channel: string; - Computer: string; - Security: unknown; - EventData: { - TargetUserSid: string; - TargetUserName: string; - TargetDomainName: string; - TargetLogonId: string; - LogonType: number; - }; - }; - }; -} - -export enum LogonType { - Network = "Network", - Interactive = "Interactive", - Batch = "Batch", - Service = "Service", - Unlock = "Unlock", - NetworkCleartext = "NetworkCleartext", - NewCredentials = "NewCredentials", - RemoteInteractive = "RemoteInteractive", - CacheInteractive = "CacheInteractive", - Unknown = "Unknown", -} +export interface LogonsWindows { + logon_type: LogonType; + sid: string; + account_name: string; + account_domain: string; + logon_id: string; + logon_process: string; + authentication_package: string; + source_ip: string; + activity_id: string; + computer: string; + channel: string; + provider: string; + provider_guid: string; + source_workstation: string; + eventlog_generated: string; + message: string; + datetime: string; + timestamp_desc: "Account Logon" | "Account Logoff" | "Account Failed Logon"; + artifact: "Logon EventLog" | "Logoff EventLog" | "Failed Logon EventLog"; + data_type: "windows:eventlogs:logon:entry" | "windows:eventlogs:logoff:entry" | "windows:eventlogs:logon:failed:entry"; + evidence: string; +} + +export interface Raw4624Logons { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: number; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + EventData: { + SubjectUserSid: string; + SubjectUserName: string; + SubjectDomainName: string; + SubjectLogonId: string; + TargetUserSid: string; + TargetUserName: string; + TargetDomainName: string; + TargetLogonId: string; + LogonType: number; + LogonProcessName: string; + AuthenticationPackageName: string; + WorkstationName: string; + LogonGuid: string; + TransmittedServices: string; + LmPackageName: string; + KeyLength: number; + ProcessId: string; + ProcessName: string; + IpAddress: string; + IpPort: string; + ImpersonationLevel: string; + RestrictedAdminMode: string; + TargetOutboundUserName: string; + TargetOutboundDomainName: string; + VirtualAccount: string; + TargetLinkedLogonId: string; + ElevatedToken: string; + }; + }; + }; +} + +export interface Raw4634Logoffs { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: number; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: unknown; + Execution: { + "#attributes": { + ProcessID: number; + ThreadID: number; + }; + }; + Channel: string; + Computer: string; + Security: unknown; + }; + EventData: { + TargetUserSid: string; + TargetUserName: string; + TargetDomainName: string; + TargetLogonId: string; + LogonType: number; + }; + }; + }; +} + +export interface Raw4625FailedLogons { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: number; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + Execution: { + "#attributes": { + ProcessID: number; + ThreadID: number; + } + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + }; + Channel: string; + Computer: string; + Security: unknown; + }; + EventData: { + SubjectUserSid: string; + SubjectUserName: string; + SubjectDomainName: string; + SubjectLogonId: string; + TargetUserSid: string; + TargetUserName: string; + TargetDomainName: string; + LogonType: number; + LogonProcessName: string; + AuthenticationPackageName: string; + WorkstationName: string; + TransmittedServices: string; + LmPackageName: string; + KeyLength: number; + ProcessId: string; + ProcessName: string; + IpAddress: string; + IpPort: string; + Status: string; + FailureReason: string; + }; + }; + }; +} + +export enum LogonType { + Network = "Network", + Interactive = "Interactive", + Batch = "Batch", + Service = "Service", + Unlock = "Unlock", + NetworkCleartext = "NetworkCleartext", + NewCredentials = "NewCredentials", + RemoteInteractive = "RemoteInteractive", + CacheInteractive = "CacheInteractive", + Unknown = "Unknown", +} diff --git a/types/windows/eventlogs/msi.ts b/types/windows/eventlogs/msi.ts new file mode 100644 index 00000000..42f2059a --- /dev/null +++ b/types/windows/eventlogs/msi.ts @@ -0,0 +1,78 @@ +export interface MsiInstalled { + name: string; + language: number; + version: string; + manufacturer: string; + installation_status: number; + hostname: string; + sid: string; + pid: number; + thread_id: number; + message: string; + datetime: string; + timestamp_desc: "MSI Installed"; + artifact: "EventLog MSI Installed 1033"; + data_type: "windows:eventlog:application:msi"; + evidence: string; +} + +export interface RawMsiInstalled { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + + System: { + Provider: { + "#attributes": { + Name: string; + }; + } | string; + EventID: { + "#attributes": { + Qualifiers: number; + }; + "#text": number; + } | number; + Version: number; + Level: number; + Task: number; + Keywords: string; + Opcode: number; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + Execution: { + "#attributes": { + ProcessID: number; + ThreadID: number; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + }; + Channel: string; + Computer: string; + Security: { + "#attributes": { + "UserID": string; + }; + }; + }; + EventData: { + Data: { + "#text": string[]; + }; + Binary: string; + }; + }; + }; +} \ No newline at end of file diff --git a/types/windows/eventlogs/processtree.ts b/types/windows/eventlogs/processtree.ts index 3b0e9f4f..b7f274b8 100644 --- a/types/windows/eventlogs/processtree.ts +++ b/types/windows/eventlogs/processtree.ts @@ -1,96 +1,96 @@ -/** - * Object representing a reassembled process tree from 4688 Security EventLog - * This object is Timesketch compatible. It does **not** need to be timeline - */ -export interface EventLogProcessTree { - pid: number; - parent_pid: number; - process_name: string; - process_path: string; - parent_name: string; - parent_path: string; - user: string; - sid: string; - domain: string; - commandline: string; - /**Complete process tree for a process */ - message: string; - datetime: string; - timestamp_desc: "EventLog Generated"; - artifact: "EventLogs Process Tree"; - evtx_path: string; - data_type: "windows:eventlogs:proctree:entry"; - record: number; - logon_id: number; -} - -export interface RawProcess { - event_record_id: number; - timestamp: string; - data: { - Event: { - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - } - } - EventID: number; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - } - } - Correlation: { - "#attributes": { - ActivityID: string; - } - } - Execution: { - "#attributes": { - ProcessID: number; - ThreadID: number; - } - } - Channel: string; - Computer: string; - Security: { - "#attributes": { - UserID: string; - } | null - } - } - EventData: { - SubjectUserSid: string; - SubjectUserName: string; - SubjectDomainName: string; - SubjectLogonId: string; - NewProcessId: string; - NewProcessName: string; - TokenElevationType: string; - ProcessId: string; - CommandLine?: string; - TargetUserSid?: string; - TargetUserName?: string; - TargetDomainName?: string; - TargetLogonId?: string; - ParentProcessName?: string; - MandatoryLabel?: string; - } - } - } -} -export interface ProcTracker { - login_id: number; - pid: number, - parent: number, - name: string, - parent_name: string, - record: number; +/** + * Object representing a reassembled process tree from 4688 Security EventLog + * This object is Timesketch compatible. It does **not** need to be timeline + */ +export interface EventLogProcessTree { + pid: number; + parent_pid: number; + process_name: string; + process_path: string; + parent_name: string; + parent_path: string; + user: string; + sid: string; + domain: string; + commandline: string; + /**Complete process tree for a process */ + message: string; + datetime: string; + timestamp_desc: "EventLog Generated"; + artifact: "EventLogs Process Tree"; + data_type: "windows:eventlogs:proctree:entry"; + record: number; + logon_id: number; + evidence: string; +} + +export interface RawProcess { + event_record_id: number; + timestamp: string; + data: { + Event: { + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + } + } + EventID: number; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + } + } + Correlation: { + "#attributes": { + ActivityID: string; + } + } + Execution: { + "#attributes": { + ProcessID: number; + ThreadID: number; + } + } + Channel: string; + Computer: string; + Security: { + "#attributes": { + UserID: string; + } | null + } + } + EventData: { + SubjectUserSid: string; + SubjectUserName: string; + SubjectDomainName: string; + SubjectLogonId: string; + NewProcessId: string; + NewProcessName: string; + TokenElevationType: string; + ProcessId: string; + CommandLine?: string; + TargetUserSid?: string; + TargetUserName?: string; + TargetDomainName?: string; + TargetLogonId?: string; + ParentProcessName?: string; + MandatoryLabel?: string; + } + } + } +} +export interface ProcTracker { + login_id: number; + pid: number, + parent: number, + name: string, + parent_name: string, + record: number; } \ No newline at end of file diff --git a/types/windows/eventlogs/rdp.ts b/types/windows/eventlogs/rdp.ts index 25061baf..9522d525 100644 --- a/types/windows/eventlogs/rdp.ts +++ b/types/windows/eventlogs/rdp.ts @@ -1,326 +1,327 @@ -/** - * Parsed RDP events - */ -export interface RdpActivity { - /**Session ID associated with RDP */ - session_id: number; - /**User associated RDP logon */ - user: string; - /**Domain associated with the user */ - domain: string; - /**Account associated with the RDP logon */ - account: string; - source_ip: string; - /**Hostname associated with evtx file */ - hostname: string; - /**Activity ID associated with EventLog entry */ - activity_id: string; - message: string; - datetime: string; - timestamp_desc: "RDP Logon" | "RDP Reconnect" | "RDP Logoff" | "RDP Disconnect"; - artifact: "RDP EventLog"; - data_type: "windows:eventlogs:rdp:entry"; -} - -export interface Raw21Logons { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: 21; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - } | null; - Channel: string; - Computer: string; - Security: unknown; - }; - UserData: { - EventXML: { - User: string; - SessionID: number; - Address: string; - "#attributes": { - xmlns: string; - }; - } - - }; - }; - }; -} - -export interface Raw25Reconnect { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: 25; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - } | null; - Channel: string; - Computer: string; - Security: unknown; - }; - UserData: { - EventXML: { - User: string; - SessionID: number; - Address: string; - "#attributes": { - xmlns: string; - }; - } - - }; - }; - }; -} - -export interface Raw23Logoff { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: 23; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - } | null; - Channel: string; - Computer: string; - Security: unknown; - }; - UserData: { - EventXML: { - User: string; - SessionID: number; - "#attributes": { - xmlns: string; - }; - } - - }; - }; - }; -} - -export interface Raw24Disconnect { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: 24; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - } | null; - Channel: string; - Computer: string; - Security: unknown; - }; - UserData: { - EventXML: { - SessionID: number; - User: string; - Address: string; - "#attributes": { - xmlns: string; - }; - } - - }; - }; - }; -} - -export interface Raw41SessionStart { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: 41; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - } | null; - Channel: string; - Computer: string; - Security: unknown; - }; - UserData: { - EventXML: { - SessionID: number; - User: string; - "#attributes": { - xmlns: string; - }; - } - - }; - }; - }; -} - -export interface Raw42SessionEnd { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - }; - }; - EventID: 42; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - } | null; - Channel: string; - Computer: string; - Security: unknown; - }; - UserData: { - EventXML: { - SessionID: number; - User: string; - "#attributes": { - xmlns: string; - }; - } - - }; - }; - }; +/** + * Parsed RDP events + */ +export interface RdpActivity { + /**Session ID associated with RDP */ + session_id: number; + /**User associated RDP logon */ + user: string; + /**Domain associated with the user */ + domain: string; + /**Account associated with the RDP logon */ + account: string; + source_ip: string; + /**Hostname associated with evtx file */ + hostname: string; + /**Activity ID associated with EventLog entry */ + activity_id: string; + message: string; + datetime: string; + timestamp_desc: "RDP Logon" | "RDP Reconnect" | "RDP Logoff" | "RDP Disconnect"; + artifact: "RDP EventLog"; + data_type: "windows:eventlogs:rdp:entry"; + evidence: string; +} + +export interface Raw21Logons { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: 21; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + UserData: { + EventXML: { + User: string; + SessionID: number; + Address: string; + "#attributes": { + xmlns: string; + }; + } + + }; + }; + }; +} + +export interface Raw25Reconnect { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: 25; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + UserData: { + EventXML: { + User: string; + SessionID: number; + Address: string; + "#attributes": { + xmlns: string; + }; + } + + }; + }; + }; +} + +export interface Raw23Logoff { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: 23; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + UserData: { + EventXML: { + User: string; + SessionID: number; + "#attributes": { + xmlns: string; + }; + } + + }; + }; + }; +} + +export interface Raw24Disconnect { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: 24; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + UserData: { + EventXML: { + SessionID: number; + User: string; + Address: string; + "#attributes": { + xmlns: string; + }; + } + + }; + }; + }; +} + +export interface Raw41SessionStart { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: 41; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + UserData: { + EventXML: { + SessionID: number; + User: string; + "#attributes": { + xmlns: string; + }; + } + + }; + }; + }; +} + +export interface Raw42SessionEnd { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + }; + }; + EventID: 42; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + } | null; + Channel: string; + Computer: string; + Security: unknown; + }; + UserData: { + EventXML: { + SessionID: number; + User: string; + "#attributes": { + xmlns: string; + }; + } + + }; + }; + }; } \ No newline at end of file diff --git a/types/windows/eventlogs/scriptblocks.ts b/types/windows/eventlogs/scriptblocks.ts index 18496be4..55f8d169 100644 --- a/types/windows/eventlogs/scriptblocks.ts +++ b/types/windows/eventlogs/scriptblocks.ts @@ -1,80 +1,80 @@ -/** - * Object representing a sync log entry. - * This object is Timesketch compatible. It does **not** need to be timeline - */ -export interface Scriptblock { - total_parts: number; - message: string; - datetime: string; - timestamp_desc: "EventLog Entry Created"; - data_type: "windows:eventlogs:powershell:scriptblock:entry"; - artifact: "Windows PowerShell Scriptblock"; - id: string; - source_file: string; - path: string; - script_length: number; - has_signature_block: boolean; - has_copyright_string: boolean; - hostname: string; - version: number; - activity_id: string; - channel: string; - user_id: string; - process_id: number; - threat_id: number; - system_time: string; - created_time: string; -} - -export interface RawBlock { - event_record_id: number; - timestamp: string; - data: { - Event: { - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - } - } - EventID: number; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - } - } - Correlation: { - "#attributes": { - ActivityID: string; - } - } - Execution: { - "#attributes": { - ProcessID: number; - ThreadID: number; - } - } - Channel: string; - Computer: string; - Security: { - "#attributes": { - UserID: string; - } - } - } - EventData: { - MessageNumber: number; - MessageTotal: number; - ScriptBlockText: string; - ScriptBlockId: string; - Path: string; - } - } - } +/** + * Object representing a sync log entry. + * This object is Timesketch compatible. It does **not** need to be timeline + */ +export interface Scriptblock { + total_parts: number; + message: string; + datetime: string; + timestamp_desc: "EventLog Entry Created"; + data_type: "windows:eventlogs:powershell:scriptblock:entry"; + artifact: "Windows PowerShell Scriptblock"; + id: string; + path: string; + script_length: number; + has_signature_block: boolean; + has_copyright_string: boolean; + hostname: string; + version: number; + activity_id: string; + channel: string; + user_id: string; + process_id: number; + threat_id: number; + system_time: string; + created_time: string; + evidence: string; +} + +export interface RawBlock { + event_record_id: number; + timestamp: string; + data: { + Event: { + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + } + } + EventID: number; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + } + } + Correlation: { + "#attributes": { + ActivityID: string; + } + } + Execution: { + "#attributes": { + ProcessID: number; + ThreadID: number; + } + } + Channel: string; + Computer: string; + Security: { + "#attributes": { + UserID: string; + } + } + } + EventData: { + MessageNumber: number; + MessageTotal: number; + ScriptBlockText: string; + ScriptBlockId: string; + Path: string; + } + } + } } \ No newline at end of file diff --git a/types/windows/eventlogs/services.ts b/types/windows/eventlogs/services.ts index ca1450a6..f998f045 100644 --- a/types/windows/eventlogs/services.ts +++ b/types/windows/eventlogs/services.ts @@ -1,80 +1,81 @@ -export interface ServiceInstalls { - name: string; - image_path: string; - service_type: string; - start_type: string; - account: string; - hostname: string; - timestamp: string; - process_id: number; - thread_id: number; - sid: string; - message: string; - datetime: string; - timestamp_desc: "Windows Service Installed"; - artifact: "EventLog Service 7045"; - data_type: "windows:eventlog:system:service"; -} - -export interface RawService7045 { - event_record_id: number; - timestamp: string; - data: { - Event: { - "#attributes": { - xmlns: string; - }; - System: { - Provider: { - "#attributes": { - Name: string; - Guid: string; - EventSourceName: string; - }; - }; - EventID: number | { - "#attributes": { - Qualifiers: number; - }; - "#text": number; - }; - Version: number; - Level: number; - Task: number; - Opcode: number; - Keywords: string; - TimeCreated: { - "#attributes": { - SystemTime: string; - }; - }; - EventRecordID: number; - Correlation: { - "#attributes": { - ActivityID: string; - }; - }; - Execution: { - "#attributes": { - ProcessID: number; - ThreadID: number; - }; - }; - Channel: string; - Computer: string; - Security: { - "#attributes": { - UserID: string; - }; - }; - }; - EventData: { - ServiceName: string; - ImagePath: string; - ServiceType: string; - StartType: string; - AccountName: string; - }; - }; - }; -} +export interface ServiceInstalls { + name: string; + image_path: string; + service_type: string; + start_type: string; + account: string; + hostname: string; + timestamp: string; + process_id: number; + thread_id: number; + sid: string; + message: string; + datetime: string; + timestamp_desc: "Windows Service Installed"; + artifact: "EventLog Service 7045"; + data_type: "windows:eventlog:system:service"; + evidence: string; +} + +export interface RawService7045 { + event_record_id: number; + timestamp: string; + data: { + Event: { + "#attributes": { + xmlns: string; + }; + System: { + Provider: { + "#attributes": { + Name: string; + Guid: string; + EventSourceName: string; + }; + }; + EventID: number | { + "#attributes": { + Qualifiers: number; + }; + "#text": number; + }; + Version: number; + Level: number; + Task: number; + Opcode: number; + Keywords: string; + TimeCreated: { + "#attributes": { + SystemTime: string; + }; + }; + EventRecordID: number; + Correlation: { + "#attributes": { + ActivityID: string; + }; + }; + Execution: { + "#attributes": { + ProcessID: number; + ThreadID: number; + }; + }; + Channel: string; + Computer: string; + Security: { + "#attributes": { + UserID: string; + }; + }; + }; + EventData: { + ServiceName: string; + ImagePath: string; + ServiceType: string; + StartType: string; + AccountName: string; + }; + }; + }; +} diff --git a/types/windows/eventlogs/velociraptor.ts b/types/windows/eventlogs/velociraptor.ts new file mode 100644 index 00000000..fe58e92a --- /dev/null +++ b/types/windows/eventlogs/velociraptor.ts @@ -0,0 +1,66 @@ +export interface VeloExecution { + evidence: string; + pid: number; + message: string; + datetime: string; + provider: string; + event_id: number; + thread_id: number; + event: string; + path: string; + arguments: string[]; + timestamp_desc: "Velociraptor Executed"; + artifact: "Velociraptor EventLog"; + data_type: "windows:eventlogs:velociraptor:entry"; +} + +export interface VeloRaw { + event_record_id: number, + timestamp: string, + data: { + Event: { + "#attributes": { + xmlns: string + }, + System: { + Provider: { + "#attributes": { + Name: string, + Guid: string + } + }, + EventID: { + "#attributes": { + Qualifiers: number + }, + "#text": number, + }, + Version: number, + Level: number, + Task: number, + Opcode: number, + Keywords: string, + TimeCreated: { + "#attributes": { + SystemTime: string, + } + }, + EventRecordID: number, + Execution: { + "#attributes": { + ProcessID: number, + ThreadID: number + } + }, + Channel: string, + Computer: string, + }, + EventData: { + Data: { + "#text": string, + }, + } + } + }, + evidence: string; +} diff --git a/types/windows/jumplists.ts b/types/windows/jumplists.ts index cce12d4a..701e9748 100644 --- a/types/windows/jumplists.ts +++ b/types/windows/jumplists.ts @@ -1,58 +1,58 @@ -import { Shortcut } from "./shortcuts"; - -/** - * Windows `Jumplists` files track opened files via applications in the Taskbar or Start Menu - * Jumplists contain `lnk` data and therefore can show evidence of file interaction. - * There are two (2) types of Jumplist files: - * - * - Custom - Files that are pinned to Taskbar applications - * - Automatic - Files that are not pinned to Taskbar applications - * - * References: - * - https://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidoc - * - https://binaryforay.blogspot.com/2016/02/jump-lists-in-depth-understand-format.html - */ -export interface Jumplists { - /**Path to Jumplist file */ - source: string; - /**Jupmlist type. Custom or Automatic */ - jumplist_type: string; - /**Application ID for Jumplist file */ - app_id: string; - /**Metadata associated with Jumplist entry */ - jumplist_metadata: DestEntries; - /**Shortcut information for Jumplist entry */ - lnk_info: Shortcut; -} - -/** - * Metadata associated with Jumplist entry - */ -interface DestEntries { - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_file_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_file_id: string; - /**Hostname associated with Jumplist entry */ - hostname: string; - /**Jumplist entry number */ - entry: number; - /**Modified timestamp of Jumplist entry */ - modified: string; - /**Status if Jumplist entry is pinned. `Pinned` or `NotPinned` */ - pin_status: string; - /**Path associated with Jumplist entry */ - path: string; -} +import { Shortcut } from "./shortcuts"; + +/** + * Windows `Jumplists` files track opened files via applications in the Taskbar or Start Menu + * Jumplists contain `lnk` data and therefore can show evidence of file interaction. + * There are two (2) types of Jumplist files: + * + * - Custom - Files that are pinned to Taskbar applications + * - Automatic - Files that are not pinned to Taskbar applications + * + * References: + * - https://github.com/libyal/dtformats/blob/main/documentation/Jump%20lists%20format.asciidoc + * - https://binaryforay.blogspot.com/2016/02/jump-lists-in-depth-understand-format.html + */ +export interface Jumplists { + /**Path to Jumplist file */ + evidence: string; + /**Jupmlist type. Custom or Automatic */ + jumplist_type: string; + /**Application ID for Jumplist file */ + app_id: string; + /**Metadata associated with Jumplist entry */ + jumplist_metadata: DestEntries; + /**Shortcut information for Jumplist entry */ + lnk_info: Shortcut; +} + +/** + * Metadata associated with Jumplist entry + */ +interface DestEntries { + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_file_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_file_id: string; + /**Hostname associated with Jumplist entry */ + hostname: string; + /**Jumplist entry number */ + entry: number; + /**Modified timestamp of Jumplist entry */ + modified: string; + /**Status if Jumplist entry is pinned. `Pinned` or `NotPinned` */ + pin_status: string; + /**Path associated with Jumplist entry */ + path: string; +} diff --git a/types/windows/outlook.ts b/types/windows/outlook.ts index 71ff81d0..c8257c83 100644 --- a/types/windows/outlook.ts +++ b/types/windows/outlook.ts @@ -1,239 +1,239 @@ -/** - * Metadata about parsed Outlook messagse - */ -export interface OutlookMessage { - /**Body of the message. Can be either plaintext, html, rtf */ - body: string; - /**Subject line */ - subject: string; - /**From line */ - from: string; - /**Who received the email */ - recipient: string; - /**Delivered timestamp */ - delivered: string; - /**Other recipients */ - recipients: string[]; - /**Attachment message metadata */ - attachments: AttachmentInfo[] | undefined; - /**Full path to the folder containing the message */ - folder_path: string; - /**Source path to the OST file */ - source_file: string; - /**Yara rule that matched if Yara scanning was enabled */ - yara_hits: string[] | undefined; -} - -/** - * Metadata about an Outlook folder - */ -export interface FolderInfo { - /**Name of the folder */ - name: string; - /**Created timestamp of folder */ - created: string; - /**Modified timestamp of folder */ - modified: string; - /**Array of properties for the folder */ - properties: PropertyContext[]; - /**Array of sub-folders */ - subfolders: SubFolder[]; - /**Array of sub-folders pointing to more metadata */ - associated_content: SubFolder[]; - /**Count of subfolders */ - subfolder_count: number; - /**Count of messages in the folder */ - message_count: number; - /**Internal structure containing information required to extract messages */ - messages_table: TableInfo; -} - -/** - * Additional metadata associated with a folder - */ -export interface FolderMetadata { - message_class: string; - created: string; - properties: PropertyContext[]; -} - -export interface NameEntry { - reference: number; - entry_type: number; - value: unknown; - index: number; - guid: string; - name_type: NameType; -} - -export enum NameType { - String = "String", - Guid = "Guid", -} - -/** - * Preview of sub-folder metadata - */ -export interface SubFolder { - /**Name of the sub-folder */ - name: string; - /**Folder ID */ - node: number; -} - -/** - * Primary source of metadata for OST data - */ -export interface PropertyContext { - /**Property name(s) */ - name: string[]; - /**Type of property. Such as string, int, GUID, date, etc */ - property_type: string; - /**Property ID */ - prop_id: number; - /**Reference to the property in the OST */ - reference: number; - /**Property value. Value will depend on type. Ex: string, int, array, int, etc */ - value: unknown; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface TableInfo { - block_data: number[][]; - block_descriptors: Record; - rows: number[]; - columns: TableRows[]; - include_cols: string[]; - row_size: number; - map_offset: number; - node: HeapNode; - total_rows: number; - has_branch: TableBranchInfo | undefined; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface DescriptorData { - node_level: string; - node: Node; - block_subnode_id: number; - block_data_id: number; - block_descriptor_id: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface Node { - node_id: string; - node_id_num: number; - node: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface TableRows { - value: undefined; - column: ColumnDescriptor; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface ColumnDescriptor { - property_type: string; - id: number; - property_name: string[]; - offset: number; - size: number; - index: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface HeapNode { - node: string; - index: number; - block_index: number; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface TableBranchInfo { - node: HeapNode; - rows_info: RowsInfo; -} - -/** - * Internal structure containing information required to extract messages - */ -export interface RowsInfo { - row_end: number; - count: number; -} - -/** - * Metadata associated with email messages - */ -export interface MessageDetails { - /**Array of properties for the message */ - props: PropertyContext[]; - /**Message body. Can be plaintext, html, or rtf */ - body: string; - /**Subject line for message */ - subject: string; - /**From address of the email */ - from: string; - /**Recipient of the email */ - recipient: string; - /**Delivered timestamp */ - delivered: string; - /**Preview of attachments in the email */ - attachments: AttachmentInfo[]; - /**Array of other recipients who also received the email*/ - recipients: TableRows[][]; -} - -/** - * Preview of the attachment metadata - */ -export interface AttachmentInfo { - /**Name of the attachment */ - name: string; - /**Size of the attachment */ - size: number; - /**How the attachment was attached */ - method: string; - /**Node ID for the attachment */ - node: number; - /**Block ID for the attachment */ - block_id: number; - /**Descriptor ID for the attachment */ - descriptor_id: number; -} - -/** - * Metadata about the attachment - */ -export interface Attachment { - /**Base64 string containing attachment*/ - data: string; - /**Size of the attachment */ - size: bigint; - /**Name of the attachment */ - name: string; - /**Mime type of the attachment */ - mime: string; - /**Attachment extension (includes the dot) */ - extension: string; - /**How the attachment was attached */ - method: string; - /**Array of properties for the attachment */ - props: PropertyContext[]; -} +/** + * Metadata about parsed Outlook messagse + */ +export interface OutlookMessage { + /**Body of the message. Can be either plaintext, html, rtf */ + body: string; + /**Subject line */ + subject: string; + /**From line */ + from: string; + /**Who received the email */ + recipient: string; + /**Delivered timestamp */ + delivered: string; + /**Other recipients */ + recipients: string[]; + /**Attachment message metadata */ + attachments: AttachmentInfo[] | undefined; + /**Full path to the folder containing the message */ + folder_path: string; + /**Source path to the OST file */ + evidence: string; + /**Yara rule that matched if Yara scanning was enabled */ + yara_hits: string[] | undefined; +} + +/** + * Metadata about an Outlook folder + */ +export interface FolderInfo { + /**Name of the folder */ + name: string; + /**Created timestamp of folder */ + created: string; + /**Modified timestamp of folder */ + modified: string; + /**Array of properties for the folder */ + properties: PropertyContext[]; + /**Array of sub-folders */ + subfolders: SubFolder[]; + /**Array of sub-folders pointing to more metadata */ + associated_content: SubFolder[]; + /**Count of subfolders */ + subfolder_count: number; + /**Count of messages in the folder */ + message_count: number; + /**Internal structure containing information required to extract messages */ + messages_table: TableInfo; +} + +/** + * Additional metadata associated with a folder + */ +export interface FolderMetadata { + message_class: string; + created: string; + properties: PropertyContext[]; +} + +export interface NameEntry { + reference: number; + entry_type: number; + value: unknown; + index: number; + guid: string; + name_type: NameType; +} + +export enum NameType { + String = "String", + Guid = "Guid", +} + +/** + * Preview of sub-folder metadata + */ +export interface SubFolder { + /**Name of the sub-folder */ + name: string; + /**Folder ID */ + node: number; +} + +/** + * Primary source of metadata for OST data + */ +export interface PropertyContext { + /**Property name(s) */ + name: string[]; + /**Type of property. Such as string, int, GUID, date, etc */ + property_type: string; + /**Property ID */ + prop_id: number; + /**Reference to the property in the OST */ + reference: number; + /**Property value. Value will depend on type. Ex: string, int, array, int, etc */ + value: unknown; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface TableInfo { + block_data: number[][]; + block_descriptors: Record; + rows: number[]; + columns: TableRows[]; + include_cols: string[]; + row_size: number; + map_offset: number; + node: HeapNode; + total_rows: number; + has_branch: TableBranchInfo | undefined; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface DescriptorData { + node_level: string; + node: Node; + block_subnode_id: number; + block_data_id: number; + block_descriptor_id: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface Node { + node_id: string; + node_id_num: number; + node: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface TableRows { + value: undefined; + column: ColumnDescriptor; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface ColumnDescriptor { + property_type: string; + id: number; + property_name: string[]; + offset: number; + size: number; + index: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface HeapNode { + node: string; + index: number; + block_index: number; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface TableBranchInfo { + node: HeapNode; + rows_info: RowsInfo; +} + +/** + * Internal structure containing information required to extract messages + */ +export interface RowsInfo { + row_end: number; + count: number; +} + +/** + * Metadata associated with email messages + */ +export interface MessageDetails { + /**Array of properties for the message */ + props: PropertyContext[]; + /**Message body. Can be plaintext, html, or rtf */ + body: string; + /**Subject line for message */ + subject: string; + /**From address of the email */ + from: string; + /**Recipient of the email */ + recipient: string; + /**Delivered timestamp */ + delivered: string; + /**Preview of attachments in the email */ + attachments: AttachmentInfo[]; + /**Array of other recipients who also received the email*/ + recipients: TableRows[][]; +} + +/** + * Preview of the attachment metadata + */ +export interface AttachmentInfo { + /**Name of the attachment */ + name: string; + /**Size of the attachment */ + size: number; + /**How the attachment was attached */ + method: string; + /**Node ID for the attachment */ + node: number; + /**Block ID for the attachment */ + block_id: number; + /**Descriptor ID for the attachment */ + descriptor_id: number; +} + +/** + * Metadata about the attachment + */ +export interface Attachment { + /**Base64 string containing attachment*/ + data: string; + /**Size of the attachment */ + size: bigint; + /**Name of the attachment */ + name: string; + /**Mime type of the attachment */ + mime: string; + /**Attachment extension (includes the dot) */ + extension: string; + /**How the attachment was attached */ + method: string; + /**Array of properties for the attachment */ + props: PropertyContext[]; +} diff --git a/types/windows/pca.ts b/types/windows/pca.ts index 0cdace9c..273c9225 100644 --- a/types/windows/pca.ts +++ b/types/windows/pca.ts @@ -1,22 +1,22 @@ -export interface ProgramCompatibilityAssist { - last_run: string; - path: string; - run_status: number; - file_description: string; - vendor: string; - version: string; - program_id: string; - exit_message: string; - pca_type: PcaType; - message: string; - datetime: string; - source: string; - timestamp_desc: "Last Run"; - artifact: "Windows Program Compatibility Assist"; - data_type: "windows:pca:entry"; -} - -export enum PcaType { - AppLaunch = "AppLaunch", - General = "General", +export interface ProgramCompatibilityAssist { + last_run: string; + path: string; + run_status: number; + file_description: string; + vendor: string; + version: string; + program_id: string; + exit_message: string; + pca_type: PcaType; + message: string; + datetime: string; + timestamp_desc: "Last Run"; + artifact: "Windows Program Compatibility Assist"; + data_type: "windows:pca:entry"; + evidence: string; +} + +export enum PcaType { + AppLaunch = "AppLaunch", + General = "General", } \ No newline at end of file diff --git a/types/windows/powershell.ts b/types/windows/powershell.ts index 4d163340..8846f50a 100644 --- a/types/windows/powershell.ts +++ b/types/windows/powershell.ts @@ -1,13 +1,13 @@ -export interface History { - line: string; - path: string; - created: string; - modified: string; - accessed: string; - changed: string; - message: string; - datetime: string; - timestamp_desc: "PowerShell History Modified"; - artifact: "PowerShell History"; - data_type: "application:powershell:entry"; -} +export interface History { + line: string; + created: string; + modified: string; + accessed: string; + changed: string; + message: string; + datetime: string; + timestamp_desc: "PowerShell History Modified"; + artifact: "PowerShell History"; + data_type: "application:powershell:entry"; + evidence: string; +} diff --git a/types/windows/prefetch.ts b/types/windows/prefetch.ts index 3fba9c5e..e16b8e13 100644 --- a/types/windows/prefetch.ts +++ b/types/windows/prefetch.ts @@ -1,37 +1,37 @@ -/** - * Windows `Prefetch` data tracks execution of applications on Windows Workstations. - * `Prefetch` is disabled on Windows Servers and may be disabled on systems with SSDs. - * - * References: - * - https://github.com/libyal/libscca/blob/main/documentation/Windows%20Prefetch%20File%20(PF)%20format.asciidoc - */ -export interface Prefetch { - /**Path to prefetch file */ - path: string; - /**Name of executed file */ - filename: string; - /**Prefetch hash */ - hash: string; - /**Most recent execution timestamp */ - last_run_time: string; - /**Array of up to eight (8) execution timestamps */ - all_run_times: string[]; - /**Number of executions */ - run_count: number; - /**Size of executed file */ - size: number; - /**Array of volume serial numbers associated with accessed files/directories */ - volume_serial: string[]; - /**Array of volume creation timestamps associated with accessed files/directories */ - volume_creation: string[]; - /**Array of volumes associated accessed files/directories */ - volume_path: string[]; - /**Number of files accessed by executed file */ - accessed_file_count: number; - /**Number of directories accessed by executed file */ - accessed_directories_count: number; - /**Array of accessed files by executed file */ - accessed_files: string[]; - /**Array of accessed directories by executed file */ - accessed_directories: string[]; -} +/** + * Windows `Prefetch` data tracks execution of applications on Windows Workstations. + * `Prefetch` is disabled on Windows Servers and may be disabled on systems with SSDs. + * + * References: + * - https://github.com/libyal/libscca/blob/main/documentation/Windows%20Prefetch%20File%20(PF)%20format.asciidoc + */ +export interface Prefetch { + /**Path to prefetch file */ + evidence: string; + /**Name of executed file */ + filename: string; + /**Prefetch hash */ + hash: string; + /**Most recent execution timestamp */ + last_run_time: string; + /**Array of up to eight (8) execution timestamps */ + all_run_times: string[]; + /**Number of executions */ + run_count: number; + /**Size of executed file */ + size: number; + /**Array of volume serial numbers associated with accessed files/directories */ + volume_serial: string[]; + /**Array of volume creation timestamps associated with accessed files/directories */ + volume_creation: string[]; + /**Array of volumes associated accessed files/directories */ + volume_path: string[]; + /**Number of files accessed by executed file */ + accessed_file_count: number; + /**Number of directories accessed by executed file */ + accessed_directories_count: number; + /**Array of accessed files by executed file */ + accessed_files: string[]; + /**Array of accessed directories by executed file */ + accessed_directories: string[]; +} diff --git a/types/windows/recyclebin.ts b/types/windows/recyclebin.ts index be4b7d05..3b60be17 100644 --- a/types/windows/recyclebin.ts +++ b/types/windows/recyclebin.ts @@ -1,24 +1,24 @@ -/** - * Windows `Recycle Bin` files contain metadata about "deleted" files - * Currently artemis parses the `$I Recycle Bin` files using the std API - * - * References: - * - https://github.com/libyal/dtformats/blob/main/documentation/Windows%20Recycle.Bin%20file%20formats.asciidoc - * - https://cybersecurity.att.com/blogs/security-essentials/digital-dumpster-diving-exploring-the-intricacies-of-recycle-bin-forensics - */ -export interface RecycleBin { - /**Size of deleted file */ - size: number; - /**Deleted timestamp of file */ - deleted: string; - /**Name of deleted file */ - filename: string; - /**Full path to the deleted file */ - full_path: string; - /**Directory associated with deleted file */ - directory: string; - /**SID associated with the deleted file */ - sid: string; - /**Path to the file in the Recycle Bin */ - recycle_path: string; -} +/** + * Windows `Recycle Bin` files contain metadata about "deleted" files + * Currently artemis parses the `$I Recycle Bin` files using the std API + * + * References: + * - https://github.com/libyal/dtformats/blob/main/documentation/Windows%20Recycle.Bin%20file%20formats.asciidoc + * - https://cybersecurity.att.com/blogs/security-essentials/digital-dumpster-diving-exploring-the-intricacies-of-recycle-bin-forensics + */ +export interface RecycleBin { + /**Size of deleted file */ + size: number; + /**Deleted timestamp of file */ + deleted: string; + /**Name of deleted file */ + filename: string; + /**Full path to the deleted file */ + full_path: string; + /**Directory associated with deleted file */ + directory: string; + /**SID associated with the deleted file */ + sid: string; + /**Path to the file in the Recycle Bin */ + evidence: string; +} diff --git a/types/windows/registry.ts b/types/windows/registry.ts index de31b0e2..8c885379 100644 --- a/types/windows/registry.ts +++ b/types/windows/registry.ts @@ -1,83 +1,83 @@ -import { Descriptor } from "./acls"; - -/** - * Windows `Registry` is a collection of binary files that store Windows configuration settings and OS information. - * There are multiple `Registry` files on a system such as: - * - `SYSTEM` - * - `SOFTWARE` - * - `SAM` - * - `SECURITY` - * - `NTUSER.DAT -- One per user` - * - `UsrClass.dat -- One per user` - * - * References: - * - https://github.com/libyal/libregf - * - https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md - */ - -/** - * Interface representing the parsed `Registry` structure - */ -export interface Registry { - /** - * Full path to `Registry` key and name. - * Ex: ` ROOT\...\CurrentVersion\Run` - */ - path: string; - /** - * Path to Key - * Ex: ` ROOT\...\CurrentVersion` - */ - key: string; - /** - * Key name - * Ex: `Run` - */ - name: string; - /** - * Values associated with key name - * Ex: `Run => Vmware`. Where `Run` is the `Key` name and `Vmware` is the value name - */ - values: Value[]; - /**Timestamp of when the path was last modified */ - last_modified: string; - /**Depth of key name */ - depth: number; - /**Offset to the Security Key info for the key */ - security_offset: number; - /**Path to Registry file */ - registry_path: string; - /**Registry file name */ - registry_file: string; -} - -/** - * The value data associated with Registry key - * References: - * https://github.com/libyal/libregf - * https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md - */ -export interface Value { - /**Name of Value */ - value: string; - /** - * Data associated with value. All types are strings by default. The real type can be determined by `data_type`. - * `REG_BINARY` is a base64 encoded string - */ - data: string; - /** - * Value type. - * Full list of types at: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types - */ - data_type: string; -} - -/** - * Security Key data associated with a Registry Key - */ -export interface SecurityKey { - /**Number of references to the key */ - reference_count: number; - /**Permissions and ACLs associated with the key */ - info: Descriptor; -} +import { Descriptor } from "./acls"; + +/** + * Windows `Registry` is a collection of binary files that store Windows configuration settings and OS information. + * There are multiple `Registry` files on a system such as: + * - `SYSTEM` + * - `SOFTWARE` + * - `SAM` + * - `SECURITY` + * - `NTUSER.DAT -- One per user` + * - `UsrClass.dat -- One per user` + * + * References: + * - https://github.com/libyal/libregf + * - https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md + */ + +/** + * Interface representing the parsed `Registry` structure + */ +export interface Registry { + /** + * Full path to `Registry` key and name. + * Ex: ` ROOT\...\CurrentVersion\Run` + */ + path: string; + /** + * Path to Key + * Ex: ` ROOT\...\CurrentVersion` + */ + key: string; + /** + * Key name + * Ex: `Run` + */ + name: string; + /** + * Values associated with key name + * Ex: `Run => Vmware`. Where `Run` is the `Key` name and `Vmware` is the value name + */ + values: Value[]; + /**Timestamp of when the path was last modified */ + last_modified: string; + /**Depth of key name */ + depth: number; + /**Offset to the Security Key info for the key */ + security_offset: number; + /**Path to Registry file */ + evidence: string; + /**Registry file name */ + registry_file: string; +} + +/** + * The value data associated with Registry key + * References: + * https://github.com/libyal/libregf + * https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md + */ +export interface Value { + /**Name of Value */ + value: string; + /** + * Data associated with value. All types are strings by default. The real type can be determined by `data_type`. + * `REG_BINARY` is a base64 encoded string + */ + data: string; + /** + * Value type. + * Full list of types at: https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-value-types + */ + data_type: string; +} + +/** + * Security Key data associated with a Registry Key + */ +export interface SecurityKey { + /**Number of references to the key */ + reference_count: number; + /**Permissions and ACLs associated with the key */ + info: Descriptor; +} diff --git a/types/windows/registry/bam.ts b/types/windows/registry/bam.ts index ccc80d30..5a929fdc 100644 --- a/types/windows/registry/bam.ts +++ b/types/windows/registry/bam.ts @@ -1,12 +1,13 @@ -export interface Bam { - key_path: string; - reg_path: string; - sid: string; - path: string; - last_execution: string; - message: string; - datetime: string; - timestamp_desc: "Last Execution"; - artifact: "Windows Background Activity Monitor"; - data_type: "windows:registry:bam:entry"; +export interface Bam { + key_path: string; + reg_path: string; + sid: string; + path: string; + last_execution: string; + message: string; + datetime: string; + timestamp_desc: "Last Execution"; + artifact: "Windows Background Activity Monitor"; + data_type: "windows:registry:bam:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/windows/registry/eventlog_providers.ts b/types/windows/registry/eventlog_providers.ts index 9ff52e55..310fcae6 100644 --- a/types/windows/registry/eventlog_providers.ts +++ b/types/windows/registry/eventlog_providers.ts @@ -1,25 +1,25 @@ -export interface RegistryEventlogProviders { - registry_file: string; - key_path: string; - name: string; - channel_names: string[]; - message_file: string; - last_modified: string; - parameter_file: string; - guid: string; - enabled: boolean; - channel_types: ChannelType[]; - message: string; - datetime: string; - timestamp_desc: "Registry Last Modified"; - artifact: "Windows EventLog Provider"; - data_type: "windows:registry:eventlogprovider:entry"; -} - -export enum ChannelType { - Admin = "Admin", - Operational = "Operational", - Analytic = "Analytic", - Debug = "Debug", - Unknown = "Unknown", +export interface RegistryEventlogProviders { + key_path: string; + name: string; + channel_names: string[]; + message_file: string; + last_modified: string; + parameter_file: string; + guid: string; + enabled: boolean; + channel_types: ChannelType[]; + message: string; + datetime: string; + timestamp_desc: "Registry Last Modified"; + artifact: "Windows EventLog Provider"; + data_type: "windows:registry:eventlogprovider:entry"; + evidence: string; +} + +export enum ChannelType { + Admin = "Admin", + Operational = "Operational", + Analytic = "Analytic", + Debug = "Debug", + Unknown = "Unknown", } \ No newline at end of file diff --git a/types/windows/registry/firewall_rules.ts b/types/windows/registry/firewall_rules.ts index 581fba0e..2047ce18 100644 --- a/types/windows/registry/firewall_rules.ts +++ b/types/windows/registry/firewall_rules.ts @@ -1,48 +1,48 @@ -/** - * Object representing a Windows Firewall rule - * References: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fasp/55e50895-2e1f-4479-b130-122f9dc0265f and - */ -export interface FirewallRules { - action: string; - active: boolean; - direction: Direction; - protocol: Protocol; - protocol_number: number; - local_port: number; - remote_port: number; - name: string; - registry_key_name: string; - description: string; - application: string; - registry_file: string; - key_path: string; - last_modified: string; - rule_version: string; - profile: string; - service: string; - remote_address: string[]; - local_address: string[]; - [key: string]: unknown; - message: string; - datetime: string; - timestamp_desc: "Registry Last Modified"; - artifact: "Windows Firewall Rule"; - data_type: "windows:registry:firewallrule:entry"; -} - -export enum Direction { - Inbound = "Inbound", - Outbound = "Outbound", - Unknown = "Unknown", -} - -export enum Protocol { - TCP = "TCP", - UDP = "UDP", - ICMP = "ICMP", - ICMP_v6 = "ICMP_v6", - Unkonwn = "Unknown", - IPV6 = "IPv6", - GRE = "GRE", - IGMP = "IGMP", +/** + * Object representing a Windows Firewall rule + * References: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-fasp/55e50895-2e1f-4479-b130-122f9dc0265f and + */ +export interface FirewallRules { + action: string; + active: boolean; + direction: Direction; + protocol: Protocol; + protocol_number: number; + local_port: number; + remote_port: number; + name: string; + registry_key_name: string; + description: string; + application: string; + key_path: string; + last_modified: string; + rule_version: string; + profile: string; + service: string; + remote_address: string[]; + local_address: string[]; + [ key: string ]: unknown; + message: string; + datetime: string; + timestamp_desc: "Registry Last Modified"; + artifact: "Windows Firewall Rule"; + data_type: "windows:registry:firewallrule:entry"; + evidence: string; +} + +export enum Direction { + Inbound = "Inbound", + Outbound = "Outbound", + Unknown = "Unknown", +} + +export enum Protocol { + TCP = "TCP", + UDP = "UDP", + ICMP = "ICMP", + ICMP_v6 = "ICMP_v6", + Unknown = "Unknown", + IPV6 = "IPv6", + GRE = "GRE", + IGMP = "IGMP", } \ No newline at end of file diff --git a/types/windows/registry/recently_used.ts b/types/windows/registry/recently_used.ts index cda268b0..79a765bc 100644 --- a/types/windows/registry/recently_used.ts +++ b/types/windows/registry/recently_used.ts @@ -1,44 +1,44 @@ -import { ShellItems } from "../shellitems"; - -export interface Mru { - ntuser_path: string; - kind: MruType; - /**Filename of MRU entry*/ - filename: string; - /**Path to MRU entry */ - path: string; - /**Created time of MRU entry */ - created: string; - /**Modified time of MRU entry */ - modified: string; - /**Accessed time of MRU entry */ - accessed: string; - /**All ShellItems that make up the MRU entry */ - items: ShellItems[]; - message: string; - datetime: string; - timestamp_desc: "MRU Entry Created"; - artifact: "Windows Most Recently Used" | "MRU Open Save" | "MRU Last Visit" | "MRU Recent Docs"; - data_type: "windows:registry:mru:entry"; -} - -export interface MruValues { - /**Filename of MRU entry*/ - filename: string; - /**Path to MRU entry */ - path: string; - /**Created time of MRU entry */ - created: string; - /**Modified time of MRU entry */ - modified: string; - /**Accessed time of MRU entry */ - accessed: string; - /**All ShellItems that make up the MRU entry */ - items: ShellItems[]; -} - -export enum MruType { - LASTVISITED = "LastVisited", - OPENSAVE = "OpenSave", - RECENTDOCS = "RecentDocs", -} +import { ShellItems } from "../shellitems"; + +export interface Mru { + kind: MruType; + /**Filename of MRU entry*/ + filename: string; + /**Path to MRU entry */ + path: string; + /**Created time of MRU entry */ + created: string; + /**Modified time of MRU entry */ + modified: string; + /**Accessed time of MRU entry */ + accessed: string; + /**All ShellItems that make up the MRU entry */ + items: ShellItems[]; + message: string; + datetime: string; + timestamp_desc: "MRU Entry Created"; + artifact: "Windows Most Recently Used" | "MRU Open Save" | "MRU Last Visit" | "MRU Recent Docs"; + data_type: "windows:registry:mru:entry"; + evidence: string; +} + +export interface MruValues { + /**Filename of MRU entry*/ + filename: string; + /**Path to MRU entry */ + path: string; + /**Created time of MRU entry */ + created: string; + /**Modified time of MRU entry */ + modified: string; + /**Accessed time of MRU entry */ + accessed: string; + /**All ShellItems that make up the MRU entry */ + items: ShellItems[]; +} + +export enum MruType { + LASTVISITED = "LastVisited", + OPENSAVE = "OpenSave", + RECENTDOCS = "RecentDocs", +} diff --git a/types/windows/registry/run.ts b/types/windows/registry/run.ts index 493061c6..c1f25bd3 100644 --- a/types/windows/registry/run.ts +++ b/types/windows/registry/run.ts @@ -1,20 +1,20 @@ -export interface RegistryRunKey { - key_modified: string; - key_path: string; - registry_path: string; - registry_file: string; - path: string; - /**When file was created */ - created: string; - has_signature: boolean; - md5: string; - sha1: string; - sha256: string; - value: string; - name: string; - message: string; - datetime: string; - timestamp_desc: "Registry Last Modified"; - artifact: "Windows Registry Run Key"; - data_type: "windows:registry:run:entry"; +export interface RegistryRunKey { + key_modified: string; + key_path: string; + registry_file: string; + path: string; + /**When file was created */ + created: string; + has_signature: boolean; + md5: string; + sha1: string; + sha256: string; + value: string; + name: string; + message: string; + datetime: string; + timestamp_desc: "Registry Last Modified"; + artifact: "Windows Registry Run Key"; + data_type: "windows:registry:run:entry"; + evidence: string; } \ No newline at end of file diff --git a/types/windows/registry/usb.ts b/types/windows/registry/usb.ts index e24185f6..ac855878 100644 --- a/types/windows/registry/usb.ts +++ b/types/windows/registry/usb.ts @@ -1,28 +1,29 @@ -/** - * USB forensics actually is pretty complex. More research needs to be done to determine what information is available. - * References: - * - https://www.sans.org/blog/the-truth-about-usb-device-serial-numbers/ - */ - -export interface UsbDevices { - device_class_id: string; - friendly_name: string; - /**Last drive letter assigned to the USB */ - drive_letter: string; - last_connected: string; - last_insertion: string; - last_removal: string; - install: string; - first_install: string; - usb_type: string; - vendor: string; - product: string; - revision: string; - tracking_id: string; - disk_id: string; - message: string; - datetime: string; - timestamp_desc: "USB Last Connected"; - artifact: "Windows USB Device"; - data_type: "windows:registry:usb:entry"; -} +/** + * USB forensics actually is pretty complex. More research needs to be done to determine what information is available. + * References: + * - https://www.sans.org/blog/the-truth-about-usb-device-serial-numbers/ + */ + +export interface UsbDevices { + device_class_id: string; + friendly_name: string; + /**Last drive letter assigned to the USB */ + drive_letter: string; + last_connected: string; + last_insertion: string; + last_removal: string; + install: string; + first_install: string; + usb_type: string; + vendor: string; + product: string; + revision: string; + tracking_id: string; + disk_id: string; + message: string; + datetime: string; + timestamp_desc: "USB Last Connected"; + artifact: "Windows USB Device"; + data_type: "windows:registry:usb:entry"; + evidence: string; +} diff --git a/types/windows/registry/wifi.ts b/types/windows/registry/wifi.ts index 81762ce7..634fac36 100644 --- a/types/windows/registry/wifi.ts +++ b/types/windows/registry/wifi.ts @@ -1,36 +1,36 @@ -export interface Wifi { - name: string; - description: string; - managed: boolean; - category: WifiCategory; - created_local_time: string; - name_type: NameType; - id: string; - last_connected_local_time: string; - registry_path: string; - registry_file: string; - message: string; - datetime: string; - timestamp_desc: "Registry Key Modified"; - artifact: "WiFi Network"; - data_type: "windows:registry:wifi:entry"; -} - -export enum WifiCategory { - Public = "Public", - Private = "Private", - Domain = "Domain", - Unknown = "Unknown", -} - -/** - * From: https://community.spiceworks.com/t/what-are-the-nametype-values-for-the-networklist-registry-keys/526112/6 - */ -export enum NameType { - Wired = "Wired", - Vpn = "VPN", - Wireless = "Wireless", - Mobile = "Mobile", - Unknown = "Unknown", - +export interface Wifi { + name: string; + description: string; + managed: boolean; + category: WifiCategory; + created_local_time: string; + name_type: NameType; + id: string; + last_connected_local_time: string; + registry_path: string; + message: string; + datetime: string; + timestamp_desc: "Registry Key Modified"; + artifact: "WiFi Network"; + data_type: "windows:registry:wifi:entry"; + evidence: string; +} + +export enum WifiCategory { + Public = "Public", + Private = "Private", + Domain = "Domain", + Unknown = "Unknown", +} + +/** + * From: https://community.spiceworks.com/t/what-are-the-nametype-values-for-the-networklist-registry-keys/526112/6 + */ +export enum NameType { + Wired = "Wired", + Vpn = "VPN", + Wireless = "Wireless", + Mobile = "Mobile", + Unknown = "Unknown", + } \ No newline at end of file diff --git a/types/windows/registry/wordwheel.ts b/types/windows/registry/wordwheel.ts index f491c31d..dd773975 100644 --- a/types/windows/registry/wordwheel.ts +++ b/types/windows/registry/wordwheel.ts @@ -1,13 +1,13 @@ -/** - * Extracted WordWheel entry - */ -export interface WordWheelEntry { - /**Searched term entered in Windows Explorer */ - search_term: string; - /**Last modified timestamp for Registry key */ - last_modified: string; - /**Registry file path */ - source_path: string; - /**Registry key path */ - reg_path: string; +/** + * Extracted WordWheel entry + */ +export interface WordWheelEntry { + /**Searched term entered in Windows Explorer */ + search_term: string; + /**Last modified timestamp for Registry key */ + last_modified: string; + /**Registry file path */ + evidence: string; + /**Registry key path */ + reg_path: string; } \ No newline at end of file diff --git a/types/windows/search.ts b/types/windows/search.ts index 5deed162..e2fc8ed7 100644 --- a/types/windows/search.ts +++ b/types/windows/search.ts @@ -1,75 +1,77 @@ -/** - * Windows `Search` is an indexing service for tracking a huge amount of files and content on Windows. - * `Search` can parse a large amount of metadata (properties) for each entry it indexes. It has almost 600 different types of properties it can parse. - * It can even index some of the contents of a file. - * - * `Search` can index large parts of the file system, so parsing the `Search` database can provide a partial file listing of the system. - * `Search` is disabled on Windows Servers and starting on newer versions of Windows 11 it is stored in a SQLITE database (previously was an ESE database) - * - * References: - * - https://github.com/libyal/esedb-kb/blob/main/documentation/Windows%20Search.asciidoc - * - https://en.wikipedia.org/wiki/Windows_Search - */ -export interface SearchEntry { - /**Index ID for row */ - document_id: number; - /**Search entry name */ - entry: string; - /**Search entry last modified */ - last_modified: string; - /** - * JSON object representing the properties associated with the entry - * - * Example: - * ``` - * "properties": { - "3-System_ItemFolderNameDisplay": "Programs", - "4429-System_IsAttachment": "0", - "4624-System_Search_AccessCount": "0", - "4702-System_VolumeId": "08542f90-0000-0000-0000-501f00000000", - "17F-System_DateAccessed": "k8DVxD162QE=", - "4392-System_FileExtension": ".lnk", - "4631F-System_Search_GatherTime": "7B6taj962QE=", - "5-System_ItemTypeText": "Shortcut", - "4184-System_ComputerName": "DESKTOP-EIS938N", - "15F-System_DateModified": "EVHzDyR22QE=", - "4434-System_IsFolder": "0", - "4365-System_DateImported": "ABKRqWyI1QE=", - "4637-System_Search_Store": "file", - "4373-System_Document_DateSaved": "EVHzDyR22QE=", - "4448-System_ItemPathDisplayNarrow": "Firefox (C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs)", - "4559-System_NotUserContent": "0", - "33-System_ItemUrl": "file:C:/ProgramData/Microsoft/Windows/Start Menu/Programs/Firefox.lnk", - "4447-System_ItemPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Firefox.lnk", - "13F-System_Size": "7QMAAAAAAAA=", - "4441-System_ItemFolderPathDisplayNarrow": "Programs (C:\\ProgramData\\Microsoft\\Windows\\Start Menu)", - "0-InvertedOnlyPids": "cBFzESgSZRI=", - "4443-System_ItemNameDisplay": "Firefox.lnk", - "4442-System_ItemName": "Firefox.lnk", - "14F-System_FileAttributes": "32", - "4403-System_FolderNameDisplay": "Cygwin", - "4565-System_ParsingName": "Firefox.lnk", - "4456-System_Kind": "bGluawBwcm9ncmFt", - "27F-System_Search_Rank": "707406378", - "16F-System_DateCreated": "UUZNqWyI1QE=", - "4440-System_ItemFolderPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs", - "4397-System_FilePlaceholderStatus": "6", - "4465-System_Link_TargetParsingPath": "C:\\Program Files\\Mozilla Firefox\\firefox.exe", - "4431-System_IsEncrypted": "0", - "4457-System_KindText": "Link; Program", - "4444-System_ItemNameDisplayWithoutExtension": "Firefox", - "11-System_FileName": "Firefox.lnk", - "4623-System_SFGAOFlags": "1078002039", - "0F-InvertedOnlyMD5": "z1gPcor92OaNVyAAzRdOsw==", - "4371-System_Document_DateCreated": "ABKRqWyI1QE=", - "4633-System_Search_LastIndexedTotalTime": "0.03125", - "4396-System_FileOwner": "Administrators", - "4438-System_ItemDate": "ABKRqWyI1QE=", - "4466-System_Link_TargetSFGAOFlags": "1077936503", - "4450-System_ItemType": ".lnk", - "4678-System_ThumbnailCacheId": "DzpSS6gn5yg=" - } - * ``` - */ - properties: Record; -} +/** + * Windows `Search` is an indexing service for tracking a huge amount of files and content on Windows. + * `Search` can parse a large amount of metadata (properties) for each entry it indexes. It has almost 600 different types of properties it can parse. + * It can even index some of the contents of a file. + * + * `Search` can index large parts of the file system, so parsing the `Search` database can provide a partial file listing of the system. + * `Search` is disabled on Windows Servers and starting on newer versions of Windows 11 it is stored in a SQLITE database (previously was an ESE database) + * + * References: + * - https://github.com/libyal/esedb-kb/blob/main/documentation/Windows%20Search.asciidoc + * - https://en.wikipedia.org/wiki/Windows_Search + */ +export interface SearchEntry { + /**Index ID for row */ + document_id: number; + /**Search entry name */ + entry: string; + /**Search entry last modified */ + last_modified: string; + /** + * JSON object representing the properties associated with the entry + * + * Example: + * ``` + * "properties": { + "3-System_ItemFolderNameDisplay": "Programs", + "4429-System_IsAttachment": "0", + "4624-System_Search_AccessCount": "0", + "4702-System_VolumeId": "08542f90-0000-0000-0000-501f00000000", + "17F-System_DateAccessed": "k8DVxD162QE=", + "4392-System_FileExtension": ".lnk", + "4631F-System_Search_GatherTime": "7B6taj962QE=", + "5-System_ItemTypeText": "Shortcut", + "4184-System_ComputerName": "DESKTOP-EIS938N", + "15F-System_DateModified": "EVHzDyR22QE=", + "4434-System_IsFolder": "0", + "4365-System_DateImported": "ABKRqWyI1QE=", + "4637-System_Search_Store": "file", + "4373-System_Document_DateSaved": "EVHzDyR22QE=", + "4448-System_ItemPathDisplayNarrow": "Firefox (C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs)", + "4559-System_NotUserContent": "0", + "33-System_ItemUrl": "file:C:/ProgramData/Microsoft/Windows/Start Menu/Programs/Firefox.lnk", + "4447-System_ItemPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Firefox.lnk", + "13F-System_Size": "7QMAAAAAAAA=", + "4441-System_ItemFolderPathDisplayNarrow": "Programs (C:\\ProgramData\\Microsoft\\Windows\\Start Menu)", + "0-InvertedOnlyPids": "cBFzESgSZRI=", + "4443-System_ItemNameDisplay": "Firefox.lnk", + "4442-System_ItemName": "Firefox.lnk", + "14F-System_FileAttributes": "32", + "4403-System_FolderNameDisplay": "Cygwin", + "4565-System_ParsingName": "Firefox.lnk", + "4456-System_Kind": "bGluawBwcm9ncmFt", + "27F-System_Search_Rank": "707406378", + "16F-System_DateCreated": "UUZNqWyI1QE=", + "4440-System_ItemFolderPathDisplay": "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs", + "4397-System_FilePlaceholderStatus": "6", + "4465-System_Link_TargetParsingPath": "C:\\Program Files\\Mozilla Firefox\\firefox.exe", + "4431-System_IsEncrypted": "0", + "4457-System_KindText": "Link; Program", + "4444-System_ItemNameDisplayWithoutExtension": "Firefox", + "11-System_FileName": "Firefox.lnk", + "4623-System_SFGAOFlags": "1078002039", + "0F-InvertedOnlyMD5": "z1gPcor92OaNVyAAzRdOsw==", + "4371-System_Document_DateCreated": "ABKRqWyI1QE=", + "4633-System_Search_LastIndexedTotalTime": "0.03125", + "4396-System_FileOwner": "Administrators", + "4438-System_ItemDate": "ABKRqWyI1QE=", + "4466-System_Link_TargetSFGAOFlags": "1077936503", + "4450-System_ItemType": ".lnk", + "4678-System_ThumbnailCacheId": "DzpSS6gn5yg=" + } + * ``` + */ + properties: Record; + /**Path to the ESE database */ + evidence: string; +} diff --git a/types/windows/services.ts b/types/windows/services.ts index 77725510..3ed79cc6 100644 --- a/types/windows/services.ts +++ b/types/windows/services.ts @@ -1,53 +1,55 @@ -/** - * Windows `Services` are a common form of persistence and privilege escalation on Windows systems. Service data is stored in the SYSTEM Registry file. - * `Services` run with SYSTEM level privileges. - * - * References: - * - https://forensafe.com/blogs/windowsservices.html - * - https://github.com/Velocidex/velociraptor/blob/master/artifacts/definitions/Windows/System/Services.yaml - * - https://winreg-kb.readthedocs.io/en/latest/sources/system-keys/Services-and-drivers.html - */ -export interface Services { - /**Current State of the Service */ - state: string; - /**Name of Service */ - name: string; - /**Display name of Service */ - display_name: string; - /**Service description */ - description: string; - /**Start mode of Service */ - start_mode: string; - /**Path to executable for Service */ - path: string; - /**Service types. Ex: KernelDriver */ - service_type: string[]; - /**Account associated with Service */ - account: string; - /**Registry modified timestamp. May be used to determine when the Service was created */ - modified: string; - /**DLL associated with Service */ - service_dll: string; - /**Service command upon failure */ - failure_command: string; - /**Reset period associated with Service */ - reset_period: number; - /**Service actions upon failure */ - failure_actions: FailureActions[]; - /**Privileges associated with Service */ - required_privileges: string[]; - /**Error associated with Service */ - error_control: string; - /**Registry path associated with Service */ - reg_path: string; -} - -/** - * Failure actions executed when Service fails - */ -interface FailureActions { - /**Action executed upon failure */ - action: string; - /**Delay in seconds on failure */ - delay: number; -} +/** + * Windows `Services` are a common form of persistence and privilege escalation on Windows systems. Service data is stored in the SYSTEM Registry file. + * `Services` run with SYSTEM level privileges. + * + * References: + * - https://forensafe.com/blogs/windowsservices.html + * - https://github.com/Velocidex/velociraptor/blob/master/artifacts/definitions/Windows/System/Services.yaml + * - https://winreg-kb.readthedocs.io/en/latest/sources/system-keys/Services-and-drivers.html + */ +export interface Services { + /**Current State of the Service */ + state: string; + /**Name of Service */ + name: string; + /**Display name of Service */ + display_name: string; + /**Service description */ + description: string; + /**Start mode of Service */ + start_mode: string; + /**Path to executable for Service */ + path: string; + /**Service types. Ex: KernelDriver */ + service_type: string[]; + /**Account associated with Service */ + account: string; + /**Registry modified timestamp. May be used to determine when the Service was created */ + modified: string; + /**DLL associated with Service */ + service_dll: string; + /**Service command upon failure */ + failure_command: string; + /**Reset period associated with Service */ + reset_period: number; + /**Service actions upon failure */ + failure_actions: FailureActions[]; + /**Privileges associated with Service */ + required_privileges: string[]; + /**Error associated with Service */ + error_control: string; + /**Registry path associated with Service */ + reg_path: string; + /**Path to the Registry file */ + evidence: string; +} + +/** + * Failure actions executed when Service fails + */ +interface FailureActions { + /**Action executed upon failure */ + action: string; + /**Delay in seconds on failure */ + delay: number; +} diff --git a/types/windows/shellbags.ts b/types/windows/shellbags.ts index b2d09d4e..70ade967 100644 --- a/types/windows/shellbags.ts +++ b/types/windows/shellbags.ts @@ -1,45 +1,45 @@ -/** - * Shellbags are composed of `Shellitems` however the main value of `Shellbags` is determining what directories were browsed by the user. - * We use the `ShellItems` to reconstruct this information and return the `Shellbag` - * - * References: - * - https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc - */ -export interface Shellbags { - /**Reconstructed directory path */ - path: string; - /**FAT created timestamp. Only applicable for Directory `shell_type` */ - created: string; - /**FAT modified timestamp. Only applicable for Directory `shell_type` */ - modified: string; - /**FAT modified timestamp. Only applicable for Directory `shell_type` */ - accessed: string; - /**Entry number in MFT. Only applicable for Directory `shell_type` */ - mft_entry: number; - /**Sequence number in MFT. Only applicable for Directory `shell_type` */ - mft_sequence: number; - /** - * Type of shellitem - * - * Can be: - * `Directory, URI, RootFolder, Network, Volume, ControlPanel, UserPropertyView, Delegate, Variable, MTP, Unknown, History` - * - * Most common is typically `Directory` - */ - shell_type: string; - /** - * Reconstructed directory with any GUIDs resolved - * Ex: `20d04fe0-3aea-1069-a2d8-08002b30309d` to `This PC` - */ - resolve_path: string; - /**Registry key last modified */ - reg_modified: string; - /**User Registry file associated with `Shellbags` */ - reg_file: string; - /**Registry key path to `Shellbags` data */ - reg_path: string; - /**Full file path to the User Registry file */ - reg_file_path: string; - /**Array of Property Stores */ - stores: Record[]; -} +/** + * Shellbags are composed of `Shellitems` however the main value of `Shellbags` is determining what directories were browsed by the user. + * We use the `ShellItems` to reconstruct this information and return the `Shellbag` + * + * References: + * - https://github.com/libyal/libfwsi/blob/main/documentation/Windows%20Shell%20Item%20format.asciidoc + */ +export interface Shellbags { + /**Reconstructed directory path */ + path: string; + /**FAT created timestamp. Only applicable for Directory `shell_type` */ + created: string; + /**FAT modified timestamp. Only applicable for Directory `shell_type` */ + modified: string; + /**FAT modified timestamp. Only applicable for Directory `shell_type` */ + accessed: string; + /**Entry number in MFT. Only applicable for Directory `shell_type` */ + mft_entry: number; + /**Sequence number in MFT. Only applicable for Directory `shell_type` */ + mft_sequence: number; + /** + * Type of shellitem + * + * Can be: + * `Directory, URI, RootFolder, Network, Volume, ControlPanel, UserPropertyView, Delegate, Variable, MTP, Unknown, History` + * + * Most common is typically `Directory` + */ + shell_type: string; + /** + * Reconstructed directory with any GUIDs resolved + * Ex: `20d04fe0-3aea-1069-a2d8-08002b30309d` to `This PC` + */ + resolve_path: string; + /**Registry key last modified */ + reg_modified: string; + /**User Registry file associated with `Shellbags` */ + reg_file: string; + /**Registry key path to `Shellbags` data */ + reg_path: string; + /**Full file path to the User Registry file */ + evidence: string; + /**Array of Property Stores */ + stores: Record[]; +} diff --git a/types/windows/shimcache.ts b/types/windows/shimcache.ts index 0e403622..d619dfb2 100644 --- a/types/windows/shimcache.ts +++ b/types/windows/shimcache.ts @@ -1,20 +1,20 @@ -/** - * Windows `Shimcache` (also called: AppCompatCache, Application Compatability Cache, AppCompat) are Registry entries that track application execution. - * These entries are only written when the system is shutdown/rebooted. - * - * References: - * - https://www.mandiant.com/resources/blog/caching-out-the-val - * - https://winreg-kb.readthedocs.io/en/latest/sources/system-keys/Application-compatibility-cache.html - */ -export interface Shimcache { - /**Entry number for shimcache. Entry zero (0) is most recent execution */ - entry: number; - /**Full path to application file */ - path: string; - /**Standard Information Modified timestamp */ - last_modified: string; - /**Full path to the Registry key */ - key_path: string; - /**Path to the Registry file */ - source_path: string; -} +/** + * Windows `Shimcache` (also called: AppCompatCache, Application Compatability Cache, AppCompat) are Registry entries that track application execution. + * These entries are only written when the system is shutdown/rebooted. + * + * References: + * - https://www.mandiant.com/resources/blog/caching-out-the-val + * - https://winreg-kb.readthedocs.io/en/latest/sources/system-keys/Application-compatibility-cache.html + */ +export interface Shimcache { + /**Entry number for shimcache. Entry zero (0) is most recent execution */ + entry: number; + /**Full path to application file */ + path: string; + /**Standard Information Modified timestamp */ + last_modified: string; + /**Full path to the Registry key */ + key_path: string; + /**Path to the Registry file */ + evidence: string; +} diff --git a/types/windows/shimdb.ts b/types/windows/shimdb.ts index a92ca03c..9c09f640 100644 --- a/types/windows/shimdb.ts +++ b/types/windows/shimdb.ts @@ -1,75 +1,75 @@ -/** - * Windows Shimdatabase (ShimDB) can be used by Windows applications to provided compatibility between Windows versions. - * It does this via `shims` that are inserted into the application that modifies function calls. - * Malicious custom shims can be created as a form of persistence. - * - * References: - * - https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm - * - https://www.mandiant.com/resources/blog/fin7-shim-databases-persistence - */ -export interface Shimdb { - /**Array of `TAGS` associated with the index tag*/ - indexes: TagData[]; - /**Data associated with the Shimdb */ - db_data: DatabaseData; - /**Path to parsed sdb file */ - sdb_path: string; -} - -/** - * SDB files are composed of `TAGS`. There are multiple types of `TAGS` - * `data` have `TAGS` that can be represented via a JSON object - * `list_data` have `TAGS` that can be represented as an array of JSON objects - * - * Example: - * ``` - * "data": { - * "TAG_FIX_ID": "4aeea7ee-44f1-4085-abc2-6070eb2b6618", - * "TAG_RUNTIME_PLATFORM": "37", - * "TAG_NAME": "256Color" - * }, - * "list_data": [ - * { - * "TAG_NAME": "Force8BitColor", - * "TAG_SHIM_TAGID": "169608" - * }, - * { - * "TAG_SHIM_TAGID": "163700", - * "TAG_NAME": "DisableThemes" - * } - * ] - * ``` - * - * See https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm for complete list of `TAGS` - */ -export interface TagData { - /**TAGs represented as a JSON object */ - data: Record; - /**Array of TAGS represented as a JSON objects */ - list_data: Record[]; -} - -/** - * Metadata related to the SDB file - */ -export interface DatabaseData { - /**SDB version info */ - sdb_version: string; - /**Compile timestamp of the SDB file */ - compile_time: string; - /**Compiler version info */ - compiler_version: string; - /**Name of SDB */ - name: string; - /**Platform ID */ - platform: number; - /**ID associated with SDB */ - database_id: string; - /** - * The SDB file may contain additional metadata information - * May include additional `TAGS` - */ - additional_metadata: Record; - /**Array of `TAGS` associated with the SDB file */ - list_data: TagData[]; -} +/** + * Windows Shimdatabase (ShimDB) can be used by Windows applications to provided compatibility between Windows versions. + * It does this via `shims` that are inserted into the application that modifies function calls. + * Malicious custom shims can be created as a form of persistence. + * + * References: + * - https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm + * - https://www.mandiant.com/resources/blog/fin7-shim-databases-persistence + */ +export interface Shimdb { + /**Array of `TAGS` associated with the index tag*/ + indexes: TagData[]; + /**Data associated with the Shimdb */ + db_data: DatabaseData; + /**Path to parsed sdb file */ + evidence: string; +} + +/** + * SDB files are composed of `TAGS`. There are multiple types of `TAGS` + * `data` have `TAGS` that can be represented via a JSON object + * `list_data` have `TAGS` that can be represented as an array of JSON objects + * + * Example: + * ``` + * "data": { + * "TAG_FIX_ID": "4aeea7ee-44f1-4085-abc2-6070eb2b6618", + * "TAG_RUNTIME_PLATFORM": "37", + * "TAG_NAME": "256Color" + * }, + * "list_data": [ + * { + * "TAG_NAME": "Force8BitColor", + * "TAG_SHIM_TAGID": "169608" + * }, + * { + * "TAG_SHIM_TAGID": "163700", + * "TAG_NAME": "DisableThemes" + * } + * ] + * ``` + * + * See https://www.geoffchappell.com/studies/windows/win32/apphelp/sdb/index.htm for complete list of `TAGS` + */ +export interface TagData { + /**TAGs represented as a JSON object */ + data: Record; + /**Array of TAGS represented as a JSON objects */ + list_data: Record[]; +} + +/** + * Metadata related to the SDB file + */ +export interface DatabaseData { + /**SDB version info */ + sdb_version: string; + /**Compile timestamp of the SDB file */ + compile_time: string; + /**Compiler version info */ + compiler_version: string; + /**Name of SDB */ + name: string; + /**Platform ID */ + platform: number; + /**ID associated with SDB */ + database_id: string; + /** + * The SDB file may contain additional metadata information + * May include additional `TAGS` + */ + additional_metadata: Record; + /**Array of `TAGS` associated with the SDB file */ + list_data: TagData[]; +} diff --git a/types/windows/shortcuts.ts b/types/windows/shortcuts.ts index de8cf70b..97ab6841 100644 --- a/types/windows/shortcuts.ts +++ b/types/windows/shortcuts.ts @@ -1,142 +1,142 @@ -import { ShellItems } from "./shellitems"; - -/** - * `Shortcut` files are files that point to another file. - * They often contain a large amount of metadata related to the target file - * - * References: - * - https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/%5bMS-SHLLINK%5d.pdf - * - https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc - */ -export interface Shortcut { - /**Path to `shortcut (lnk)` file */ - source_path: string; - /**Flags that specify what data structures are in the `lnk` file */ - data_flags: string[]; - /**File attributes of target file */ - attribute_flags: string[]; - /**Standard Information created timestamp of target file */ - created: string; - /**Standard Information accessed timestamp of target file */ - accessed: string; - /**Standard Information modified timestamp of target file */ - modified: string; - /**Size in bytes of target file */ - file_size: number; - /**Flag associated where target file is located. On volume or network share */ - location_flags: string; - /**Path to target file */ - path: string; - /**Serial associated with volume if target file is on drive */ - drive_serial: string; - /**Drive type associated with volume if target file is on drive */ - drive_type: string; - /**Name of volume if target file is on drive */ - volume_label: string; - /**Network type if target file is on network share */ - network_provider: string; - /**Network share name if target file is on network share */ - network_share_name: string; - /**Network share device name if target file is on network share */ - network_device_name: string; - /**Description of shortcut (lnk) file */ - description: string; - /**Relative path to target file */ - relative_path: string; - /**Directory of target file */ - working_directory: string; - /**Command args associated with target file */ - command_line_args: string; - /**Icon path associated with shortcut (lnk) file */ - icon_location: string; - /**Hostname of target file */ - hostname: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - droid_file_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_volume_id: string; - /** - * Digital Record Object Identification (DROID) used to track lnk file - */ - birth_droid_file_id: string; - /**Shellitems associated with shortcut (lnk) file */ - shellitems: ShellItems[]; - /**Array of property stores */ - properties: Record[]; - /**Environmental variable data in shortcut */ - environment_variable: string; - /**Console metadata in shortcut */ - console: Console[]; - /**Windows Codepage in shortcut */ - codepage: number; - /**Special folder ID in shortcut */ - special_folder_id: number; - /**macOS Darwin ID in shortcut */ - darwin_id: string; - /**Shim layer entry in shortcut */ - shim_layer: string; - /**Known folder GUID in shortcut */ - known_folder: string; - /** - * If the Shortcut file strings exceed 260 bytes (520 if Unicode). Then the Shortcut is marked as "abnormal". - * Normally Shortcut strings can be 64KB in size. **However**, the Windows implementation of the Shortcut file limits string sizes to 260 bytes. - * If a string is larger than 260 bytes then it may be an indicator that it is malformed or was crafted malicious to try to avoid forensic tools. - * - * Reference: https://harfanglab.io/insidethelab/sadfuture-xdspy-latest-evolution/#tid_specifications_ignored - */ - is_abnormal: boolean; -} - -/** - * Console metadata embedded in Shortcut file - */ -interface Console { - /**Colors for Console */ - color_flags: string[]; - /**Additional colors for Console */ - pop_fill_attributes: string[]; - /**Console width buffer size */ - screen_width_buffer_size: number; - /**Console height buffer size */ - screen_height_buffer_size: number; - /**Console window width */ - window_width: number; - /**Console window height */ - window_height: number; - /**Console X coordinate */ - window_x_coordinate: number; - /**Console Y coordinate */ - window_y_coordinate: number; - /**Console font size */ - font_size: number; - /**Console font family */ - font_family: string; - /**Console font weight */ - font_weight: string; - /**Console font name */ - face_name: string; - /**Console cursor size */ - cursor_size: string; - /**Is full screen set (boolean) */ - full_screen: number; - /**Insert mode */ - insert_mode: number; - /**Automatic position set (boolean) */ - automatic_position: number; - /**Console history buffer size */ - history_buffer_size: number; - /**Console number of buffers */ - number_history_buffers: number; - /**Duplicates allowed in history */ - duplicates_allowed_history: number; - /**Base64 encoded color table. */ - color_table: string; -} +import { ShellItems } from "./shellitems"; + +/** + * `Shortcut` files are files that point to another file. + * They often contain a large amount of metadata related to the target file + * + * References: + * - https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-SHLLINK/%5bMS-SHLLINK%5d.pdf + * - https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc + */ +export interface Shortcut { + /**Path to `shortcut (lnk)` file */ + evidence: string; + /**Flags that specify what data structures are in the `lnk` file */ + data_flags: string[]; + /**File attributes of target file */ + attribute_flags: string[]; + /**Standard Information created timestamp of target file */ + created: string; + /**Standard Information accessed timestamp of target file */ + accessed: string; + /**Standard Information modified timestamp of target file */ + modified: string; + /**Size in bytes of target file */ + file_size: number; + /**Flag associated where target file is located. On volume or network share */ + location_flags: string; + /**Path to target file */ + path: string; + /**Serial associated with volume if target file is on drive */ + drive_serial: string; + /**Drive type associated with volume if target file is on drive */ + drive_type: string; + /**Name of volume if target file is on drive */ + volume_label: string; + /**Network type if target file is on network share */ + network_provider: string; + /**Network share name if target file is on network share */ + network_share_name: string; + /**Network share device name if target file is on network share */ + network_device_name: string; + /**Description of shortcut (lnk) file */ + description: string; + /**Relative path to target file */ + relative_path: string; + /**Directory of target file */ + working_directory: string; + /**Command args associated with target file */ + command_line_args: string; + /**Icon path associated with shortcut (lnk) file */ + icon_location: string; + /**Hostname of target file */ + hostname: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + droid_file_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_volume_id: string; + /** + * Digital Record Object Identification (DROID) used to track lnk file + */ + birth_droid_file_id: string; + /**Shellitems associated with shortcut (lnk) file */ + shellitems: ShellItems[]; + /**Array of property stores */ + properties: Record[]; + /**Environmental variable data in shortcut */ + environment_variable: string; + /**Console metadata in shortcut */ + console: Console[]; + /**Windows Codepage in shortcut */ + codepage: number; + /**Special folder ID in shortcut */ + special_folder_id: number; + /**macOS Darwin ID in shortcut */ + darwin_id: string; + /**Shim layer entry in shortcut */ + shim_layer: string; + /**Known folder GUID in shortcut */ + known_folder: string; + /** + * If the Shortcut file strings exceed 260 bytes (520 if Unicode). Then the Shortcut is marked as "abnormal". + * Normally Shortcut strings can be 64KB in size. **However**, the Windows implementation of the Shortcut file limits string sizes to 260 bytes. + * If a string is larger than 260 bytes then it may be an indicator that it is malformed or was crafted malicious to try to avoid forensic tools. + * + * Reference: https://harfanglab.io/insidethelab/sadfuture-xdspy-latest-evolution/#tid_specifications_ignored + */ + is_abnormal: boolean; +} + +/** + * Console metadata embedded in Shortcut file + */ +interface Console { + /**Colors for Console */ + color_flags: string[]; + /**Additional colors for Console */ + pop_fill_attributes: string[]; + /**Console width buffer size */ + screen_width_buffer_size: number; + /**Console height buffer size */ + screen_height_buffer_size: number; + /**Console window width */ + window_width: number; + /**Console window height */ + window_height: number; + /**Console X coordinate */ + window_x_coordinate: number; + /**Console Y coordinate */ + window_y_coordinate: number; + /**Console font size */ + font_size: number; + /**Console font family */ + font_family: string; + /**Console font weight */ + font_weight: string; + /**Console font name */ + face_name: string; + /**Console cursor size */ + cursor_size: string; + /**Is full screen set (boolean) */ + full_screen: number; + /**Insert mode */ + insert_mode: number; + /**Automatic position set (boolean) */ + automatic_position: number; + /**Console history buffer size */ + history_buffer_size: number; + /**Console number of buffers */ + number_history_buffers: number; + /**Duplicates allowed in history */ + duplicates_allowed_history: number; + /**Base64 encoded color table. */ + color_table: string; +} diff --git a/types/windows/srum.ts b/types/windows/srum.ts index d8bbfd94..f10bdb46 100644 --- a/types/windows/srum.ts +++ b/types/windows/srum.ts @@ -1,283 +1,299 @@ -/** - * Windows System Resource Utilization Monitor (`SRUM`) is a service that tracks application resource usage. - * The service tracks things like time running, bytes sent, bytes received, energy usage, and lots more. - * - * This service was introduced in Windows 8 and is stored in an ESE database at `C:\\Windows\System32\\sru\\SRUDB.dat`. - * On Windows 8 some of the data can be found in the Registry too (temporary storage before writing to SRUDB.dat), but in later versions of Windows the data is no longer in the Registry. - * - * References: - * - https://github.com/libyal/esedb-kb/blob/main/documentation/System%20Resource%20Usage%20Monitor%20(SRUM).asciidoc - * - https://velociraptor.velocidex.com/digging-into-the-system-resource-usage-monitor-srum-afbadb1a375 - */ - -/** - * SRUM table associated with application executions `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}` - */ -export interface ApplicationInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Foreground Cycle time for application */ - foreground_cycle_time: number; - /**Background Cycle time for application */ - background_cycle_time: number; - /**Facetime for application */ - facetime: number; - /**Count of foreground context switches */ - foreground_context_switches: number; - /**Count of background context switches */ - background_context_switches: number; - /**Count of foreground bytes read */ - foreground_bytes_read: number; - /**Count of background bytes read */ - foreground_bytes_written: number; - /**Count of foreground read operations */ - foreground_num_read_operations: number; - /**Count of foreground write operations */ - foreground_num_write_options: number; - /**Count of foreground flushes */ - foreground_number_of_flushes: number; - /**Count of background bytes read */ - background_bytes_read: number; - /**Count of background write operations */ - background_bytes_written: number; - /**Count of background read operations */ - background_num_read_operations: number; - /**Count of background write operations */ - background_num_write_operations: number; - /**Count of background flushes */ - background_number_of_flushes: number; -} - -/** - * SRUM table associated with the timeline of an application's execution `{5C8CF1C7-7257-4F13-B223-970EF5939312}` - */ -export interface ApplicationTimeline { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Flags associated with entry */ - flags: number; - /**End time of entry */ - end_time: string; - /**Duration of timeline in microseconds */ - duration_ms: number; - /**Span of timeline in microseconds */ - span_ms: number; - /**Timeline end for entry */ - timeline_end: number; - /**In focus value for entry */ - in_focus_timeline: number; - /**User input value for entry */ - user_input_timeline: number; - /**Comp rendered value for entry */ - comp_rendered_timeline: number; - /**Comp dirtied value for entry */ - comp_dirtied_timeline: number; - /**Comp propagated value for entry */ - comp_propagated_timeline: number; - /**Audio input value for entry */ - audio_in_timeline: number; - /**Audio output value for entry */ - audio_out_timeline: number; - /**CPU value for entry */ - cpu_timeline: number; - /**Disk value for entry */ - disk_timeline: number; - /**Network value for entry */ - network_timeline: number; - /**MBB value for entry */ - mbb_timeline: number; - /**In focus seconds count */ - in_focus_s: number; - /**PSM foreground seconds count */ - psm_foreground_s: number; - /**User input seconds count */ - user_input_s: number; - /**Comp rendered seconds count */ - comp_rendered_s: number; - /**Comp dirtied seconds count */ - comp_dirtied_s: number; - /**Comp propagated seconds count */ - comp_propagated_s: number; - /**Audio input seconds count */ - audio_in_s: number; - /**Audio output seconds count */ - audio_out_s: number; - /**Cycles value for entry */ - cycles: number; - /**Cycles breakdown value for entry */ - cycles_breakdown: number; - /**Cycles attribute value for entry */ - cycles_attr: number; - /**Cycles attribute breakdown for entry */ - cycles_attr_breakdown: number; - /**Cycles WOB value for entry */ - cycles_wob: number; - /**Cycles WOB breakdown value for entry */ - cycles_wob_breakdown: number; - /**Disk raw value for entry */ - disk_raw: number; - /**Network tail raw value for entry */ - network_tail_raw: number; - /**Network bytes associated with entry*/ - network_bytes_raw: number; - /**MBB tail raw value for entry */ - mbb_tail_raw: number; - /**MBB bytes associated with entry */ - mbb_bytes_raw: number; - /**Display required seconds count */ - display_required_s: number; - /**Display required timeline value for entry */ - display_required_timeline: number; - /**Keyboard input timeline value for entry */ - keyboard_input_timeline: number; - /**Keyboard input seconds count */ - keyboard_input_s: number; - /**Mouse input seconds count */ - mouse_input_s: number; -} - -/** - * SRUM table associated with VFU `{7ACBBAA3-D029-4BE4-9A7A-0885927F1D8F}`. Unsure what this tracks. - */ -export interface AppVfu { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Flags associated with VFU entry */ - flags: number; - /**Start time associated with VFU entry */ - start_time: string; - /**End time associated with VFU entry */ - end_time: string; - /**Base64 encoded usage data associated with VFU entry */ - usage: string; -} - -/** - * SRUM table associated with EnergyInfo `{DA73FB89-2BEA-4DDC-86B8-6E048C6DA477}` - */ -export interface EnergyInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Base64 encoded binary data associated with EnergyInfo entry */ - binary_data: string; -} - -/** - * SRUM table associated with EnergyUsage `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}` and `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}LT` - */ -export interface EnergyUsage { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Event Timestamp */ - event_timestamp: string; - /**State transition associated with entry */ - state_transition: number; - /**Full charged capacity associated with entry */ - full_charged_capacity: number; - /**Designed capacity associated with entry */ - designed_capacity: number; - /** Charge level associated with entry */ - charge_level: number; - /**Cycle count associated with entry */ - cycle_count: number; - /**Configuration hash associated with entry */ - configuration_hash: number; -} - -/** - * SRUM table associated with NetworkInfo `{973F5D5C-1D90-4944-BE8E-24B94231A174}` - */ -export interface NetworkInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Interface luid associated with entry */ - interface_luid: number; - /**L2 profile ID associated with entry */ - l2_profile_id: number; - /**L2 profile flags associated with entry */ - l2_profile_flags: number; - /**Bytes sent associated with entry */ - bytes_sent: number; - /**Bytes received associated with entry */ - bytes_recvd: number; -} - -/** - * SRUM table associated with NetworkConnectivityInfo `{DD6636C4-8929-4683-974E-22C046A43763}` - */ -export interface NetworkConnectivityInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Interface luid associated with entry */ - interface_luid: number; - /**L2 profile ID associated with entry */ - l2_profile_id: number; - /**Connected time associated with entry */ - connected_time: number; - /*Connect start time associated with entry*/ - connect_start_time: number; - /**L2 profile flags associated with entry */ - l2_profile_flags: number; -} - -/** - * SRUM table associated with NotificationInfo `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}` - */ -export interface NotificationInfo { - /**ID in for row in the ESE table */ - auto_inc_id: number; - /**Timestamp when ESE table was updated */ - timestamp: string; - /**Application name */ - app_id: string; - /**SID associated with the application process */ - user_id: string; - /**Notification type associated with entry */ - notification_type: number; - /**Size of payload associated with entry */ - payload_size: number; - /**Network type associated with entry */ - network_type: number; -} +/** + * Windows System Resource Utilization Monitor (`SRUM`) is a service that tracks application resource usage. + * The service tracks things like time running, bytes sent, bytes received, energy usage, and lots more. + * + * This service was introduced in Windows 8 and is stored in an ESE database at `C:\\Windows\System32\\sru\\SRUDB.dat`. + * On Windows 8 some of the data can be found in the Registry too (temporary storage before writing to SRUDB.dat), but in later versions of Windows the data is no longer in the Registry. + * + * References: + * - https://github.com/libyal/esedb-kb/blob/main/documentation/System%20Resource%20Usage%20Monitor%20(SRUM).asciidoc + * - https://velociraptor.velocidex.com/digging-into-the-system-resource-usage-monitor-srum-afbadb1a375 + */ + +/** + * SRUM table associated with application executions `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA89}` + */ +export interface ApplicationInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Foreground Cycle time for application */ + foreground_cycle_time: number; + /**Background Cycle time for application */ + background_cycle_time: number; + /**Facetime for application */ + facetime: number; + /**Count of foreground context switches */ + foreground_context_switches: number; + /**Count of background context switches */ + background_context_switches: number; + /**Count of foreground bytes read */ + foreground_bytes_read: number; + /**Count of background bytes read */ + foreground_bytes_written: number; + /**Count of foreground read operations */ + foreground_num_read_operations: number; + /**Count of foreground write operations */ + foreground_num_write_options: number; + /**Count of foreground flushes */ + foreground_number_of_flushes: number; + /**Count of background bytes read */ + background_bytes_read: number; + /**Count of background write operations */ + background_bytes_written: number; + /**Count of background read operations */ + background_num_read_operations: number; + /**Count of background write operations */ + background_num_write_operations: number; + /**Count of background flushes */ + background_number_of_flushes: number; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with the timeline of an application's execution `{5C8CF1C7-7257-4F13-B223-970EF5939312}` + */ +export interface ApplicationTimeline { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Flags associated with entry */ + flags: number; + /**End time of entry */ + end_time: string; + /**Duration of timeline in microseconds */ + duration_ms: number; + /**Span of timeline in microseconds */ + span_ms: number; + /**Timeline end for entry */ + timeline_end: number; + /**In focus value for entry */ + in_focus_timeline: number; + /**User input value for entry */ + user_input_timeline: number; + /**Comp rendered value for entry */ + comp_rendered_timeline: number; + /**Comp dirtied value for entry */ + comp_dirtied_timeline: number; + /**Comp propagated value for entry */ + comp_propagated_timeline: number; + /**Audio input value for entry */ + audio_in_timeline: number; + /**Audio output value for entry */ + audio_out_timeline: number; + /**CPU value for entry */ + cpu_timeline: number; + /**Disk value for entry */ + disk_timeline: number; + /**Network value for entry */ + network_timeline: number; + /**MBB value for entry */ + mbb_timeline: number; + /**In focus seconds count */ + in_focus_s: number; + /**PSM foreground seconds count */ + psm_foreground_s: number; + /**User input seconds count */ + user_input_s: number; + /**Comp rendered seconds count */ + comp_rendered_s: number; + /**Comp dirtied seconds count */ + comp_dirtied_s: number; + /**Comp propagated seconds count */ + comp_propagated_s: number; + /**Audio input seconds count */ + audio_in_s: number; + /**Audio output seconds count */ + audio_out_s: number; + /**Cycles value for entry */ + cycles: number; + /**Cycles breakdown value for entry */ + cycles_breakdown: number; + /**Cycles attribute value for entry */ + cycles_attr: number; + /**Cycles attribute breakdown for entry */ + cycles_attr_breakdown: number; + /**Cycles WOB value for entry */ + cycles_wob: number; + /**Cycles WOB breakdown value for entry */ + cycles_wob_breakdown: number; + /**Disk raw value for entry */ + disk_raw: number; + /**Network tail raw value for entry */ + network_tail_raw: number; + /**Network bytes associated with entry*/ + network_bytes_raw: number; + /**MBB tail raw value for entry */ + mbb_tail_raw: number; + /**MBB bytes associated with entry */ + mbb_bytes_raw: number; + /**Display required seconds count */ + display_required_s: number; + /**Display required timeline value for entry */ + display_required_timeline: number; + /**Keyboard input timeline value for entry */ + keyboard_input_timeline: number; + /**Keyboard input seconds count */ + keyboard_input_s: number; + /**Mouse input seconds count */ + mouse_input_s: number; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with VFU `{7ACBBAA3-D029-4BE4-9A7A-0885927F1D8F}`. Unsure what this tracks. + */ +export interface AppVfu { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Flags associated with VFU entry */ + flags: number; + /**Start time associated with VFU entry */ + start_time: string; + /**End time associated with VFU entry */ + end_time: string; + /**Base64 encoded usage data associated with VFU entry */ + usage: string; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with EnergyInfo `{DA73FB89-2BEA-4DDC-86B8-6E048C6DA477}` + */ +export interface EnergyInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Base64 encoded binary data associated with EnergyInfo entry */ + binary_data: string; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with EnergyUsage `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}` and `{FEE4E14F-02A9-4550-B5CE-5FA2DA202E37}LT` + */ +export interface EnergyUsage { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Event Timestamp */ + event_timestamp: string; + /**State transition associated with entry */ + state_transition: number; + /**Full charged capacity associated with entry */ + full_charged_capacity: number; + /**Designed capacity associated with entry */ + designed_capacity: number; + /** Charge level associated with entry */ + charge_level: number; + /**Cycle count associated with entry */ + cycle_count: number; + /**Configuration hash associated with entry */ + configuration_hash: number; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with NetworkInfo `{973F5D5C-1D90-4944-BE8E-24B94231A174}` + */ +export interface NetworkInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Interface luid associated with entry */ + interface_luid: number; + /**L2 profile ID associated with entry */ + l2_profile_id: number; + /**L2 profile flags associated with entry */ + l2_profile_flags: number; + /**Bytes sent associated with entry */ + bytes_sent: number; + /**Bytes received associated with entry */ + bytes_recvd: number; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with NetworkConnectivityInfo `{DD6636C4-8929-4683-974E-22C046A43763}` + */ +export interface NetworkConnectivityInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Interface luid associated with entry */ + interface_luid: number; + /**L2 profile ID associated with entry */ + l2_profile_id: number; + /**Connected time associated with entry */ + connected_time: number; + /*Connect start time associated with entry*/ + connect_start_time: number; + /**L2 profile flags associated with entry */ + l2_profile_flags: number; + /**Path to the SRUM file */ + evidence: string; +} + +/** + * SRUM table associated with NotificationInfo `{D10CA2FE-6FCF-4F6D-848E-B2E99266FA86}` + */ +export interface NotificationInfo { + /**ID in for row in the ESE table */ + auto_inc_id: number; + /**Timestamp when ESE table was updated */ + timestamp: string; + /**Application name */ + app_id: string; + /**SID associated with the application process */ + user_id: string; + /**Notification type associated with entry */ + notification_type: number; + /**Size of payload associated with entry */ + payload_size: number; + /**Network type associated with entry */ + network_type: number; + /**Path to the SRUM file */ + evidence: string; +} diff --git a/types/windows/tasks.ts b/types/windows/tasks.ts index cada40b6..ccfaf76f 100644 --- a/types/windows/tasks.ts +++ b/types/windows/tasks.ts @@ -1,512 +1,512 @@ -/** - * `Schedule Tasks` are a common form of Persistence on Windows systems. There are two (2) types of `Task` files: - * - XML based `Task` files - * - Job based `Task` files - * - * Starting on Windows Vista and higher XML files are used for `Schedule Tasks`. - * - * References: - * - https://github.com/libyal/dtformats/blob/main/documentation/Job%20file%20format.asciidoc - * - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f - */ - -/** - * JSON representation of the Task XML schema. - * Most of the schema is Optional. Only `Actions` is required - */ -export interface TaskXml { - /**Registration Info about the Task */ - registrationInfo?: RegistrationInfo; - /**Triggers that start the Task */ - triggers?: Triggers; - /**Settings for the Task */ - settings?: Settings; - /**Base64 encoded raw binary data associated with the Task */ - data?: string; - /**Principal user information related to the Task */ - principals?: Principals; - /**Actions executed by the Task */ - actions: Actions; - /**Path to the XML file */ - path: string; -} - -/** - * Parsed information about the Job file - */ -export interface TaskJob { - /**ID associated with the Task */ - job_id: string; - /**Error retry count for the Task */ - error_retry_count: number; - /**Error retry interval for the Task */ - error_retry_interval: number; - /**Idle deadline for Task */ - idle_deadline: number; - /**Idle wait for Task */ - idle_wait: number; - /**Task Priority */ - priority: string; - /**Max run time for Task */ - max_run_time: number; - /**Task Exit code */ - exit_code: number; - /**Task Status */ - status: string; - /**Flags associated with Task */ - flags: string[]; - /**Last run time for Task in LOCALTIME */ - system_time: string; - /**Running count for Task */ - running_instance_count: number; - /**Application name associated with Task */ - application_name: string; - /**Parameters for application */ - parameters: string; - /**Working directory associated with Task */ - working_directory: string; - /**Creator of Task */ - author: string; - /**Comments associated with Task */ - comments: string; - /**Base64 encoded User data associated with Task */ - user_data: string; - /**Start Error associated with Task */ - start_error: number; - /**Triggers that start the Task */ - triggers: JobTriggers[]; - /**Path to Job file */ - path: string; -} - -/** - * Triggers associated with Job file - */ -interface JobTriggers { - /**Task start date */ - start_date: string; - /**Task end date */ - end_date: string; - /**Task start time */ - start_time: string; - /**Task duration */ - duration: number; - /**Task interval */ - interval_mins: number; - /**Array of trigger flags */ - flags: string[]; - /**Array of trigger types */ - types: string[]; -} - -/** - * Registration Info related to Task XML - */ -interface RegistrationInfo { - /**URI associated with */ - uri?: string; - /**SID associated with Task */ - sid?: string; - /**Source of Task */ - source?: string; - /**Creation OR Modification of Task */ - date?: string; - /**Creator of Task */ - author?: string; - /**Version level of Task */ - version?: string; - /**User-friendly description of Task */ - description?: string; - /**URI of external documentation for Task */ - documentation?: string; -} - -/** - * Triggers that active the Task - */ -interface Triggers { - /**Boot triggers for Task */ - boot: BootTrigger[]; - /**Registration triggers for Task. Format is exactly same as BootTrigger*/ - registration: BootTrigger[]; - /**Idle triggers for Task */ - idle: IdleTrigger[]; - /**Time triggers for Task */ - time: TimeTrigger[]; - /**Event triggers for Task */ - event: EventTrigger[]; - /**Logon triggers for Task */ - logon: LogonTrigger[]; - /**Session triggers for Task */ - session: SessionTrigger[]; - /**Calendar triggers for Task */ - calendar: CalendarTrigger[]; - /**Windows Notifications triggers for Task */ - wnf: WnfTrigger[]; -} - -/** - * Most Triggers have a collection of common options - */ -interface BaseTriggers { - /**ID for trigger */ - id?: string; - /**Start date for Task */ - start_boundary?: string; - /**End date for Task */ - end_boundary?: string; - /**Bool value to activate Trigger */ - enabled?: boolean; - /**Time limit for Task */ - execution_time_limit?: string; - /**Repetition for Task */ - repetition?: Repetition; -} - -/** - * Repetition Options for Triggers - */ -interface Repetition { - /**Trigger restart intervals */ - interval: string; - /**Repetition can stop after duration has elapsed */ - duration?: string; - /**Task can stop at end of duration */ - stop_at_duration_end?: boolean; -} - -/** - * Boot options to Trigger Task - */ -interface BootTrigger { - /**Base Triggers associated with Boot */ - common?: BaseTriggers; - /**Task delayed after boot */ - delay?: string; -} - -/** - * Idle options to Trigger Task - */ -interface IdleTrigger { - /**Base Triggers associated with Idle */ - common?: BaseTriggers; -} - -/** - * Time options to Trigger Task - */ -interface TimeTrigger { - /**Base Triggers associated with Time */ - common?: BaseTriggers; - /**Delay time for `start_boundary` */ - random_delay?: string; -} - -/** - * Event options to Trigger Task - */ -interface EventTrigger { - /**Base Triggers associated with Event */ - common?: BaseTriggers; - /**Array of subscriptions that can Trigger the Task */ - subscription: string[]; - /**Delay to Trigger the Task */ - delay?: string; - /**Trigger can start Task after `number_of_occurrences` */ - number_of_occurrences?: number; - /**Trigger can start Task after `period_of_occurrence` */ - period_of_occurrence?: string; - /**Specifies XML field name */ - matching_element?: string; - /**Specifies set of XML elements */ - value_queries?: string[]; -} - -/** - * Logon options to Trigger Task - */ -interface LogonTrigger { - /**Base Triggers associated with Logon */ - common?: BaseTriggers; - /**Account name associated with Logon Trigger */ - user_id?: string; - /**Delay Logon Task Trigger */ - delay?: string; -} - -/** - * Session options to Trigger Task - */ -interface SessionTrigger { - /**Base Triggers associated with Session */ - common?: BaseTriggers; - /**Account name associated with Session Trigger */ - user_id?: string; - /**Delay Session Task Trigger */ - delay?: string; - /**Session change that Triggers Task */ - state_change?: string; -} - -/** - * Windows Notification options to Trigger Task - */ -interface WnfTrigger { - /**Base Triggers associated with Windows Notification */ - common?: BaseTriggers; - /**Notification State name */ - state_name: string; - /**Delay Notification Trigger Task */ - delay?: string; - /**Data associated with Notification Trigger */ - data?: string; - /**Offset associated with Notification Trigger */ - data_offset?: string; -} - -/** - * Calendar Options to Trigger Task - */ -interface CalendarTrigger { - /**Base Triggers associated with Calendar */ - common?: BaseTriggers; - /**Delay Calendar Trigger Task */ - random_delay?: string; - /**Run Task on every X number of days */ - schedule_by_day?: ByDay; - /**Run Task on every X number of weeks */ - schedule_by_week?: ByWeek; - /**Run Task on specific days of month */ - schedule_by_month?: ByMonth; - /**Run Task on specific weeks on specific days */ - schedule_by_month_day_of_week?: ByMonthDayWeek; -} - -/** - * How often to run Task by days - */ -interface ByDay { - /**Run Task on X number of days. Ex: Two (2) means every other day */ - days_interval?: number; -} - -/** - * How often to run Task by Weeks - */ -interface ByWeek { - /**Run Task on X number of weeks. Ex: Two (2) means every other week */ - weeks_interval?: number; - /**Runs on specified days of the week. Ex: Monday, Tuesday */ - days_of_week?: string[]; -} - -/** - * How often to run Task by Months - */ -interface ByMonth { - /**Days of month to run Task */ - days_of_month?: string[]; - /**Months to run Task. Ex: July, August */ - months?: string[]; -} - -/**How often to run Tasks by Months and Weeks */ -interface ByMonthDayWeek { - /**Weeks of month to run Task */ - weeks?: string[]; - /**Days of month to run Task */ - days_of_week?: string[]; - /**Months to run Task */ - months?: string[]; -} - -/** - * Settings determine how to run Task Actions - */ -interface Settings { - /**Start Task on demand */ - allow_start_on_demand?: boolean; - /**Restart if fails */ - restart_on_failure?: RestartType; - /**Determines how Windows handles multiple Task executions */ - multiple_instances_policy?: string; - /**Disable Task on battery power */ - disallow_start_if_on_batteries?: boolean; - /**Stop Task if going on battery power */ - stop_if_going_on_batteries?: boolean; - /**Task can be terminated if time limits exceeded */ - allow_hard_terminate?: boolean; - /**If scheduled time is missed, Task may be started */ - start_when_available?: boolean; - /**Run based on network profile name */ - network_profile_name?: string; - /**Run only if network connection available */ - run_only_if_network_available?: boolean; - /**Wake system from standby or hibernate to run */ - wake_to_run?: boolean; - /**Task is enabled */ - enabled?: boolean; - /**Task is hidden from console or GUI */ - hidden?: boolean; - /**Delete Task after specified duration and no future run times */ - delete_expired_tasks_after?: string; - /**Options to run when Idle */ - idle_settings?: IdleSettings; - /**Network settings to run */ - network_settings?: NetworkSettings; - /**Task execution time limit */ - execution_time_limit?: string; - /**Task Priority. Lowest is 1. Highest is 10 */ - priority?: number; - /**Only run if system is Idle */ - run_only_if_idle?: boolean; - /**Use unified scheduling engine to handle Task execution */ - use_unified_scheduling_engine?: boolean; - /**Task is disabled on Remote App Sessions */ - disallow_start_on_remote_app_session?: boolean; - /**Options to run Task during system maintenance periods */ - maintenance?: MaintenanceSettings; - /**Task disabled on next OS startup */ - volatile?: boolean; -} - -/** - * Restart on failure options - */ -interface RestartType { - /**Duration between restarts */ - interval: string; - /**Number of restart attempts */ - count: number; -} - -/** - * Idle options - */ -interface IdleSettings { - /**Task may be delayed up until specified duration */ - duration?: string; - /**Task will wait for system to become idle */ - wait_timeout?: string; - /**Task stops if system is no longer Idle */ - stop_on_idle_end?: boolean; - /**Task restarts when system returns to Idle */ - restart_on_idle?: boolean; -} - -/** - * Network options - */ -interface NetworkSettings { - /**Task runs only on specified network name */ - name?: string; - /**GUID associated with `NetworkSettings` */ - id?: string; -} - -/** - * Maintenance options - */ -interface MaintenanceSettings { - /**Duration of maintenance */ - period: string; - /**Deadline for Task to run */ - deadline?: string; - /**Task can run independently of other Tasks with `MaintenanceSettings` */ - exclusive?: boolean; -} - -/** - * SID data associated with Task - */ -interface Principals { - /**Principal name for running the Task */ - user_id?: string; - /**Determines if Task run on logon */ - logon_type?: string; - /**Group ID associated with Task. Task can be triggered by anyone in Group ID */ - group_id?: string; - /**Friendly name of the principal */ - display_name?: string; - /**Privilege level of Task */ - run_level?: string; - /**Process Token SID associated with Task */ - process_token_sid_type?: string; - /**Array of privileges value */ - required_privileges?: string[]; - /**Unique user selected ID */ - id_attribute?: string; -} - -/** - * Actions run by the Task - */ -interface Actions { - /**Executes one or more commands */ - exec: ExecType[]; - /**COM handler to execute */ - com_handler: ComHandlerType[]; - /**Send an email */ - send_email: SendEmail[]; - /**Display a message */ - show_message: Message[]; -} - -/** - * Command options - */ -interface ExecType { - /**Command to execute */ - command: string; - /**Arguments for command */ - arguments?: string; - /**Path to a directory */ - working_directory?: string; -} - -/** - * COM options - */ -interface ComHandlerType { - /**COM GUID */ - class_id: string; - /**XML data for COM */ - data?: string; -} - -/** - * SendEmail options - */ -interface SendEmail { - /**Email server domain */ - server?: string; - /**Subject of email */ - subject?: string; - /**Who should received email */ - to?: string; - /**Who should be CC'd */ - cc?: string; - /**Who should be BCC'd */ - bcc?: string; - /**Reply to email address */ - reply_to?: string; - /**The sender email address */ - from: string; - /**Custom header fields to include in email */ - header_fields?: Record; - /**Email message body */ - body?: string; - /**List of files to be attached */ - attachment?: string[]; -} - -/** - * Message options - */ -interface Message { - /**Title of message */ - title?: string; - /**Message body */ - body: string; -} +/** + * `Schedule Tasks` are a common form of Persistence on Windows systems. There are two (2) types of `Task` files: + * - XML based `Task` files + * - Job based `Task` files + * + * Starting on Windows Vista and higher XML files are used for `Schedule Tasks`. + * + * References: + * - https://github.com/libyal/dtformats/blob/main/documentation/Job%20file%20format.asciidoc + * - https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-tsch/0d6383e4-de92-43e7-b0bb-a60cfa36379f + */ + +/** + * JSON representation of the Task XML schema. + * Most of the schema is Optional. Only `Actions` is required + */ +export interface TaskXml { + /**Registration Info about the Task */ + registrationInfo?: RegistrationInfo; + /**Triggers that start the Task */ + triggers?: Triggers; + /**Settings for the Task */ + settings?: Settings; + /**Base64 encoded raw binary data associated with the Task */ + data?: string; + /**Principal user information related to the Task */ + principals?: Principals; + /**Actions executed by the Task */ + actions: Actions; + /**Path to the XML file */ + evidence: string; +} + +/** + * Parsed information about the Job file + */ +export interface TaskJob { + /**ID associated with the Task */ + job_id: string; + /**Error retry count for the Task */ + error_retry_count: number; + /**Error retry interval for the Task */ + error_retry_interval: number; + /**Idle deadline for Task */ + idle_deadline: number; + /**Idle wait for Task */ + idle_wait: number; + /**Task Priority */ + priority: string; + /**Max run time for Task */ + max_run_time: number; + /**Task Exit code */ + exit_code: number; + /**Task Status */ + status: string; + /**Flags associated with Task */ + flags: string[]; + /**Last run time for Task in LOCALTIME */ + system_time: string; + /**Running count for Task */ + running_instance_count: number; + /**Application name associated with Task */ + application_name: string; + /**Parameters for application */ + parameters: string; + /**Working directory associated with Task */ + working_directory: string; + /**Creator of Task */ + author: string; + /**Comments associated with Task */ + comments: string; + /**Base64 encoded User data associated with Task */ + user_data: string; + /**Start Error associated with Task */ + start_error: number; + /**Triggers that start the Task */ + triggers: JobTriggers[]; + /**Path to Job file */ + evidence: string; +} + +/** + * Triggers associated with Job file + */ +interface JobTriggers { + /**Task start date */ + start_date: string; + /**Task end date */ + end_date: string; + /**Task start time */ + start_time: string; + /**Task duration */ + duration: number; + /**Task interval */ + interval_mins: number; + /**Array of trigger flags */ + flags: string[]; + /**Array of trigger types */ + types: string[]; +} + +/** + * Registration Info related to Task XML + */ +interface RegistrationInfo { + /**URI associated with */ + uri?: string; + /**SID associated with Task */ + sid?: string; + /**Source of Task */ + source?: string; + /**Creation OR Modification of Task */ + date?: string; + /**Creator of Task */ + author?: string; + /**Version level of Task */ + version?: string; + /**User-friendly description of Task */ + description?: string; + /**URI of external documentation for Task */ + documentation?: string; +} + +/** + * Triggers that active the Task + */ +interface Triggers { + /**Boot triggers for Task */ + boot: BootTrigger[]; + /**Registration triggers for Task. Format is exactly same as BootTrigger*/ + registration: BootTrigger[]; + /**Idle triggers for Task */ + idle: IdleTrigger[]; + /**Time triggers for Task */ + time: TimeTrigger[]; + /**Event triggers for Task */ + event: EventTrigger[]; + /**Logon triggers for Task */ + logon: LogonTrigger[]; + /**Session triggers for Task */ + session: SessionTrigger[]; + /**Calendar triggers for Task */ + calendar: CalendarTrigger[]; + /**Windows Notifications triggers for Task */ + wnf: WnfTrigger[]; +} + +/** + * Most Triggers have a collection of common options + */ +interface BaseTriggers { + /**ID for trigger */ + id?: string; + /**Start date for Task */ + start_boundary?: string; + /**End date for Task */ + end_boundary?: string; + /**Bool value to activate Trigger */ + enabled?: boolean; + /**Time limit for Task */ + execution_time_limit?: string; + /**Repetition for Task */ + repetition?: Repetition; +} + +/** + * Repetition Options for Triggers + */ +interface Repetition { + /**Trigger restart intervals */ + interval: string; + /**Repetition can stop after duration has elapsed */ + duration?: string; + /**Task can stop at end of duration */ + stop_at_duration_end?: boolean; +} + +/** + * Boot options to Trigger Task + */ +interface BootTrigger { + /**Base Triggers associated with Boot */ + common?: BaseTriggers; + /**Task delayed after boot */ + delay?: string; +} + +/** + * Idle options to Trigger Task + */ +interface IdleTrigger { + /**Base Triggers associated with Idle */ + common?: BaseTriggers; +} + +/** + * Time options to Trigger Task + */ +interface TimeTrigger { + /**Base Triggers associated with Time */ + common?: BaseTriggers; + /**Delay time for `start_boundary` */ + random_delay?: string; +} + +/** + * Event options to Trigger Task + */ +interface EventTrigger { + /**Base Triggers associated with Event */ + common?: BaseTriggers; + /**Array of subscriptions that can Trigger the Task */ + subscription: string[]; + /**Delay to Trigger the Task */ + delay?: string; + /**Trigger can start Task after `number_of_occurrences` */ + number_of_occurrences?: number; + /**Trigger can start Task after `period_of_occurrence` */ + period_of_occurrence?: string; + /**Specifies XML field name */ + matching_element?: string; + /**Specifies set of XML elements */ + value_queries?: string[]; +} + +/** + * Logon options to Trigger Task + */ +interface LogonTrigger { + /**Base Triggers associated with Logon */ + common?: BaseTriggers; + /**Account name associated with Logon Trigger */ + user_id?: string; + /**Delay Logon Task Trigger */ + delay?: string; +} + +/** + * Session options to Trigger Task + */ +interface SessionTrigger { + /**Base Triggers associated with Session */ + common?: BaseTriggers; + /**Account name associated with Session Trigger */ + user_id?: string; + /**Delay Session Task Trigger */ + delay?: string; + /**Session change that Triggers Task */ + state_change?: string; +} + +/** + * Windows Notification options to Trigger Task + */ +interface WnfTrigger { + /**Base Triggers associated with Windows Notification */ + common?: BaseTriggers; + /**Notification State name */ + state_name: string; + /**Delay Notification Trigger Task */ + delay?: string; + /**Data associated with Notification Trigger */ + data?: string; + /**Offset associated with Notification Trigger */ + data_offset?: string; +} + +/** + * Calendar Options to Trigger Task + */ +interface CalendarTrigger { + /**Base Triggers associated with Calendar */ + common?: BaseTriggers; + /**Delay Calendar Trigger Task */ + random_delay?: string; + /**Run Task on every X number of days */ + schedule_by_day?: ByDay; + /**Run Task on every X number of weeks */ + schedule_by_week?: ByWeek; + /**Run Task on specific days of month */ + schedule_by_month?: ByMonth; + /**Run Task on specific weeks on specific days */ + schedule_by_month_day_of_week?: ByMonthDayWeek; +} + +/** + * How often to run Task by days + */ +interface ByDay { + /**Run Task on X number of days. Ex: Two (2) means every other day */ + days_interval?: number; +} + +/** + * How often to run Task by Weeks + */ +interface ByWeek { + /**Run Task on X number of weeks. Ex: Two (2) means every other week */ + weeks_interval?: number; + /**Runs on specified days of the week. Ex: Monday, Tuesday */ + days_of_week?: string[]; +} + +/** + * How often to run Task by Months + */ +interface ByMonth { + /**Days of month to run Task */ + days_of_month?: string[]; + /**Months to run Task. Ex: July, August */ + months?: string[]; +} + +/**How often to run Tasks by Months and Weeks */ +interface ByMonthDayWeek { + /**Weeks of month to run Task */ + weeks?: string[]; + /**Days of month to run Task */ + days_of_week?: string[]; + /**Months to run Task */ + months?: string[]; +} + +/** + * Settings determine how to run Task Actions + */ +interface Settings { + /**Start Task on demand */ + allow_start_on_demand?: boolean; + /**Restart if fails */ + restart_on_failure?: RestartType; + /**Determines how Windows handles multiple Task executions */ + multiple_instances_policy?: string; + /**Disable Task on battery power */ + disallow_start_if_on_batteries?: boolean; + /**Stop Task if going on battery power */ + stop_if_going_on_batteries?: boolean; + /**Task can be terminated if time limits exceeded */ + allow_hard_terminate?: boolean; + /**If scheduled time is missed, Task may be started */ + start_when_available?: boolean; + /**Run based on network profile name */ + network_profile_name?: string; + /**Run only if network connection available */ + run_only_if_network_available?: boolean; + /**Wake system from standby or hibernate to run */ + wake_to_run?: boolean; + /**Task is enabled */ + enabled?: boolean; + /**Task is hidden from console or GUI */ + hidden?: boolean; + /**Delete Task after specified duration and no future run times */ + delete_expired_tasks_after?: string; + /**Options to run when Idle */ + idle_settings?: IdleSettings; + /**Network settings to run */ + network_settings?: NetworkSettings; + /**Task execution time limit */ + execution_time_limit?: string; + /**Task Priority. Lowest is 1. Highest is 10 */ + priority?: number; + /**Only run if system is Idle */ + run_only_if_idle?: boolean; + /**Use unified scheduling engine to handle Task execution */ + use_unified_scheduling_engine?: boolean; + /**Task is disabled on Remote App Sessions */ + disallow_start_on_remote_app_session?: boolean; + /**Options to run Task during system maintenance periods */ + maintenance?: MaintenanceSettings; + /**Task disabled on next OS startup */ + volatile?: boolean; +} + +/** + * Restart on failure options + */ +interface RestartType { + /**Duration between restarts */ + interval: string; + /**Number of restart attempts */ + count: number; +} + +/** + * Idle options + */ +interface IdleSettings { + /**Task may be delayed up until specified duration */ + duration?: string; + /**Task will wait for system to become idle */ + wait_timeout?: string; + /**Task stops if system is no longer Idle */ + stop_on_idle_end?: boolean; + /**Task restarts when system returns to Idle */ + restart_on_idle?: boolean; +} + +/** + * Network options + */ +interface NetworkSettings { + /**Task runs only on specified network name */ + name?: string; + /**GUID associated with `NetworkSettings` */ + id?: string; +} + +/** + * Maintenance options + */ +interface MaintenanceSettings { + /**Duration of maintenance */ + period: string; + /**Deadline for Task to run */ + deadline?: string; + /**Task can run independently of other Tasks with `MaintenanceSettings` */ + exclusive?: boolean; +} + +/** + * SID data associated with Task + */ +interface Principals { + /**Principal name for running the Task */ + user_id?: string; + /**Determines if Task run on logon */ + logon_type?: string; + /**Group ID associated with Task. Task can be triggered by anyone in Group ID */ + group_id?: string; + /**Friendly name of the principal */ + display_name?: string; + /**Privilege level of Task */ + run_level?: string; + /**Process Token SID associated with Task */ + process_token_sid_type?: string; + /**Array of privileges value */ + required_privileges?: string[]; + /**Unique user selected ID */ + id_attribute?: string; +} + +/** + * Actions run by the Task + */ +interface Actions { + /**Executes one or more commands */ + exec: ExecType[]; + /**COM handler to execute */ + com_handler: ComHandlerType[]; + /**Send an email */ + send_email: SendEmail[]; + /**Display a message */ + show_message: Message[]; +} + +/** + * Command options + */ +interface ExecType { + /**Command to execute */ + command: string; + /**Arguments for command */ + arguments?: string; + /**Path to a directory */ + working_directory?: string; +} + +/** + * COM options + */ +interface ComHandlerType { + /**COM GUID */ + class_id: string; + /**XML data for COM */ + data?: string; +} + +/** + * SendEmail options + */ +interface SendEmail { + /**Email server domain */ + server?: string; + /**Subject of email */ + subject?: string; + /**Who should received email */ + to?: string; + /**Who should be CC'd */ + cc?: string; + /**Who should be BCC'd */ + bcc?: string; + /**Reply to email address */ + reply_to?: string; + /**The sender email address */ + from: string; + /**Custom header fields to include in email */ + header_fields?: Record; + /**Email message body */ + body?: string; + /**List of files to be attached */ + attachment?: string[]; +} + +/** + * Message options + */ +interface Message { + /**Title of message */ + title?: string; + /**Message body */ + body: string; +} diff --git a/types/windows/userassist.ts b/types/windows/userassist.ts index 45974282..c9bcc8b7 100644 --- a/types/windows/userassist.ts +++ b/types/windows/userassist.ts @@ -1,21 +1,23 @@ -/** - * Windows `UserAssist` is a Registry artifact that records applications executed via Windows Explorer. - * These entries are typically ROT13 encoded (though this can be disabled) - * - * References: - * - https://winreg-kb.readthedocs.io/en/latest/sources/explorer-keys/User-assist.html - */ -export interface UserAssist { - /**Path of executed application */ - path: string; - /**Last execution time of application */ - last_execution: string; - /**Number of times executed */ - count: number; - /**Registry path to UserAssist entry */ - reg_path: string; - /**ROT13 encoded path */ - rot_path: string; - /**Path of executed application with folder description GUIDs resolved */ - folder_path: string; -} +/** + * Windows `UserAssist` is a Registry artifact that records applications executed via Windows Explorer. + * These entries are typically ROT13 encoded (though this can be disabled) + * + * References: + * - https://winreg-kb.readthedocs.io/en/latest/sources/explorer-keys/User-assist.html + */ +export interface UserAssist { + /**Path of executed application */ + path: string; + /**Last execution time of application */ + last_execution: string; + /**Number of times executed */ + count: number; + /**Registry path to UserAssist entry */ + reg_path: string; + /**ROT13 encoded path */ + rot_path: string; + /**Path of executed application with folder description GUIDs resolved */ + folder_path: string; + /**Path to the Registry file */ + evidence: string; +} diff --git a/types/windows/users.ts b/types/windows/users.ts index 634373db..2b59db2a 100644 --- a/types/windows/users.ts +++ b/types/windows/users.ts @@ -1,29 +1,31 @@ -/** - * Parse the SAM Registry file and get user info - */ -export interface UserInfo { - /**Last logon for account */ - last_logon: string; - /**Time when password last set */ - password_last_set: string; - /**Last password failure */ - last_password_failure: string; - /**Relative ID for account. Typically last number of SID */ - relative_id: number; - /**Primary group ID for account */ - primary_group_id: number; - /**UAC flags associated with account */ - user_account_control_flags: string[]; - /**Country code for account */ - country_code: number; - /**Code page for account */ - code_page: number; - /**Number of password failures associated with account */ - number_password_failures: number; - /**Number of logons for account */ - number_logons: number; - /**Username for account */ - username: string; - /**SID for account */ - sid: string; -} +/** + * Parse the SAM Registry file and get user info + */ +export interface UserInfo { + /**Last logon for account */ + last_logon: string; + /**Time when password last set */ + password_last_set: string; + /**Last password failure */ + last_password_failure: string; + /**Relative ID for account. Typically last number of SID */ + relative_id: number; + /**Primary group ID for account */ + primary_group_id: number; + /**UAC flags associated with account */ + user_account_control_flags: string[]; + /**Country code for account */ + country_code: number; + /**Code page for account */ + code_page: number; + /**Number of password failures associated with account */ + number_password_failures: number; + /**Number of logons for account */ + number_logons: number; + /**Username for account */ + username: string; + /**SID for account */ + sid: string; + /**Path to the Registry file */ + evidence: string; +} diff --git a/types/windows/usnjrnl.ts b/types/windows/usnjrnl.ts index bf86ef3e..dc32cb64 100644 --- a/types/windows/usnjrnl.ts +++ b/types/windows/usnjrnl.ts @@ -1,38 +1,40 @@ -/** - * Windows `UsnJrnl` is a sparse binary file that tracks changes to files and directories. - * Located at the alternative data stream (ADS) `\:\$Extend\$UsnJrnl:$J`. - * Parsing this data can sometimes show files that have been deleted. However, depending on the file activity - * on the system entries in the `UsnJrnl` may get overwritten quickly - * - * References: - * - https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-usn_record_v2?redirectedfrom=MSDN - * - https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#usn_change_journal - */ -export interface UsnJrnl { - /**Entry number in the MFT */ - mft_entry: number; - /**Sequence number in the MFT */ - mft_sequence: number; - /**Parent entry number in the MFT */ - parent_mft_entry: number; - /**Parent sequence number in the MFT */ - parent_mft_sequence: number; - /**ID number in the Update Sequence Number Journal (UsnJrnl) */ - update_sequence_number: number; - /**Timestamp of of entry update */ - update_time: string; - /**Reason for update action */ - update_reason: string; - /**Source information of the update */ - update_source_flags: string; - /**Security ID associated with entry */ - security_descriptor_id: number; - /**Attributes associate with entry */ - file_attributes: string[]; - /**Name associated with entry. Can be file or directory */ - filename: string; - /**Extension if available for filename */ - extension: string; - /**Full path for the UsnJrnl entry. Obtained by parsing `$MFT` and referencing the `parent_mft_entry` */ - full_path: string; -} +/** + * Windows `UsnJrnl` is a sparse binary file that tracks changes to files and directories. + * Located at the alternative data stream (ADS) `\:\$Extend\$UsnJrnl:$J`. + * Parsing this data can sometimes show files that have been deleted. However, depending on the file activity + * on the system entries in the `UsnJrnl` may get overwritten quickly + * + * References: + * - https://learn.microsoft.com/en-us/windows/win32/api/winioctl/ns-winioctl-usn_record_v2?redirectedfrom=MSDN + * - https://github.com/libyal/libfsntfs/blob/main/documentation/New%20Technologies%20File%20System%20(NTFS).asciidoc#usn_change_journal + */ +export interface UsnJrnl { + /**Entry number in the MFT */ + mft_entry: number; + /**Sequence number in the MFT */ + mft_sequence: number; + /**Parent entry number in the MFT */ + parent_mft_entry: number; + /**Parent sequence number in the MFT */ + parent_mft_sequence: number; + /**ID number in the Update Sequence Number Journal (UsnJrnl) */ + update_sequence_number: number; + /**Timestamp of of entry update */ + update_time: string; + /**Reason for update action */ + update_reason: string; + /**Source information of the update */ + update_source_flags: string; + /**Security ID associated with entry */ + security_descriptor_id: number; + /**Attributes associate with entry */ + file_attributes: string[]; + /**Name associated with entry. Can be file or directory */ + filename: string; + /**Extension if available for filename */ + extension: string; + /**Full path for the UsnJrnl entry. Obtained by parsing `$MFT` and referencing the `parent_mft_entry` */ + full_path: string; + /**Path to the UsnJrnl file */ + evidence: string; +} diff --git a/types/windows/wmi.ts b/types/windows/wmi.ts index 4f8c6d35..1355dff0 100644 --- a/types/windows/wmi.ts +++ b/types/windows/wmi.ts @@ -1,289 +1,291 @@ -/** - * Windows Management Instrumentation (WMI) is a collections of tools that allow users to manage the system. - * This parser parses the WMI Repository database typically found at C:\\Windows\\System32\\wbem\\Repository. - * Malware can use WMI to achieve persistence on a system - * - * References: - * - https://docs.velociraptor.app/blog/2022/2022-01-12-wmi-eventing/ - * - https://redcanary.com/threat-detection-report/techniques/windows-management-instrumentation/ - * - https://github.com/libyal/dtformats/blob/main/documentation/WMI%20repository%20file%20format.asciidoc - */ -export interface WmiPersist { - /**SID associated with the WMI entry */ - sid: string; - /**Name of WMI class */ - class: string; - /**Query that triggers the WMI entry */ - query: string; - /**Filter associated with WMI entry */ - filter: string; - /**Consumer associated with WMI entry */ - consumer: string; - /**Name of the Consumer */ - consumer_name: string; - /** Data associated with the WMI entry. Can use `class` to determine what the type is. - * Most common ones are defined, however users can create their own class - */ - values: - | EventLogConsumer - | ActiveScriptConsumer - | CommandLineConsumer - | LogFileConsumer - | SmtpConsumer - | Record; -} - -/** - * Consumer that logs a message to the Windows EventLogs when an event is triggered - */ -export interface EventLogConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Event category */ - Category: number; - /**Name of the event property that contains data */ - NameOfRawDataProperty: string; - /**Event message in the message DLL */ - EvenID: number; - /**Type of event */ - EventType: number; - /**Array of strings to insert for an event log entry */ - InsertionStringTemplates: string[]; - /**Number of strings in `InsertionStringTemplates` */ - NumberOfInsertionStrings: number; - /**SID associated with event */ - NameOfUserSidProperty: string | Uint8Array; - /**Source name where message is located */ - SourceName: string; - /**Name of system on which to log an event */ - UNCServerName: string; -} - -/** - * Consumer to execute a script when an event is triggered - */ -export interface ActiveScriptConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ - KillTimeOut: number; - /**Name of scripting engine to use */ - ScriptingEngine: string; - /**Name of file to execute script. Must be NULL if `ScriptText` is NOT NULL */ - ScriptFileName: string; - /**Contents of script to execute. Must be NULL if `ScriptFileName` is NOT NULL */ - ScriptText: string; -} - -/** - * Consumer to start a process when an event is triggered - */ -export interface CommandLineConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Specifies command to execute */ - CommandLineTemplate: string; - /**Unused */ - CreateNewConsole: boolean; - /**Will create a new process group */ - CreateNewProcessGroup: boolean; - /**New process will run in Virtual DOS Machine (VDM) */ - CreateSeparateWowVdm: boolean; - /**New process will run in shared Virtual DOS Machine (VDM) */ - CreateSharedWowVdm: boolean; - /**Unused */ - DesktopName: string; - /**Specifies the file to execute */ - ExecutablePath: string; - /**Color to use if new console is window is created */ - FillAttributes: number; - /**Cursor feedback is disabled */ - ForceOffFeedback: boolean; - /**Cursor feedback is enabled */ - ForceOnFeedback: boolean; - /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ - KillTimeout: number; - /**Priority of process threads */ - Priority: number; - /**Determines if process is launched with interactive WinStation or default WinStation */ - RunInteractively: boolean; - /**Determines Window show state */ - ShowWindowCommand: number; - /**Whether to use default error mode */ - UseDefaultErrorMode: boolean; - /**Title to use for process */ - WindowTitle: string; - /**Working directory for the process */ - WorkingDirectory: string; - /**X-offset, in pixels, from the left edge of the screen to the left edge of the window, if a new window is created. */ - XCoordinate: number; - /**Screen buffer width, in character columns, if a new console window is created. This property is ignored in a GUI process. */ - XNumCharacters: number; - /**Width, in pixels, of a new window, if a new window is created. */ - XSize: number; - /**Y-offset, in pixels, from the top edge of the screen to the top edge of the window, if a new window is created. */ - YCoordinate: number; - /**Screen buffer height, in character rows, if a new console window is created. This property is ignored in a GUI process. */ - YNumCharacters: number; - /**Height, in pixels, of the new window, if a new window is created. */ - YSize: number; - /**Specifies the initial text and background colors if a new console window is created in a console application */ - FillAttribute: number; -} - -/** - * Consumer to write customer strings to text file (log) when an event is triggered - */ -export interface LogFileConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Whether log file is Unicode or multibyte code file */ - IsUnicode: boolean; - /**Max log file size */ - MaximumFileSize: bigint; - /**String to write to log file */ - Text: string; -} - -/** - * Consumer that sends an email when an event is triggered - */ -export interface SmtpConsumer { - /**Creator of Consumer in bytes */ - CreatorSID: Uint8Array; - /**Name of system where WMI sends events */ - MachineName: string; - /**Max queue for consumer in bytes */ - MaximumQueueSize: number; - /**Unique ID for consumer */ - Name: string; - /**Addresses to send email (BCC) */ - BccLine: string; - /**Addresses to send email (CC) */ - CcLine: string; - /**From address to use to send email. Default is: `WinMgmt@MachineName` */ - FromLine: string; - /**Headers to insert into email */ - HeaderFields: string[]; - /**Body of email */ - Message: string; - /**Reply-to line of an email message */ - ReplyToLine: string; - /**SMTP server to use to send emails */ - SMTPServer: string; - /**Subject line for email */ - Subject: string; - /**Addresses to send email to */ - ToLine: string; -} - -export interface Namespace { - class: string; - /**SHA256 hash of the namespace name */ - clas_hash: string; - name: string; - path: string; - super_class: string; -} - -export interface ClassInfo { - super_class_name: string; - class_name: string; - qualifiers: Qualifier[]; - properties: Property[]; - class_hash: string; - includes_parent_props: boolean; -} - -interface Qualifier { - name: string; - value_data_type: CimType; - data: unknown; -} - -interface Property { - name: string; - property_data_type: CimType; - property_index: number; - data_offset: number; - class_level: number; - qualifiers: Qualifier[]; -} - -enum CimType { - Sint16 = "Sint16", - Sint32 = "Sint32", - Real32 = "Real32", - Real64 = "Real64", - String = "String", - Bool = "Bool", - Object = "Object", - Sint8 = "Sint8", - Uint8 = "Uint8", - Uint16 = "Uint16", - Uint32 = "Uint32", - Sint64 = "Sint64", - Uint64 = "Uint64", - Datetime = "Datetime", - Reference = "Reference", - Char = "Char", - ArrayString = "ArrayString", - ArraySint16 = "ArraySint16", - ArraySint8 = "ArraySint8", - ArraySint32 = "ArraySint32", - ArraySint64 = "ArraySint64", - ArrayReal32 = "ArrayReal32", - ArrayReal64 = "ArrayReal64", - ArrayBool = "ArrayBool", - ArrayUint8 = "ArrayUint8", - ArrayUint16 = "ArrayUint16", - ArrayUint32 = "ArrayUint32", - ArrayUint64 = "ArrayUint64", - ArrayChar = "ArrayChar", - ArrayObject = "ArrayObject", - ByteRefString = "ByteRefString", - ByteRefUint32 = "ByteRefUint32", - ByteRefUint16 = "ByteRefUint64", - ByteRefSint64 = "ByteRefSint64", - ByteRefSint32 = "ByteRefSint32", - ByteRefReal32 = "ByteRefReal32", - ByteRefBool = "ByteRefBool", - ByteRefDatetime = "ByteRefDatetime", - ByteRefUint8 = "ByteRefUint8", - ByteRefReference = "ByteRefReference", - ByteRefObject = "ByteRefObject", - Unknown = "Unknown", - None = "None", -} - -export interface IndexBody { - array_sub_pages: number[]; - array_key_offset: number[]; - key_data: number[]; - array_value_offsets: number[]; - value_data: string[]; +/** + * Windows Management Instrumentation (WMI) is a collections of tools that allow users to manage the system. + * This parser parses the WMI Repository database typically found at C:\\Windows\\System32\\wbem\\Repository. + * Malware can use WMI to achieve persistence on a system + * + * References: + * - https://docs.velociraptor.app/blog/2022/2022-01-12-wmi-eventing/ + * - https://redcanary.com/threat-detection-report/techniques/windows-management-instrumentation/ + * - https://github.com/libyal/dtformats/blob/main/documentation/WMI%20repository%20file%20format.asciidoc + */ +export interface WmiPersist { + /**SID associated with the WMI entry */ + sid: string; + /**Name of WMI class */ + class: string; + /**Query that triggers the WMI entry */ + query: string; + /**Filter associated with WMI entry */ + filter: string; + /**Consumer associated with WMI entry */ + consumer: string; + /**Name of the Consumer */ + consumer_name: string; + /** Data associated with the WMI entry. Can use `class` to determine what the type is. + * Most common ones are defined, however users can create their own class + */ + values: + | EventLogConsumer + | ActiveScriptConsumer + | CommandLineConsumer + | LogFileConsumer + | SmtpConsumer + | Record; + /**Path to the WMI Repository directory */ + evidence: string; +} + +/** + * Consumer that logs a message to the Windows EventLogs when an event is triggered + */ +export interface EventLogConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Event category */ + Category: number; + /**Name of the event property that contains data */ + NameOfRawDataProperty: string; + /**Event message in the message DLL */ + EvenID: number; + /**Type of event */ + EventType: number; + /**Array of strings to insert for an event log entry */ + InsertionStringTemplates: string[]; + /**Number of strings in `InsertionStringTemplates` */ + NumberOfInsertionStrings: number; + /**SID associated with event */ + NameOfUserSidProperty: string | Uint8Array; + /**Source name where message is located */ + SourceName: string; + /**Name of system on which to log an event */ + UNCServerName: string; +} + +/** + * Consumer to execute a script when an event is triggered + */ +export interface ActiveScriptConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ + KillTimeOut: number; + /**Name of scripting engine to use */ + ScriptingEngine: string; + /**Name of file to execute script. Must be NULL if `ScriptText` is NOT NULL */ + ScriptFileName: string; + /**Contents of script to execute. Must be NULL if `ScriptFileName` is NOT NULL */ + ScriptText: string; +} + +/** + * Consumer to start a process when an event is triggered + */ +export interface CommandLineConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Specifies command to execute */ + CommandLineTemplate: string; + /**Unused */ + CreateNewConsole: boolean; + /**Will create a new process group */ + CreateNewProcessGroup: boolean; + /**New process will run in Virtual DOS Machine (VDM) */ + CreateSeparateWowVdm: boolean; + /**New process will run in shared Virtual DOS Machine (VDM) */ + CreateSharedWowVdm: boolean; + /**Unused */ + DesktopName: string; + /**Specifies the file to execute */ + ExecutablePath: string; + /**Color to use if new console is window is created */ + FillAttributes: number; + /**Cursor feedback is disabled */ + ForceOffFeedback: boolean; + /**Cursor feedback is enabled */ + ForceOnFeedback: boolean; + /**How many seconds to wait until process is killed. Zero (0) means process will not be killed */ + KillTimeout: number; + /**Priority of process threads */ + Priority: number; + /**Determines if process is launched with interactive WinStation or default WinStation */ + RunInteractively: boolean; + /**Determines Window show state */ + ShowWindowCommand: number; + /**Whether to use default error mode */ + UseDefaultErrorMode: boolean; + /**Title to use for process */ + WindowTitle: string; + /**Working directory for the process */ + WorkingDirectory: string; + /**X-offset, in pixels, from the left edge of the screen to the left edge of the window, if a new window is created. */ + XCoordinate: number; + /**Screen buffer width, in character columns, if a new console window is created. This property is ignored in a GUI process. */ + XNumCharacters: number; + /**Width, in pixels, of a new window, if a new window is created. */ + XSize: number; + /**Y-offset, in pixels, from the top edge of the screen to the top edge of the window, if a new window is created. */ + YCoordinate: number; + /**Screen buffer height, in character rows, if a new console window is created. This property is ignored in a GUI process. */ + YNumCharacters: number; + /**Height, in pixels, of the new window, if a new window is created. */ + YSize: number; + /**Specifies the initial text and background colors if a new console window is created in a console application */ + FillAttribute: number; +} + +/** + * Consumer to write customer strings to text file (log) when an event is triggered + */ +export interface LogFileConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Whether log file is Unicode or multibyte code file */ + IsUnicode: boolean; + /**Max log file size */ + MaximumFileSize: bigint; + /**String to write to log file */ + Text: string; +} + +/** + * Consumer that sends an email when an event is triggered + */ +export interface SmtpConsumer { + /**Creator of Consumer in bytes */ + CreatorSID: Uint8Array; + /**Name of system where WMI sends events */ + MachineName: string; + /**Max queue for consumer in bytes */ + MaximumQueueSize: number; + /**Unique ID for consumer */ + Name: string; + /**Addresses to send email (BCC) */ + BccLine: string; + /**Addresses to send email (CC) */ + CcLine: string; + /**From address to use to send email. Default is: `WinMgmt@MachineName` */ + FromLine: string; + /**Headers to insert into email */ + HeaderFields: string[]; + /**Body of email */ + Message: string; + /**Reply-to line of an email message */ + ReplyToLine: string; + /**SMTP server to use to send emails */ + SMTPServer: string; + /**Subject line for email */ + Subject: string; + /**Addresses to send email to */ + ToLine: string; +} + +export interface Namespace { + class: string; + /**SHA256 hash of the namespace name */ + clas_hash: string; + name: string; + path: string; + super_class: string; +} + +export interface ClassInfo { + super_class_name: string; + class_name: string; + qualifiers: Qualifier[]; + properties: Property[]; + class_hash: string; + includes_parent_props: boolean; +} + +interface Qualifier { + name: string; + value_data_type: CimType; + data: unknown; +} + +interface Property { + name: string; + property_data_type: CimType; + property_index: number; + data_offset: number; + class_level: number; + qualifiers: Qualifier[]; +} + +enum CimType { + Sint16 = "Sint16", + Sint32 = "Sint32", + Real32 = "Real32", + Real64 = "Real64", + String = "String", + Bool = "Bool", + Object = "Object", + Sint8 = "Sint8", + Uint8 = "Uint8", + Uint16 = "Uint16", + Uint32 = "Uint32", + Sint64 = "Sint64", + Uint64 = "Uint64", + Datetime = "Datetime", + Reference = "Reference", + Char = "Char", + ArrayString = "ArrayString", + ArraySint16 = "ArraySint16", + ArraySint8 = "ArraySint8", + ArraySint32 = "ArraySint32", + ArraySint64 = "ArraySint64", + ArrayReal32 = "ArrayReal32", + ArrayReal64 = "ArrayReal64", + ArrayBool = "ArrayBool", + ArrayUint8 = "ArrayUint8", + ArrayUint16 = "ArrayUint16", + ArrayUint32 = "ArrayUint32", + ArrayUint64 = "ArrayUint64", + ArrayChar = "ArrayChar", + ArrayObject = "ArrayObject", + ByteRefString = "ByteRefString", + ByteRefUint32 = "ByteRefUint32", + ByteRefUint16 = "ByteRefUint64", + ByteRefSint64 = "ByteRefSint64", + ByteRefSint32 = "ByteRefSint32", + ByteRefReal32 = "ByteRefReal32", + ByteRefBool = "ByteRefBool", + ByteRefDatetime = "ByteRefDatetime", + ByteRefUint8 = "ByteRefUint8", + ByteRefReference = "ByteRefReference", + ByteRefObject = "ByteRefObject", + Unknown = "Unknown", + None = "None", +} + +export interface IndexBody { + array_sub_pages: number[]; + array_key_offset: number[]; + key_data: number[]; + array_value_offsets: number[]; + value_data: string[]; } \ No newline at end of file