[
  {
    "path": ".gitattributes",
    "content": "# Set default behaviour, in case users don't have core.autocrlf set.\n* text=auto\n\n# Explicitly declare text files we want to always be normalized and converted\n# to native line endings on checkout.\n*.md            text\n*.gitattributes text\n\n# Declare files that will always have CRLF line endings on checkout.\n*.ps1    text  eol=crlf\n*.psm1   text  eol=crlf\n*.psd1   text  eol=crlf\n*.psc1   text  eol=crlf\n*.ps1xml text  eol=crlf\n*.clixml text  eol=crlf\n*.xml    text  eol=crlf\n*.txt    text  eol=crlf\n*.nuspec text  eol=crlf\n*.reg    text  eol=crlf\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.yml",
    "content": "name: 🐛 Bug\ndescription: You need help installing FLARE-VM or something doesn't work as expected\nlabels: [\":bug: bug\"]\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for helping improving FLARE-VM. Before submitting your issue:\n        - Read the [Troubleshooting section in the README](https://github.com/mandiant/flare-vm#troubleshooting).\n        - We track only bugs related to the installer in this repository. If the issue is related to a concrete tool or package (for example a single package that fails to install), please report it in [VM-Packages](https://github.com/mandiant/VM-Packages/issues/new?assignees=&labels=%3Abug%3A+bug&template=bug.yml).\n        - Check the [open issues](https://github.com/mandiant/flare-vm/issues) and ensure there is not already a similar issue. If there is already a similar issue, please add more details there instead of opening a new one.\n        - Ensure you are running the [latest version of the FLARE-VM installer](https://github.com/mandiant/flare-vm/blob/main/install.ps1).\n        - Ensure your VM satisfies the [requirements](https://github.com/mandiant/flare-vm#requirements) such as having internet connection.\n        - Fill all the requested information accurately in this issue to ensure we are able to help you.\n        - If you know how to solve this problem, please send also a pull request! :pray:\n  - type: textarea\n    id: problem\n    attributes:\n      label: What's the problem?\n      description: Include the actual and expected behavior. The more details, the better!\n    validations:\n      required: true\n  - type: textarea\n    id: steps\n    attributes:\n      label: Steps to Reproduce\n      placeholder: |\n        1. First Step\n        2. Second Step\n        3. and so on…\n    validations:\n      required: true\n  - type: textarea\n    id: environment\n    attributes:\n      label: Environment\n      description: |\n        Include the following details about your environment:\n        - **Virtualization software**: VMWare, VirtualBox, etc.\n        - **VM OS version**: run `(Get-CimInstance Win32_OperatingSystem).version` in Powershell\n        - **VM PowerShell version**: run `$PSVersionTable.PSVersion.ToString()` in Powershell\n        - **VM Chocolatey version**: run `choco --version`\n        - **VM Boxstarter version**: run `choco info -l -r \"boxstarter\"`\n        - **Output of `VM-Get-Host-Info`** that will be available if the `vm.common` package has been install: run `VM-Get-Host-Info` in PowerShell with admin rights\n      placeholder: |\n        - Virtualization software: \n        - VM OS version: \n        - VM PowerShell version: \n        - VM Chocolatey version: \n        - VM Boxstarter version: \n        - Output of `VM-Get-Host-Info`: \n\n    validations:\n      required: true\n  - type: textarea\n    id: extra-info\n    attributes:\n      label: Additional Information\n      description: |\n        Any additional information, configuration or data that might be necessary to understand and reproduce the issue. For example:\n        - Console output\n        - The log files `C:\\ProgramData\\_VM\\log.txt` and `C:\\ProgramData\\chocolatey\\logs\\chocolatey.log`\n\n        Text logs are preferred over screenshots.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.yml",
    "content": "name: 💡 Feature proposal\ndescription: Propose a new feature or improvement.\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for helping improving FLARE-VM. Before submitting your issue:\n        - **If you need help installing FLARE-VM or want to report a bug, use the [bug issue type](https://github.com/mandiant/flare-vm/issues/new?assignees=&labels=%3Abug%3A+bug&template=bug.yml) instead and provide all the information requested there.** Otherwise we won't be able to help you.\n        - We track only features related to the installer in this repository. If the issue is related to a concrete tool or package, please report it in [VM-Packages](https://github.com/mandiant/VM-Packages/issues/new).\n        - Check the [open issues](https://github.com/mandiant/flare-vm/issues) and ensure there is not already a similar issue. If there is already a similar issue, please add more details there instead of opening a new one.\n  - type: textarea\n    id: problem\n    attributes:\n      label: Details\n      description: The more details, the better!\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/workflows/build-vbox.yaml",
    "content": "name: Build & release vbox\n# Create or update a release by adding a body and the built vbox binaries on a tag creation that starts by `vbox-`.\n# The tag can be created in the GH UI (by creating a release) or using git, e.g.:\n# VERSION=1.0.0 && git tag -a vbox-$VERSION origin/main -m \"vbox tag\" && git push origin vbox-$VERSION\n\non:\n  push:\n    tags:\n      - 'vbox-*'\n  workflow_dispatch: # manual trigger for testing\n\npermissions:\n  contents: write\n\njobs:\n  build:\n    # use old linux for better portability\n    runs-on: ubuntu-22.04\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Install build requirements\n        run: python -m pip install --upgrade pip pyinstaller\n      - name: Build standalone executables\n        run: |\n          cd virtualbox\n\n          pyinstaller --onefile --log-level DEBUG vbox-adapter-check.py\n          pyinstaller --onefile --log-level DEBUG vbox-clean-snapshots.py\n\n          ls dist\n      # Only test vbox-clean-snapshots as vbox-adapter-check uses Notify and it fails in GH actions\n      - name: Check  vbox-clean-snapshots runs correctly\n        run: virtualbox/dist/vbox-clean-snapshots --help\n      - name: Make files executable\n        run: |\n          chmod +x virtualbox/dist/*\n          chmod +x virtualbox/install.sh\n      - name: Zip binaries\n        run: zip -rj vbox.zip virtualbox/dist virtualbox/install.sh\n      - name: Upload ZIP to release\n        uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # v2.11.2\n        with:\n          repo_token: ${{ secrets.GITHUB_TOKEN }}\n          file: vbox.zip\n          tag: ${{ github.ref }}\n          overwrite: true\n          body: |\n            **FLARE-VM does not have releases**, as the project relies on external URLs outside of our control, making it impossible to install an older version. This release only includes standalone Linux executables for some of the scripts in the [`virtualbox`](https://github.com/mandiant/flare-vm/tree/main/virtualbox) folder (related to use FLARE-VM in VirtualBox) and a bash script to make it easier to use them. **To install FLARE-VM check the instructions in the [FLARE-VM README](https://github.com/mandiant/flare-vm)**.\n\n            - **vbox-adapter-check**: Print the status of all internet adapters of all VMs in VirtualBox. Useful to detect internet access, which is undesirable for dynamic malware analysis. Compatibility is limited to Linux systems using the GTK graphical toolkit, which includes desktops like GNOME.\n            - **vbox-clean-snapshots**: Delete a snapshot and its children recursively skipping snapshots with a substring in the name. Useful to delete several snapshots, which is not possible via the VirtualBox UI.\n\n"
  },
  {
    "path": ".github/workflows/linter.yml",
    "content": "name: Linter\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  lint:\n    runs-on: windows-2022\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2\n      - name: Install dependencies\n        run: pip install black==25.* isort==6.* flake8==7.*\n      # Different line limits: Black/isort (120), Flake8 (150). \n      # Flake8 allows longer lines for better long string readability. Black doesn't enforce string length.\n      - name: Run black\n        run: black --line-length=120 --check --diff .\n      - name: Run flake8\n        run: flake8 --max-line-length=150\n      - name: Run isort\n        run: isort --check --diff --profile black --line-length=120 .\n      - name: Run PowerShell linter\n        run: scripts/lint.ps1\n\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled source #\n###################\n*.com\n*.class\n*.o\n*.so\n__pycache__/\n\n# Packages #\n############\n# it's better to unpack these files and commit the raw source\n# git has its own built in compression methods\n*.7z\n*.dmg\n*.gz\n*.rar\n*.tar\n*.nupkg\n\n# Logs and databases #\n######################\n*.log\n*.sql\n*.sqlite\n\n# OS generated files #\n######################\n.DS_Store\n.DS_Store?\n._*\n.Spotlight-V100\n.Trashes\nehthumbs.db\nThumbs.db\n\n# Pycharm artifacts\n###################\n.idea\n\n# vscode\n# #################\n.vscode/\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to contribute\n\nWant to open an issue or send a code contribution?\nRead the information below to learn how.\nWe are looking forward working with you to improve FLARE-VM! :sparkling_heart:\n\n## Repository structure of FLARE-VM\n\nThe FLARE-VM code is spited in two repositories:\n- **[FLARE-VM](https://github.com/mandiant/flare-vm) (this repository)**: FLARE-VM installation script, and configuration\n  - [Submit improvement proposals and report issues related to the installer](https://github.com/mandiant/flare-vm/issues/new/choose)\n\n- **[VM-Packages](https://github.com/mandiant/VM-Packages)**: Source code of tool packages used by FLARE-VM (this repository) and [CommandoVM](https://github.com/mandiant/commando-vm)\n  - [Documentation and contribution guides for tool packages](https://github.com/mandiant/VM-Packages/wiki)\n  - [Submit new tool packages or report package related issues](https://github.com/mandiant/VM-Packages/issues/new/choose)\n\nBefore opening an issue, ensure you select the correct repository ([FLARE-VM](https://github.com/mandiant/flare-vm) for the FLARE-VM installer, [VM-Packages](https://github.com/mandiant/VM-Packages) for concrete tools and packages).\nSelect the correct issue type and read the issue template carefully to ensure you provide all needed information.\n\n## Before contributing code\n\n### Sign our Contributor License Agreement\n\nContributions to this project must be accompanied by a [Contributor License Agreement](https://cla.developers.google.com/about) (CLA).\nYou (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project.\n\nIf you or your current employer have already signed the Google CLA (even if it was for a different project), you probably don't need to do it again.\n\nVisit <https://cla.developers.google.com/> to see your current agreements or to sign a new one.\n\n## Review our community guidelines\n\nThis project follows [Google's Open Source Community Guidelines](https://opensource.google/conduct).\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\r\n                                 Apache License\r\n                           Version 2.0, January 2004\r\n                        http://www.apache.org/licenses/\r\n\r\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\r\n\r\n   1. Definitions.\r\n\r\n      \"License\" shall mean the terms and conditions for use, reproduction,\r\n      and distribution as defined by Sections 1 through 9 of this document.\r\n\r\n      \"Licensor\" shall mean the copyright owner or entity authorized by\r\n      the copyright owner that is granting the License.\r\n\r\n      \"Legal Entity\" shall mean the union of the acting entity and all\r\n      other entities that control, are controlled by, or are under common\r\n      control with that entity. For the purposes of this definition,\r\n      \"control\" means (i) the power, direct or indirect, to cause the\r\n      direction or management of such entity, whether by contract or\r\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\r\n      outstanding shares, or (iii) beneficial ownership of such entity.\r\n\r\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\r\n      exercising permissions granted by this License.\r\n\r\n      \"Source\" form shall mean the preferred form for making modifications,\r\n      including but not limited to software source code, documentation\r\n      source, and configuration files.\r\n\r\n      \"Object\" form shall mean any form resulting from mechanical\r\n      transformation or translation of a Source form, including but\r\n      not limited to compiled object code, generated documentation,\r\n      and conversions to other media types.\r\n\r\n      \"Work\" shall mean the work of authorship, whether in Source or\r\n      Object form, made available under the License, as indicated by a\r\n      copyright notice that is included in or attached to the work\r\n      (an example is provided in the Appendix below).\r\n\r\n      \"Derivative Works\" shall mean any work, whether in Source or Object\r\n      form, that is based on (or derived from) the Work and for which the\r\n      editorial revisions, annotations, elaborations, or other modifications\r\n      represent, as a whole, an original work of authorship. For the purposes\r\n      of this License, Derivative Works shall not include works that remain\r\n      separable from, or merely link (or bind by name) to the interfaces of,\r\n      the Work and Derivative Works thereof.\r\n\r\n      \"Contribution\" shall mean any work of authorship, including\r\n      the original version of the Work and any modifications or additions\r\n      to that Work or Derivative Works thereof, that is intentionally\r\n      submitted to Licensor for inclusion in the Work by the copyright owner\r\n      or by an individual or Legal Entity authorized to submit on behalf of\r\n      the copyright owner. For the purposes of this definition, \"submitted\"\r\n      means any form of electronic, verbal, or written communication sent\r\n      to the Licensor or its representatives, including but not limited to\r\n      communication on electronic mailing lists, source code control systems,\r\n      and issue tracking systems that are managed by, or on behalf of, the\r\n      Licensor for the purpose of discussing and improving the Work, but\r\n      excluding communication that is conspicuously marked or otherwise\r\n      designated in writing by the copyright owner as \"Not a Contribution.\"\r\n\r\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\r\n      on behalf of whom a Contribution has been received by Licensor and\r\n      subsequently incorporated within the Work.\r\n\r\n   2. Grant of Copyright License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      copyright license to reproduce, prepare Derivative Works of,\r\n      publicly display, publicly perform, sublicense, and distribute the\r\n      Work and such Derivative Works in Source or Object form.\r\n\r\n   3. Grant of Patent License. Subject to the terms and conditions of\r\n      this License, each Contributor hereby grants to You a perpetual,\r\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\r\n      (except as stated in this section) patent license to make, have made,\r\n      use, offer to sell, sell, import, and otherwise transfer the Work,\r\n      where such license applies only to those patent claims licensable\r\n      by such Contributor that are necessarily infringed by their\r\n      Contribution(s) alone or by combination of their Contribution(s)\r\n      with the Work to which such Contribution(s) was submitted. If You\r\n      institute patent litigation against any entity (including a\r\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\r\n      or a Contribution incorporated within the Work constitutes direct\r\n      or contributory patent infringement, then any patent licenses\r\n      granted to You under this License for that Work shall terminate\r\n      as of the date such litigation is filed.\r\n\r\n   4. Redistribution. You may reproduce and distribute copies of the\r\n      Work or Derivative Works thereof in any medium, with or without\r\n      modifications, and in Source or Object form, provided that You\r\n      meet the following conditions:\r\n\r\n      (a) You must give any other recipients of the Work or\r\n          Derivative Works a copy of this License; and\r\n\r\n      (b) You must cause any modified files to carry prominent notices\r\n          stating that You changed the files; and\r\n\r\n      (c) You must retain, in the Source form of any Derivative Works\r\n          that You distribute, all copyright, patent, trademark, and\r\n          attribution notices from the Source form of the Work,\r\n          excluding those notices that do not pertain to any part of\r\n          the Derivative Works; and\r\n\r\n      (d) If the Work includes a \"NOTICE\" text file as part of its\r\n          distribution, then any Derivative Works that You distribute must\r\n          include a readable copy of the attribution notices contained\r\n          within such NOTICE file, excluding those notices that do not\r\n          pertain to any part of the Derivative Works, in at least one\r\n          of the following places: within a NOTICE text file distributed\r\n          as part of the Derivative Works; within the Source form or\r\n          documentation, if provided along with the Derivative Works; or,\r\n          within a display generated by the Derivative Works, if and\r\n          wherever such third-party notices normally appear. The contents\r\n          of the NOTICE file are for informational purposes only and\r\n          do not modify the License. You may add Your own attribution\r\n          notices within Derivative Works that You distribute, alongside\r\n          or as an addendum to the NOTICE text from the Work, provided\r\n          that such additional attribution notices cannot be construed\r\n          as modifying the License.\r\n\r\n      You may add Your own copyright statement to Your modifications and\r\n      may provide additional or different license terms and conditions\r\n      for use, reproduction, or distribution of Your modifications, or\r\n      for any such Derivative Works as a whole, provided Your use,\r\n      reproduction, and distribution of the Work otherwise complies with\r\n      the conditions stated in this License.\r\n\r\n   5. Submission of Contributions. Unless You explicitly state otherwise,\r\n      any Contribution intentionally submitted for inclusion in the Work\r\n      by You to the Licensor shall be under the terms and conditions of\r\n      this License, without any additional terms or conditions.\r\n      Notwithstanding the above, nothing herein shall supersede or modify\r\n      the terms of any separate license agreement you may have executed\r\n      with Licensor regarding such Contributions.\r\n\r\n   6. Trademarks. This License does not grant permission to use the trade\r\n      names, trademarks, service marks, or product names of the Licensor,\r\n      except as required for reasonable and customary use in describing the\r\n      origin of the Work and reproducing the content of the NOTICE file.\r\n\r\n   7. Disclaimer of Warranty. Unless required by applicable law or\r\n      agreed to in writing, Licensor provides the Work (and each\r\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\r\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\r\n      implied, including, without limitation, any warranties or conditions\r\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\r\n      PARTICULAR PURPOSE. You are solely responsible for determining the\r\n      appropriateness of using or redistributing the Work and assume any\r\n      risks associated with Your exercise of permissions under this License.\r\n\r\n   8. Limitation of Liability. In no event and under no legal theory,\r\n      whether in tort (including negligence), contract, or otherwise,\r\n      unless required by applicable law (such as deliberate and grossly\r\n      negligent acts) or agreed to in writing, shall any Contributor be\r\n      liable to You for damages, including any direct, indirect, special,\r\n      incidental, or consequential damages of any character arising as a\r\n      result of this License or out of the use or inability to use the\r\n      Work (including but not limited to damages for loss of goodwill,\r\n      work stoppage, computer failure or malfunction, or any and all\r\n      other commercial damages or losses), even if such Contributor\r\n      has been advised of the possibility of such damages.\r\n\r\n   9. Accepting Warranty or Additional Liability. While redistributing\r\n      the Work or Derivative Works thereof, You may choose to offer,\r\n      and charge a fee for, acceptance of support, warranty, indemnity,\r\n      or other liability obligations and/or rights consistent with this\r\n      License. However, in accepting such obligations, You may act only\r\n      on Your own behalf and on Your sole responsibility, not on behalf\r\n      of any other Contributor, and only if You agree to indemnify,\r\n      defend, and hold each Contributor harmless for any liability\r\n      incurred by, or claims asserted against, such Contributor by reason\r\n      of your accepting any such warranty or additional liability.\r\n\r\n   END OF TERMS AND CONDITIONS\r\n\r\n   APPENDIX: How to apply the Apache License to your work.\r\n\r\n      To apply the Apache License to your work, attach the following\r\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\r\n      replaced with your own identifying information. (Don't include\r\n      the brackets!)  The text should be enclosed in the appropriate\r\n      comment syntax for the file format. We also recommend that a\r\n      file or class name and description of purpose be included on the\r\n      same \"printed page\" as the copyright notice for easier\r\n      identification within third-party archives.\r\n\r\n   Copyright [yyyy] [name of copyright owner]\r\n\r\n   Licensed under the Apache License, Version 2.0 (the \"License\");\r\n   you may not use this file except in compliance with the License.\r\n   You may obtain a copy of the License at\r\n\r\n       http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n   Unless required by applicable law or agreed to in writing, software\r\n   distributed under the License is distributed on an \"AS IS\" BASIS,\r\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n   See the License for the specific language governing permissions and\r\n   limitations under the License.\r\n"
  },
  {
    "path": "LayoutModification.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!--\r\n Copyright 2023 Google LLC\r\n\r\n Licensed under the Apache License, Version 2.0 (the \"License\");\r\n you may not use this file except in compliance with the License.\r\n You may obtain a copy of the License at\r\n\r\n     http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n Unless required by applicable law or agreed to in writing, software\r\n distributed under the License is distributed on an \"AS IS\" BASIS,\r\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n See the License for the specific language governing permissions and\r\n limitations under the License.\r\n-->\r\n\r\n<LayoutModificationTemplate\r\n    xmlns=\"http://schemas.microsoft.com/Start/2014/LayoutModification\"\r\n    xmlns:defaultlayout=\"http://schemas.microsoft.com/Start/2014/FullDefaultLayout\"\r\n    xmlns:start=\"http://schemas.microsoft.com/Start/2014/StartLayout\"\r\n    xmlns:taskbar=\"http://schemas.microsoft.com/Start/2014/TaskbarLayout\"\r\n    Version=\"1\">\r\n  <LayoutOptions StartTileGroupCellWidth=\"6\" StartTileGroupsColumnCount=\"1\" />\r\n  <DefaultLayoutOverride>\r\n    <StartLayoutCollection>\r\n      <defaultlayout:StartLayout GroupCellWidth=\"6\">\r\n      </defaultlayout:StartLayout>\r\n    </StartLayoutCollection>\r\n  </DefaultLayoutOverride>\r\n    <CustomTaskbarLayoutCollection PinListPlacement=\"Replace\">\r\n      <defaultlayout:TaskbarLayout>\r\n        <taskbar:TaskbarPinList>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%AppData%\\Microsoft\\Windows\\Start Menu\\Programs\\System Tools\\File Explorer.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Productivity Tools\\Windows Terminal.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Utilities\\CyberChef.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Disassemblers\\ida.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Networking\\fakenet.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\PE\\CFF Explorer.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Utilities\\procexp.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Utilities\\procmon.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Productivity Tools\\notepad++.lnk\"/>\r\n          <taskbar:DesktopApp DesktopApplicationLinkPath=\"%TOOL_LIST_DIR%\\Productivity Tools\\VisualStudio.lnk\"/>\r\n        </taskbar:TaskbarPinList>\r\n      </defaultlayout:TaskbarLayout>\r\n    </CustomTaskbarLayoutCollection>\r\n</LayoutModificationTemplate>\r\n"
  },
  {
    "path": "README.md",
    "content": "# FLARE-VM\nWelcome to FLARE-VM - a collection of software installations scripts for Windows systems that allows you to easily setup and maintain a reverse engineering environment on a virtual machine (VM). FLARE-VM was designed to solve the problem of reverse engineering tool curation and relies on two main technologies: [Chocolatey](https://chocolatey.org) and [Boxstarter](https://boxstarter.org). Chocolatey is a Windows-based Nuget package management system, where a \"package\" is essentially a ZIP file containing PowerShell installation scripts that download and configure a specific tool. Boxstarter leverages Chocolatey packages to automate the installation of software and create repeatable, scripted Windows environments.\n\n<p align=\"center\">\n  <img src=\"Images/flarevm-logo.png\" alt=\"FLARE-VM Logo\" width=\"600\">\n</p>\n\n## Requirements\n**FLARE-VM should ONLY be installed on a virtual machine**.\nThe VM should satisfy the following requirements:\n\n* Windows ≥ 10\n* PowerShell ≥ 5\n* Disk capacity of at least 60 GB and memory of at least 2GB\n* Usernames without spaces or other special characters\n* Internet connection\n* Tamper Protection and any Anti-Malware solution (e.g., Windows Defender) disabled, preferably via Group Policy\n* Windows Updates Disabled\n\n## Installation instruction\nThis section documents the steps to install FLARE-VM. You may also find useful the [_Building a VM for Reverse Engineering and Malware Analysis! Installing the FLARE-VM_ video](https://www.youtube.com/watch?v=i8dCyy8WMKY).\n\n### Pre-installation\n* Prepare a Windows 10+ virtual machine\n  * Install Windows in the virtual machine, for example using the raw Windows 10 ISO from https://www.microsoft.com/en-us/software-download/windows10ISO\n  * Ensure the [requirements above](#requirements) are satisfied, including:\n    * Disable Windows Updates (at least until installation is finished)\n      * https://www.windowscentral.com/how-stop-updates-installing-automatically-windows-10\n    * Disable Tamper Protection and any Anti-Malware solution (e.g., Windows Defender), preferably via Group Policy.\n      * GPO: [https://stackoverflow.com/questions/62174426/how-to-permanently-disable-windows-defender-real-time-protection-with-gpo](https://superuser.com/a/1757341)\n      * Non-GPO - Manual: [https://www.maketecheasier.com/permanently-disable-windows-defender-windows-10/](https://www.maketecheasier.com/permanently-disable-windows-defender-windows-10)\n      * Non-GPO - Automated: [https://github.com/ionuttbara/windows-defender-remover](https://github.com/ionuttbara/windows-defender-remover)\n      * Non-GPO - Semi-Automated (User needs to toggle off Tamper Protection): [https://github.com/AveYo/LeanAndMean/blob/main/ToggleDefender.ps1](https://github.com/AveYo/LeanAndMean/blob/main/ToggleDefender.ps1)\n* Take a VM snapshot so you can always revert to a state before the FLARE-VM installation\n* NOTE for IDA Pro: If you are installing IDA Pro via `idapro.vm`, you must place your IDA Pro installer (and optionally, your license file) on the Desktop before running the FLARE-VM installer.\n\n### FLARE-VM installation\n* Open a `PowerShell` prompt as administrator\n* Download the installation script [`installer.ps1`](https://raw.githubusercontent.com/mandiant/flare-vm/main/install.ps1) to your Desktop:\n  * `(New-Object net.webclient).DownloadFile('https://raw.githubusercontent.com/mandiant/flare-vm/main/install.ps1',\"$([Environment]::GetFolderPath(\"Desktop\"))\\install.ps1\")`\n* Unblock the installation script:\n  * `Unblock-File .\\install.ps1`\n* Enable script execution:\n  * `Set-ExecutionPolicy Unrestricted -Force`\n    * If you receive an error saying the execution policy is overridden by a policy defined at a more specific scope, you may need to pass a scope in via `Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force`. To view execution policies for all scopes, execute `Get-ExecutionPolicy -List`\n* Finally, execute the installer script as follow:\n  * `.\\install.ps1`\n    * To pass your password as an argument: `.\\install.ps1 -password <password>`\n    * To use the CLI-only mode with minimal user interaction: `.\\install.ps1 -password <password> -noWait -noGui`\n    * To use the CLI-only mode with minimal user interaction and a custom config file: `.\\install.ps1 -customConfig <config.xml> -password <password> -noWait -noGui`\n* After installation it is recommended to switch to `host-only` networking mode and take a VM snapshot\n\n#### Installer Parameters\nBelow are the CLI parameter descriptions.\n\n```\nPARAMETERS\n    -password <String>\n        Current user password to allow reboot resiliency via Boxstarter. The script prompts for the password if not provided.\n\n    -noPassword [<SwitchParameter>]\n        Switch parameter indicating a password is not needed for reboots.\n\n    -customConfig <String>\n        Path to a configuration XML file. May be a file path or URL.\n\n    -customLayout <String>\n        Path to a taskbar layout XML file. May be a file path or URL.\n\n    -noWait [<SwitchParameter>]\n        Switch parameter to skip installation message before installation begins.\n\n    -noGui [<SwitchParameter>]\n        Switch parameter to skip customization GUI.\n\n    -noReboots [<SwitchParameter>]\n        Switch parameter to prevent reboots (not recommended).\n\n    -noChecks [<SwitchParameter>]\n        Switch parameter to skip validation checks (not recommended).\n```\n\nGet full usage information by running `Get-Help .\\install.ps1 -Detailed`.\n\n#### Installer GUI\n\nThe Installer GUI is display after executing the validation checks and installing Boxstarter and Chocolatey (if they are not installed already).\nUsing the installer GUI you may customize:\n* Package selection from FLARE-VM and Chocolatey community\n* Environment variable paths\n\n![Installer GUI](Images/installer-gui.png)\n\n#### Configuration\n\nThe installer will download [`config.xml`](https://raw.githubusercontent.com/mandiant/flare-vm/main/config.xml) from the FLARE-VM repository. This file contains the default configuration, including the list of packages to install and the environment variable paths. You may use your own configuration by specifying the CLI-argument `-customConfig` and providing either a local file path or URL to your `config.xml` file. For example:\n\n```\n.\\install.ps1 -customConfig \"https://raw.githubusercontent.com/mandiant/flare-vm/main/config.xml\"\n```\n\n#### Taskbar Layout\nThe installer will use [`CustomStartLayout.xml`](https://raw.githubusercontent.com/mandiant/flare-vm/main/CustomStartLayout.xml) from the FLARE-VM repository. This file contains the default taskbar layout. You may use your own configuration by specifying the CLI-argument `-customLayout` and providing a local file path or URL to your `CustomStartLayout.xml` file. For example:\n\n```\n.\\install.ps1 -customLayout \"https://raw.githubusercontent.com/mandiant/flare-vm/main/CustomStartLayout.xml\"\n```\n\n##### Things to Consider:\n- Items in the .xml that are not installed will not display in the taskbar (no broken links will be pinned)\n- Only applications (`.exe` files) or shortcuts to applications can be pinned.\n- If you would like to pin something that isn't an application, consider creating a shortcut that points to `cmd.exe` or `powershell` with arguments supplied that will perform that actions you would like.\n- If you would like to make something run with admin rights, consider making a shortcut using `VM-Install-Shortcut` with the flag `-runAsAdmin` and pinning the shortcut.\n\n\n#### Post installation steps\nYou can include any post installation step you like in the configuration inside the tags `apps`, `services`, `path-items`, `registry-items`, and `custom-items`.\n\nFor example:\n- To show known file extensions:\n```xml\n    <registry-items>\n        <registry-item name=\"Show known file extensions\" path=\"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\" value=\"HideFileExt\" type=\"DWord\" data=\"0\"/>\n    </registry-items>\n```\n\nFor more examples, check the default configuration file: [`config.xml`](https://raw.githubusercontent.com/mandiant/flare-vm/main/config.xml).\n\n\n## Contributing\n\n- Check our [CONTRIBUTING guide](/CONTRIBUTING.md) to learn how to contribute to the project.\n\n## Troubleshooting\nIf your installation fails, please attempt to identify the reason for the installation error by reading through the log files listed below on your system:\n* `%VM_COMMON_DIR%\\log.txt`\n* `%PROGRAMDATA%\\chocolatey\\logs\\chocolatey.log`\n* `%LOCALAPPDATA%\\Boxstarter\\boxstarter.log`\n\nEnsure you are running the latest version of the FLARE-VM installer and that your VM satisfies the [requirements](#requirements).\n\n### Installer Error\nIf the installation failed due to an issue in the installation script (e.g., `install.ps1`), [report the bug in FLARE-VM](https://github.com/mandiant/flare-vm/issues/new?labels=%3Abug%3A+bug&template=bug.yml).\nProvide all the information requested to ensure we are able to help you.\n\n> **Note:** Rarely should `install.ps1` be the reason for an installation failure. Most likely it is a specific package or set of packages that are failing (see below).\n\n### Package Error\nPackages fail to install from time to time -- this is normal. The most common reasons are outlined below:\n\n1. Failure or timeout from Chocolatey or MyGet to download a `.nupkg` file\n2. Failure or timeout due to remote host when downloading a tool\n3. Intrusion Detection System (IDS) or AV product (e.g., Windows Defender) prevents a tool download or removes the tool from the system\n4. Host specific issue, for example when using an untested version\n5. Tool fails to build due to dependencies\n6. Old tool URL (e.g., `HTTP STATUS 404`)\n7. Tool's SHA256 hash has changed from what is hardcoded in the package installation script\n\nReasons **1-4** are difficult for us to fix since we do not control them. If an issue related to reasons **1-4** is filed, it is unlikely we will be able to assist.\n\nWe can help with reasons **5-7** and welcome the community to contribute fixes as well!\nPlease [report the bug in VM-Packages](https://github.com/mandiant/VM-Packages/issues/new?labels=%3Abug%3A+bug&template=bug.yml) providing all the information requested.\n\n### Updates\n\nNote that package updates are best effort and that updates are not being tested.\nIf you encounter errors, perform a fresh FLARE-VM install.\n\n### Mailing List\nSubscribe to the FLARE mailing list for community announcements! Email \"subscribe\" to [flare-external@google.com](mailto:flare-external@google.com?subject=subscribe).\n\n## Legal Notice\n> This download configuration script is provided to assist cyber security analysts in creating handy and versatile toolboxes for malware analysis environments. It provides a convenient interface for them to obtain a useful set of analysis tools directly from their original sources. Installation and use of this script is subject to the Apache 2.0 License. You as a user of this script must review, accept and comply with the license terms of each downloaded/installed package. By proceeding with the installation, you are accepting the license terms of each package, and acknowledging that your use of each package will be subject to its respective license terms.\n"
  },
  {
    "path": "config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<!--\r\n Copyright 2017 Google LLC\r\n\r\n Licensed under the Apache License, Version 2.0 (the \"License\");\r\n you may not use this file except in compliance with the License.\r\n You may obtain a copy of the License at\r\n\r\n     http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n Unless required by applicable law or agreed to in writing, software\r\n distributed under the License is distributed on an \"AS IS\" BASIS,\r\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n See the License for the specific language governing permissions and\r\n limitations under the License.\r\n-->\r\n\r\n<config>\r\n    <envs>\r\n        <env name=\"VM_COMMON_DIR\" value=\"%ProgramData%\\_VM\"/>\r\n        <env name=\"TOOL_LIST_DIR\" value=\"%UserProfile%\\Desktop\\Tools\"/>\r\n        <env name=\"RAW_TOOLS_DIR\" value=\"%SystemDrive%\\Tools\"/>\r\n    </envs>\r\n    <packages>\r\n        <package name=\"010editor.vm\"/>\r\n        <package name=\"7zip.vm\"/>\r\n        <package name=\"advanced-installer.vm\"/>\r\n        <package name=\"angr.vm\"/>\r\n        <package name=\"apimonitor.vm\"/>\r\n        <package name=\"apktool.vm\"/>\r\n        <package name=\"asar.vm\"/>\r\n        <package name=\"autoit-ripper.vm\"/>\r\n        <package name=\"binaryninja.vm\"/>\r\n        <package name=\"bindiff.vm\"/>\r\n        <package name=\"blobrunner.vm\"/>\r\n        <package name=\"blobrunner64.vm\"/>\r\n        <package name=\"bytecodeviewer.vm\"/>\r\n        <package name=\"capa-explorer-web.vm\"/>\r\n        <package name=\"capa.vm\"/>\r\n        <package name=\"chrome.extensions.vm\"/>\r\n        <package name=\"cmder.vm\"/>\r\n        <package name=\"codetrack.vm\"/>\r\n        <package name=\"cryptotester.vm\"/>\r\n        <package name=\"cutter.vm\"/>\r\n        <package name=\"cyberchef.vm\"/>\r\n        <package name=\"cygwin.vm\"/>\r\n        <package name=\"de4dot-cex.vm\"/>\r\n        <package name=\"dependencywalker.vm\"/>\r\n        <package name=\"dex2jar.vm\"/>\r\n        <package name=\"didier-stevens-beta.vm\"/>\r\n        <package name=\"didier-stevens-suite.vm\"/>\r\n        <package name=\"die.vm\"/>\r\n        <package name=\"dll-to-exe.vm\"/>\r\n        <package name=\"dnlib.vm\"/>\r\n        <package name=\"dnspyex.vm\"/>\r\n        <package name=\"dotdumper.vm\"/>\r\n        <package name=\"dotnet3.5\"/> <!-- To run old .NET binaries -->\r\n        <package name=\"exeinfope.vm\"/>\r\n        <package name=\"explorersuite.vm\"/>\r\n        <package name=\"extreme_dumper.vm\"/>\r\n        <package name=\"ezviewer.vm\"/>\r\n        <package name=\"fakenet-ng.vm\"/>\r\n        <package name=\"file.vm\"/>\r\n        <package name=\"fiddler.vm\"/>\r\n        <package name=\"floss.vm\"/>\r\n        <package name=\"garbageman.vm\"/>\r\n        <package name=\"ghidra.vm\"/>\r\n        <package name=\"goresym.vm\"/>\r\n        <package name=\"gostringungarbler.vm\"/>\r\n        <package name=\"hashmyfiles.vm\"/>\r\n        <package name=\"hollowshunter.vm\"/>\r\n        <package name=\"hxd.vm\"/>\r\n        <package name=\"ida.plugin.capa.vm\"/>\r\n        <package name=\"ida.plugin.comida.vm\"/>\r\n        <package name=\"ida.plugin.delphihelper.vm\"/>\r\n        <package name=\"ida.plugin.dereferencing.vm\"/>\r\n        <package name=\"ida.plugin.diaphora.vm\"/>\r\n        <package name=\"ida.plugin.flare-emu.vm\"/>\r\n        <package name=\"ida.plugin.flare.vm\"/>\r\n        <package name=\"ida.plugin.hashdb.vm\"/>\r\n        <package name=\"ida.plugin.hrtng.vm\"/>\r\n        <package name=\"ida.plugin.ifl.vm\"/>\r\n        <package name=\"ida.plugin.xray.vm\"/>\r\n        <package name=\"ida.plugin.xrefer.vm\"/>\r\n        <package name=\"idafree.vm\"/>\r\n        <package name=\"idr.vm\"/>\r\n        <package name=\"ifpstools.vm\"/>\r\n        <package name=\"ilspy.vm\"/>\r\n        <package name=\"innoextract.vm\"/>\r\n        <package name=\"innounp.vm\"/>\r\n        <package name=\"internet_detector.vm\"/>\r\n        <package name=\"ipython.vm\"/>\r\n        <package name=\"isd.vm\"/>\r\n        <package name=\"js-beautify.vm\"/>\r\n        <package name=\"js-deobfuscator.vm\"/>\r\n        <package name=\"keystone.vm\"/>\r\n        <package name=\"libraries.python3.vm\"/>\r\n        <package name=\"magika.vm\"/>\r\n        <package name=\"malware-jail.vm\"/>\r\n        <package name=\"map.vm\"/>\r\n        <package name=\"microsoft-office.vm\"/>\r\n        <package name=\"nasm.vm\"/>\r\n        <package name=\"net-reactor-slayer.vm\"/>\r\n        <package name=\"nmap.vm\"/>\r\n        <package name=\"notepadplusplus.vm\"/>\r\n        <package name=\"notepadpp.plugin.compare.vm\"/>\r\n        <package name=\"notepadpp.plugin.jstool.vm\"/>\r\n        <package name=\"notepadpp.plugin.xmltools.vm\"/>\r\n        <package name=\"obfuscator-io-deobfuscator.vm\"/>\r\n        <package name=\"offvis.vm\"/>\r\n        <package name=\"onenoteanalyzer.vm\"/>\r\n        <package name=\"pdbresym.vm\"/>\r\n        <package name=\"pdfstreamdumper.vm\"/>\r\n        <package name=\"pe_unmapper.vm\"/>\r\n        <package name=\"pebear.vm\"/>\r\n        <package name=\"peid.vm\"/>\r\n        <package name=\"pesieve.vm\"/>\r\n        <package name=\"pestudio.vm\"/>\r\n        <package name=\"pkg-unpacker.vm\"/>\r\n        <package name=\"pma-labs.vm\"/>\r\n        <package name=\"procdot.vm\"/>\r\n        <package name=\"processdump.vm\"/>\r\n        <package name=\"psnotify.vm\"/>\r\n        <package name=\"pycdas.vm\"/>\r\n        <package name=\"pycdc.vm\"/>\r\n        <package name=\"pylingual.vm\"/>\r\n        <package name=\"rat-king-parser.vm\"/>\r\n        <package name=\"recaf.vm\"/>\r\n        <package name=\"reg_export.vm\"/>\r\n        <package name=\"regcool.vm\"/>\r\n        <package name=\"regshot.vm\"/>\r\n        <package name=\"resourcehacker.vm\"/>\r\n        <package name=\"rundotnetdll.vm\"/>\r\n        <package name=\"scdbg.vm\"/>\r\n        <package name=\"sclauncher.vm\"/>\r\n        <package name=\"sclauncher64.vm\"/>\r\n        <package name=\"sfextract.vm\"/>\r\n        <package name=\"shellcode_launcher.vm\"/>\r\n        <package name=\"sysinternals.vm\"/>\r\n        <package name=\"systeminformer.vm\"/>\r\n        <package name=\"ttd.vm\"/>\r\n        <package name=\"uncompyle6.vm\"/>\r\n        <package name=\"uniextract2.vm\"/>\r\n        <package name=\"unpyc3.vm\"/>\r\n        <package name=\"upx.vm\"/>\r\n        <package name=\"vb-decompiler-lite.vm\"/>\r\n        <package name=\"vbdec.vm\"/>\r\n        <package name=\"vcbuildtools.vm\"/>\r\n        <package name=\"vcredist-all\"/>\r\n        <package name=\"vscode.extension.jupyter.vm\"/>\r\n        <package name=\"vscode.extension.python.vm\"/>\r\n        <package name=\"vscode.vm\"/>\r\n        <package name=\"windbg.vm\"/>\r\n        <package name=\"windows-terminal.vm\"/>\r\n        <package name=\"wireshark.vm\"/>\r\n        <package name=\"x64dbg.plugin.dbgchild.vm\"/>\r\n        <package name=\"x64dbg.plugin.ollydumpex.vm\"/>\r\n        <package name=\"x64dbg.plugin.scyllahide.vm\"/>\r\n        <package name=\"x64dbg.plugin.x64dbgpy.vm\"/>\r\n        <package name=\"x64dbg.vm\"/>\r\n        <package name=\"yara.vm\"/>\r\n    </packages>\r\n    <apps>\r\n        <!--\r\n        INFO:\r\n        Removes installed AppX packages. Try:\r\n        $packages = Get-AppxPackage\r\n        $packages.Name\r\n        FORMAT:\r\n        <app name=\"APP_NAME\"/>\r\n        -->\r\n    </apps>\r\n    <services>\r\n        <!--\r\n        INFO:\r\n        Sets Services to \"Manual\" startup type. Try:\r\n        $services = Get-WmiObject -Query \"SELECT * FROM Win32_Service WHERE StartMode='Auto'\" | Get-Service\r\n        $services.Name\r\n        FORMAT:\r\n        <service name=\"SERVICE_NAME\"/>\r\n        -->\r\n    </services>\r\n    <tasks>\r\n        <!--\r\n        INFO:\r\n        Disables Scheduled Tasks. Try:\r\n        $tasks = Get-ScheduledTask\r\n        $tasks.TaskName\r\n        FORMAT:\r\n        <task name=\"DESCRIPTIVE_NAME\" value=\"TASK_NAME\"/>\r\n        -->\r\n    </tasks>\r\n    <path-items>\r\n        <!--\r\n        INFO:\r\n        Removes files and folders from the system\r\n        FORMAT:\r\n        <path-item name=\"DESCRIPTIVE_NAME\" type=\"dir/file\" path=\"DIR_PATH/FILE_PATH\"/>\r\n        -->\r\n    </path-items>\r\n    <registry-items>\r\n        <!--\r\n        INFO:\r\n        Makes custom edits to the registry\r\n        FORMAT:\r\n        <registry-item name=\"DESCRIPTIVE_NAME\" path=\"REG_PATH\" value=\"REG_VALUE\" type=\"TYPE\" data=\"NEW_DATA\"/>\r\n        -->\r\n        <registry-item name=\"Show full directory path in Explorer title bar\" path=\"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CabinetState\" value=\"FullPath\" type=\"DWord\" data=\"1\"/>\r\n        <registry-item name=\"Show known file extensions\" path=\"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\" value=\"HideFileExt\" type=\"DWord\" data=\"0\"/>\r\n        <registry-item name=\"Show hidden files\" path=\"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced\" value=\"Hidden\" type=\"DWord\" data=\"1\"/>\r\n        <registry-item name=\"Disable SmartScreen\" path=\"HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\System\" value=\"EnableSmartScreen\" type=\"DWord\" data=\"0\" />\r\n        <registry-item name=\"Disable Microsoft Edge Phishing Filter\" path=\"HKLM:\\SOFTWARE\\Policies\\Microsoft\\MicrosoftEdge\\PhishingFilter\" value=\"EnabledV9\" type=\"DWord\" data=\"0\" />\r\n        <registry-item name=\"Disable Windows Firewall (Standard Profile)\" path=\"HKLM:\\SOFTWARE\\Policies\\Microsoft\\WindowsFirewall\\StandardProfile\" value=\"EnableFirewall\" type=\"DWord\" data=\"0\" />\r\n        <registry-item name=\"Add ZoomIt to Windows Start\" path=\"HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\run\" value=\"ZoomIt\" type=\"String\" data=\"C:\\Tools\\sysinternals\\ZoomIt64.exe\" />\r\n        <registry-item name=\"Don't display ZoomIt GUI on login\" path=\"HKCU:\\Software\\Sysinternals\\ZoomIt\" value=\"OptionsShown\" type=\"DWord\" data=\"1\" />\r\n        <registry-item name=\"Hide the .lnk extension\" path=\"HKLM:\\SOFTWARE\\Classes\\lnkfile\" value=\"NeverShowExt\" type=\"String\" data=\" \"/>\r\n        <!-- Set dark mode\r\n        <registry-item name=\"Set Dark Mode on System\" path=\"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\" value=\"SystemUsesLightTheme\" type=\"DWord\" data=\"0\"/>\r\n        <registry-item name=\"Set Dark Mode on Apps\" path=\"HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize\" value=\"AppsUseLightTheme\" type=\"DWord\" data=\"0\"/>\r\n        -->\r\n    </registry-items>\r\n    <locales>\r\n        <!--\r\n        INFO:\r\n        Install extra system locales. Useful to analyse malware that behave differently depending on the locale.\r\n        FORMAT:\r\n        <locale name=\"DESCRIPTIVE_NAME\" lang=\"LANGUAGE_VALUE\"/>\r\n        -->\r\n        <locale name=\"Chinese\" lang=\"zh-CN\"/>\r\n        <locale name=\"English (United Kingdom)\" lang=\"en-GB\"/>\r\n        <locale name=\"German (Germany)\" lang=\"de-DE\"/>\r\n        <locale name=\"Russian\" lang=\"ru-RU\"/>\r\n        <locale name=\"Spanish (Spain)\" lang=\"es-ES\"/>\r\n        <locale name=\"Ukrainian\" lang=\"uk-UA\"/>\r\n        <locale name=\"Brazilian Portuguese\" lang=\"pt-BR\"/>\r\n    </locales>\r\n    <custom-items>\r\n        <!--\r\n        INFO:\r\n        Performs custom commands\r\n        FORMAT:\r\n        <custom-item name=\"DESCRIPTIVE_NAME\"> <cmd value=\"PS_COMMAND\"/> ... </custom-item>\r\n        -->\r\n        <custom-item name=\"Disabling Windows Firewall\">\r\n            <cmd value=\"Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False\" />\r\n        </custom-item>\r\n    </custom-items>\r\n</config>\r\n"
  },
  {
    "path": "install.ps1",
    "content": "<#\r\n Copyright 2017 Google LLC\r\n\r\n Licensed under the Apache License, Version 2.0 (the \"License\");\r\n you may not use this file except in compliance with the License.\r\n You may obtain a copy of the License at\r\n\r\n     http://www.apache.org/licenses/LICENSE-2.0\r\n\r\n Unless required by applicable law or agreed to in writing, software\r\n distributed under the License is distributed on an \"AS IS\" BASIS,\r\n WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n See the License for the specific language governing permissions and\r\n limitations under the License.\r\n#>\r\n\r\n<#\r\n    .SYNOPSIS\r\n        Installation script for FLARE VM.\r\n        ** Only install on a virtual machine! **\r\n\r\n    .DESCRIPTION\r\n        Installation script for FLARE VM that leverages Chocolatey and Boxstarter.\r\n        Script verifies minimal settings necessary to install FLARE VM on a virtual machine.\r\n        Script allows users to customize package selection and envrionment variables used in FLARE VM via a GUI before installation begins.\r\n        A CLI-only mode is also available by providing specific command-line arugment switches.\r\n\r\n        To execute this script:\r\n          1) Open PowerShell window as administrator\r\n          2) Allow script execution by running command \"Set-ExecutionPolicy Unrestricted\"\r\n          3) Unblock the install script by running \"Unblock-File .\\install.ps1\"\r\n          4) Execute the script by running \".\\install.ps1\"\r\n\r\n    .PARAMETER password\r\n        Current user password to allow reboot resiliency via Boxstarter. The script prompts for the password if not provided.\r\n\r\n    .PARAMETER noPassword\r\n        Switch parameter indicating a password is not needed for reboots.\r\n\r\n    .PARAMETER customConfig\r\n        Path to a configuration XML file. May be a file path or URL.\r\n\r\n    .PARAMETER customLayout\r\n        Path to a taskbar layout XML file. May be a file path or URL.\r\n\r\n    .PARAMETER noWait\r\n        Switch parameter to skip installation message before installation begins.\r\n\r\n    .PARAMETER noGui\r\n        Switch parameter to skip customization GUI.\r\n\r\n    .PARAMETER noReboots\r\n        Switch parameter to prevent reboots (not recommended).\r\n\r\n    .PARAMETER noChecks\r\n        Switch parameter to skip validation checks (not recommended).\r\n\r\n    .EXAMPLE\r\n        .\\install.ps1\r\n\r\n        Description\r\n        ---------------------------------------\r\n        Execute the installer to configure FLARE VM.\r\n\r\n    .EXAMPLE\r\n        .\\install.ps1 -password Passw0rd! -noWait -noGui -noChecks\r\n\r\n        Description\r\n        ---------------------------------------\r\n        CLI-only installation with minimal user interaction (some packages may require user interaction).\r\n        To prevent reboots, also add the \"-noReboots\" switch.\r\n\r\n    .EXAMPLE\r\n        .\\install.ps1 -customConfig \"https://raw.githubusercontent.com/mandiant/flare-vm/main/config.xml\"\r\n\r\n        Description\r\n        ---------------------------------------\r\n        Use a custom configuration XML file hosted on the internet.\r\n\r\n    .LINK\r\n        https://github.com/mandiant/flare-vm\r\n        https://github.com/mandiant/VM-Packages\r\n#>\r\n\r\nparam (\r\n  [string]$password = $null,\r\n  [switch]$noPassword,\r\n  [string]$customConfig = $null,\r\n  [string]$customLayout = $null,\r\n  [switch]$noWait,\r\n  [switch]$noGui,\r\n  [switch]$noReboots,\r\n  [switch]$noChecks\r\n)\r\n$ErrorActionPreference = 'Stop'\r\n$ProgressPreference = 'SilentlyContinue'\r\n\r\n# Function to download files and handle errors consistently\r\nfunction Save-FileFromUrl {\r\n    param (\r\n        [string]$fileSource,\r\n        [string]$fileDestination,\r\n        [switch]$exitOnError\r\n    )\r\n    Write-Host \"[+] Downloading file from '$fileSource'\"\r\n    try {\r\n        (New-Object net.webclient).DownloadFile($fileSource,$FileDestination)\r\n    } catch {\r\n        Write-Host \"`t[!] Failed to download '$fileSource'\"\r\n        Write-Host \"`t[!] $_\"\r\n        if ($exitOnError) {\r\n            Start-Sleep 3\r\n            exit 1\r\n        }\r\n    }\r\n}\r\n\r\n# Function used for getting configuration files (such as config.xml and LayoutModification.xml)\r\nfunction Get-ConfigFile {\r\n    param (\r\n        [string]$fileDestination,\r\n        [string]$fileSource\r\n    )\r\n    # Check if the source is an existing file path.\r\n    if (-not (Test-Path $fileSource)) {\r\n        # If the source doesn't exist, assume it's a URL and download the file.\r\n        Save-FileFromUrl -fileSource $fileSource -fileDestination $fileDestination\r\n    } else {\r\n        # If the source exists as a file, move it to the destination.\r\n        Write-Host \"[+] Using existing file as configuration file.\"\r\n        Move-Item -Path $fileSource -Destination $fileDestination -Force\r\n    }\r\n}\r\n\r\n# Set path to user's desktop\r\n$desktopPath = [Environment]::GetFolderPath(\"Desktop\")\r\nSet-Location -Path $desktopPath -PassThru | Out-Null\r\n\r\n# Setting global variables\r\n$script:checksPassed = $true\r\n$mandatoryChecksPassed = $true\r\n$exit_message = \"Installation cannot continue.\"\r\n\r\n################################# Functions that conduct Pre-Install Checks #################################\r\n# Function to test the network stack. Ping/GET requests to the resource to ensure that network stack looks good for installation\r\nfunction Test-WebConnection {\r\n    param (\r\n        [string]$url\r\n    )\r\n\r\n    Write-Host \"[+] Checking for Internet connectivity ($url)... (mandatory)\"\r\n\r\n    if (-not (Test-Connection $url -Quiet)) {\r\n        return \"It looks like you cannot ping $url. Check your network settings.\"\r\n    }\r\n\r\n    $response = $null\r\n    try {\r\n        $response = Invoke-WebRequest -Uri \"https://$url\" -UseBasicParsing -DisableKeepAlive\r\n    }\r\n    catch {\r\n        return \"Error accessing $url. Exception: $($_.Exception.Message)`n`t[!] Check your network settings.\"\r\n    }\r\n\r\n    if ($response -and $response.StatusCode -ne 200) {\r\n        return \"Unable to access $url. Status code: $($response.StatusCode)`n`t[!] Check your network settings.\"\r\n    }\r\n\r\n}\r\n\r\n\r\nfunction Test-PSVersion{\r\n    try {\r\n\t\t$psVersion = $PSVersionTable.PSVersion\r\n\t\tif ($psVersion -lt [System.Version]\"5.0.0\") {\r\n\t\t  return \"Your PowerShell version ($psVersion) is not supported\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine Powershell version\"\r\n\t}\r\n}\r\n\r\nfunction Test-Admin {\r\n    try {\r\n\t\t$currentPrincipal = New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())\r\n\t\tif (-not ($currentPrincipal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator))){\r\n\t\t\treturn \"The script is not running as Administrator\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine if the script is running as Administrator\"\r\n\t}\r\n}\r\nfunction Test-ExecutionPolicy {\r\n\ttry {\r\n\t\tif (-not((Get-ExecutionPolicy).ToString() -eq \"Unrestricted\")){\r\n\t\t\treturn \"You need to enable script execution with 'Set-ExecutionPolicy Unrestricted -Force'\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine Powershell execution policy\"\r\n\t}\r\n}\r\nfunction Test-DefenderAndTamperProtection {\r\n        try {\r\n\t\t$defender = Get-Service -Name WinDefend -ea 0\r\n\t\tif ($null -ne $defender) {\r\n\t\t\tif ($defender.Status -eq \"Running\") {\r\n\t\t\t\t return \"Disable Windows Defender through Group Policy, reboot, and rerun installer\"\r\n\t\t\t}\r\n\t\t}\r\n        $tpEnabled = Get-ItemProperty -Path \"HKLM:\\SOFTWARE\\Microsoft\\Windows Defender\\Features\" -Name \"TamperProtection\" -ErrorAction Stop\r\n        if ($tpEnabled.TamperProtection -eq 5) {\r\n            return \"Disable Tamper Protection, reboot, and rerun installer\"\r\n        }\r\n    } catch {\r\n\t\treturn \"Unable to determine if TamperProtection and Defender are enabled\"\r\n    }\r\n}\r\n\r\nfunction Test-WindowsVersion {\r\n\ttry {\r\n\t\t$os = Get-CimInstance -Class Win32_OperatingSystem\r\n\t\t$osMajorVersion = $os.Version.Split('.')[0] # Version examples: \"6.1.7601\", \"10.0.19045\"\r\n\t\tif ($osMajorVersion -lt 10) {\r\n\t\t\treturn \"Only Windows >= 10 is supported\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine Windows Version\"\r\n\t}\r\n}\r\n\r\n# 19045: https://www.microsoft.com/en-us/software-download/windows10ISO downloaded on April 25 2023.\r\n# 20348: the version used by windows-2022 in GH actions\r\n# 26100: https://www.microsoft.com/en-us/software-download/windows11 downloaded on May 6 2025.\r\nfunction Test-TestedOS {\r\n\t$testedVersions = @(19045, 20348, 26100)\r\n\ttry {\r\n\t\t$osVersion = (Get-CimInstance -class Win32_OperatingSystem).BuildNumber\r\n\t\tif (-not ($osVersion -in $testedVersions)){\r\n\t\t\treturn \"Windows version $osVersion has not been tested. Tested versions: $($testedVersions -join ', ')\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Windows version may not have been tested. Tested versions: $($testedVersions -join ', ')\"\r\n\t}\r\n}\r\nfunction Test-VM {\r\n    $virtualModels = @('VirtualBox', 'VMware', 'Virtual Machine', 'Hyper-V')\r\n    try {\r\n\t\t$computerSystemModel = (Get-CimInstance win32_computersystem).model\r\n\t\t$isVirtualModel = $false\r\n\r\n\t\tforeach ($model in $virtualModels) {\r\n\t\t\tif ($computerSystemModel.Contains($model)) {\r\n\t\t\t\t$isVirtualModel = $true\r\n\t\t\t\tbreak\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (-not ($isVirtualModel)) {\r\n\t\t\treturn \"You are not on a VM or have hardened your machine to not appear as such\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine if you are on a VM\"\r\n\t}\r\n}\r\n\r\nfunction Test-SpaceUserName {\r\n\ttry {\r\n\t\tif (${Env:UserName} -match '\\s') {\r\n\t\t\treturn \"Username '${Env:UserName}' contains a space and will break installation\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine if the username contains a space\"\r\n\t}\r\n}\r\nfunction Test-Storage {\r\n\ttry {\r\n\t\t$disk = Get-PSDrive (Get-Location).Drive.Name\r\n\t\tStart-Sleep -Seconds 1\r\n\t\tif (-not (($disk.used + $disk.free)/1GB -gt 58.8)) {\r\n\t\t\treturn \"A minimum of 60 GB hard drive space is preferred, increase hard drive space\"\r\n\t\t}\r\n\t} catch {\r\n\t\treturn \"Unable to determine hard drive space\"\r\n\t}\r\n}\r\n\r\n\r\n\r\nif ($noGui.IsPresent) {\r\n\tif (-not $noChecks.IsPresent) {\r\n\t\t# Check PowerShell version\r\n\t\tWrite-Host \"[+] Checking if PowerShell version is compatible (mandatory)...\"\r\n\t\t$error_info = Test-PSVersion\r\n\t\tif ($error_info){\r\n\t\t\tWrite-Host \"`t[!] $error_info\" -ForegroundColor Red\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Ensure script is ran as administrator\r\n\t\tWrite-Host \"[+] Checking if script is running as administrator (mandatory)...\"\r\n\t\t$error_info = Test-Admin\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\"  -ForegroundColor Red\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Ensure execution policy is unrestricted\r\n\t\tWrite-Host \"[+] Checking if execution policy is unrestricted.. (mandatory).\"\r\n\t\t$error_info = Test-ExecutionPolicy\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\" -ForegroundColor Red\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Check if Windows < 10\r\n\t\tWrite-Host \"[+] Checking Operating System version compatibility...\"\r\n\t\t$error_info = Test-WindowsVersion\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\" -ForegroundColor Yellow\r\n\t\t\t$script:checksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Check if host has been tested\r\n\t\tWrite-Host \"[+] Checking if the Operating System has been tested...\"\r\n\t\t$error_info= Test-TestedOS\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\" -ForegroundColor Yellow\r\n\t\t\t$script:checksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Check if system is a virtual machine\r\n\t\tWrite-Host \"[+] Checking if the system runs on a Virtual Machine...\"\r\n\t\t$error_info = Test-VM\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\" -ForegroundColor Yellow\r\n\t\t\t$script:checksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Check for spaces in the username, exit if identified\r\n\t\tWrite-Host \"[+] Checking for spaces in the username... (mandatory)\"\r\n\t\t$error_info = Test-SpaceUserName\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\" -ForegroundColor Red\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Check if host has enough disk space\r\n\t\tWrite-Host \"[+] Checking if host has enough disk space...\"\r\n\t\t$error_info = Test-Storage\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!] $error_info\"   -ForegroundColor Yellow\r\n\t\t\t$script:checksPassed = $false\r\n\t\t}\r\n\r\n\t\t# Internet connectivity checks\r\n\t\t$error_info = Test-WebConnection 'google.com'\r\n\t\tif ($error_info){\r\n\t\t\tWrite-Host \"`t[+] $error_info\" -ForegroundColor Red\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}else {\r\n\t\t\t$error_info = Test-WebConnection 'github.com'\r\n\t\t\tif ($error_info){\r\n\t\t\t\tWrite-Host \"`t[+] $error_info\" -ForegroundColor Red\r\n\t\t\t    $mandatoryChecksPassed = $false\r\n\t\t\t}else {\r\n\t\t\t\t$error_info = Test-WebConnection 'raw.githubusercontent.com'\r\n\t\t\t\tif ($error_info){\r\n\t\t\t\t    Write-Host \"`t[+] $error_info\" -ForegroundColor Red\r\n\t\t\t        $mandatoryChecksPassed = $false\r\n\t\t\t    }\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t# Check if Tamper Protection is disabled\r\n\t\tWrite-Host \"[+] Checking if Windows Defender Tamper Protection is disabled...\"\r\n\t\t$error_info = Test-DefenderAndTamperProtection\r\n\t\tif ($error_info) {\r\n\t\t\tWrite-Host \"`t[!]$errorinfo\"  -ForegroundColor Red\r\n\t\t\t$script:checksPassed = $false\r\n\t\t}\r\n\r\n\t\tif (-not $mandatoryChecksPassed){\r\n\t\t\tWrite-Host \"[!] $exit_message\" -ForegroundColor Red\r\n\t\t\tStart-Sleep 3\r\n            exit 1\r\n\t\t}\r\n\r\n\t\tif (-not $script:checksPassed){\r\n\t\t\tWrite-Host \"[-] Do you still wish to proceed? (Y/N): \" -ForegroundColor Yellow -NoNewline\r\n\t\t\t$response = Read-Host\r\n\t\t\tif ($response -notin @(\"y\",\"Y\")) {\r\n\t\t\t\texit 1\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tWrite-Host \"[+] Setting password to never expire to avoid that a password expiration blocks the installation...\"\r\n\t\t$UserNoPasswd = Get-CimInstance Win32_UserAccount -Filter \"Name='${Env:UserName}'\"\r\n\t\t$UserNoPasswd | Set-CimInstance -Property @{ PasswordExpires = $false }\r\n\r\n\t\t# Prompt user to remind them to take a snapshot\r\n\t\tWrite-Host \"[-] Have you taken a VM snapshot to ensure you can revert to pre-installation state? (Y/N): \" -ForegroundColor Yellow -NoNewline\r\n\t\t$response = Read-Host\r\n\t\tif ($response -notin @(\"y\",\"Y\")) {\r\n\t\t\texit 1\r\n\t\t}\r\n\t}\r\n\r\n}\r\n\r\nfunction Open-CheckManager {\r\n\tif ($formChecksManager.ShowDialog() -ne [System.Windows.Forms.DialogResult]::OK) {\r\n\t\texit\r\n\t}\r\n}\r\n# Init Window Install checks\r\nif (-not $noGui.IsPresent) {\r\n\r\n    Write-Host \"[+] Starting GUI to allow user to edit configuration file...\"\r\n    ################################################################################\r\n    ## BEGIN GUI\r\n    ################################################################################\r\n    Add-Type -AssemblyName System.Windows.Forms\r\n    Add-Type -Assembly System.Drawing\r\n\r\n    $errorColor = [System.Drawing.ColorTranslator]::FromHtml(\"#c80505\")\r\n    $successColor = [System.Drawing.ColorTranslator]::FromHtml(\"#417505\")\r\n    $grayedColor = [System.Drawing.ColorTranslator]::FromHtml(\"#6e6964\")\r\n\t$orangeColor = [System.Drawing.ColorTranslator]::FromHtml(\"#bf8334\")\r\n\r\n    if (-not $noChecks.IsPresent) {\r\n\r\n\t\t#################################################################################################\r\n\t\t################################ Installer Checks Form Controls #################################\r\n\t\t#################################################################################################\r\n\r\n\t\t$formChecksManager           = New-Object system.Windows.Forms.Form\r\n\t\t$formChecksManager.ClientSize  = New-Object System.Drawing.Point(700,640)\r\n\t\t$formChecksManager.text      = \"FLAREVM Pre-Install Checks\"\r\n\t\t$formChecksManager.TopMost   = $true\r\n\t\t$formChecksManager.StartPosition = 'CenterScreen'\r\n\r\n\t\t$ChecksPanel                     = New-Object system.Windows.Forms.Panel\r\n\t\t$ChecksPanel.height              = 460\r\n\t\t$ChecksPanel.width               = 89\r\n\t\t$ChecksPanel.location            = New-Object System.Drawing.Point(570,8)\r\n\r\n\t\t$InstallChecksGroup              = New-Object system.Windows.Forms.Groupbox\r\n\t\t$InstallChecksGroup.height       = 490\r\n\t\t$InstallChecksGroup.width        = 665\r\n\t\t$InstallChecksGroup.text         = \"Installation Checks\"\r\n\t\t$InstallChecksGroup.location     = New-Object System.Drawing.Point(23,14)\r\n\r\n\t\t################################# Check Labels #################################\r\n\r\n\t\t$PSVersionLabel = New-Object system.Windows.Forms.Label\r\n\t\t$PSVersionLabel.text = \"Valid Powershell version\"\r\n\t\t$PSVersionLabel.AutoSize = $true\r\n\t\t$PSVersionLabel.width = 25\r\n\t\t$PSVersionLabel.height = 10\r\n\t\t$PSVersionLabel.location = New-Object System.Drawing.Point(15,18)\r\n\t\t$PSVersionLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n        $RunningAsAdminLabel = New-Object system.Windows.Forms.Label\r\n\t\t$RunningAsAdminLabel.text = \"Running as Administrator\"\r\n\t\t$RunningAsAdminLabel.AutoSize = $true\r\n\t\t$RunningAsAdminLabel.width = 25\r\n\t\t$RunningAsAdminLabel.height = 10\r\n\t\t$RunningAsAdminLabel.location = New-Object System.Drawing.Point(15,59)\r\n\t\t$RunningAsAdminLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$ExecutionPolicyLabel = New-Object system.Windows.Forms.Label\r\n\t\t$ExecutionPolicyLabel.text = \"Execution Policy Unrestricted\"\r\n\t\t$ExecutionPolicyLabel.AutoSize = $true\r\n\t\t$ExecutionPolicyLabel.width = 25\r\n\t\t$ExecutionPolicyLabel.height = 10\r\n\t\t$ExecutionPolicyLabel.location = New-Object System.Drawing.Point(15,104)\r\n\t\t$ExecutionPolicyLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$validWindowsVersionLabel = New-Object system.Windows.Forms.Label\r\n\t\t$validWindowsVersionLabel.text = \"Valid Windows Version\"\r\n\t\t$validWindowsVersionLabel.AutoSize = $true\r\n\t\t$validWindowsVersionLabel.location = New-Object System.Drawing.Point(15,149)\r\n\t\t$validWindowsVersionLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$WindowsReleaseLabel = New-Object system.Windows.Forms.Label\r\n\t\t$WindowsReleaseLabel.text = \"Tested Windows Version\"\r\n\t\t$WindowsReleaseLabel.AutoSize = $true\r\n\t\t$WindowsReleaseLabel.width = 25\r\n\t\t$WindowsReleaseLabel.height = 10\r\n\t\t$WindowsReleaseLabel.location = New-Object System.Drawing.Point(15,193)\r\n\t\t$WindowsReleaseLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$RunningVMLabel = New-Object system.Windows.Forms.Label\r\n\t\t$RunningVMLabel.text = \"Running in a Virtual Machine\"\r\n\t\t$RunningVMLabel.AutoSize = $true\r\n\t\t$RunningVMLabel.width = 25\r\n\t\t$RunningVMLabel.height = 10\r\n\t\t$RunningVMLabel.location = New-Object System.Drawing.Point(15,239)\r\n\t\t$RunningVMLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$usernameContainsSpacesLabel = New-Object system.Windows.Forms.Label\r\n\t\t$usernameContainsSpacesLabel.text = \"Valid username\"\r\n\t\t$usernameContainsSpacesLabel.AutoSize = $true\r\n\t\t$usernameContainsSpacesLabel.location = New-Object System.Drawing.Point(15,285)\r\n\t\t$usernameContainsSpacesLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$EnoughHardStorageLabel = New-Object system.Windows.Forms.Label\r\n\t\t$EnoughHardStorageLabel.text = \"Enough Hard Drive Space\"\r\n\t\t$EnoughHardStorageLabel.AutoSize = $true\r\n\t\t$EnoughHardStorageLabel.width = 25\r\n\t\t$EnoughHardStorageLabel.height = 10\r\n\t\t$EnoughHardStorageLabel.location = New-Object System.Drawing.Point(15,325)\r\n\t\t$EnoughHardStorageLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$internetConnectivityLabel = New-Object system.Windows.Forms.Label\r\n\t\t$internetConnectivityLabel.text = \"Internet connectivity\"\r\n\t\t$internetConnectivityLabel.AutoSize = $true\r\n\t\t$internetConnectivityLabel.location = New-Object System.Drawing.Point(15,369)\r\n\t\t$internetConnectivityLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$WindowsDefenderLabel = New-Object system.Windows.Forms.Label\r\n\t\t$WindowsDefenderLabel.text = \"Windows Defender Disabled\"\r\n\t\t$WindowsDefenderLabel.AutoSize = $true\r\n\t\t$WindowsDefenderLabel.width = 25\r\n\t\t$WindowsDefenderLabel.height = 10\r\n\t\t$WindowsDefenderLabel.location = New-Object System.Drawing.Point(15,411)\r\n\t\t$WindowsDefenderLabel.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t################################# Check Boolean Controls #################################\r\n\r\n\t\t$PSVersion = New-Object system.Windows.Forms.Label\r\n\t\t$PSVersion.text = \"False\"\r\n\t\t$PSVersion.AutoSize = $true\r\n\t\t$PSVersion.width = 25\r\n\t\t$PSVersion.height = 10\r\n\t\t$PSVersion.location = New-Object System.Drawing.Point(24,18)\r\n\t\t$PSVersion.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$PSVersion.ForeColor = $errorColor\r\n\r\n\t\t$RunningAsAdmin = New-Object system.Windows.Forms.Label\r\n\t\t$RunningAsAdmin.text = \"False\"\r\n\t\t$RunningAsAdmin.AutoSize = $true\r\n\t\t$RunningAsAdmin.width = 25\r\n\t\t$RunningAsAdmin.height = 10\r\n\t\t$RunningAsAdmin.location = New-Object System.Drawing.Point(24,63)\r\n\t\t$RunningAsAdmin.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$RunningAsAdmin.ForeColor = $errorColor\r\n\r\n\t\t$ExecutionPolicy = New-Object system.Windows.Forms.Label\r\n\t\t$ExecutionPolicy.text = \"False\"\r\n\t\t$ExecutionPolicy.AutoSize = $true\r\n\t\t$ExecutionPolicy.width = 25\r\n\t\t$ExecutionPolicy.height = 10\r\n\t\t$ExecutionPolicy.location = New-Object System.Drawing.Point(24,108)\r\n\t\t$ExecutionPolicy.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$ExecutionPolicy.ForeColor = $errorColor\r\n\r\n\t\t$validWindowsVersion = New-Object system.Windows.Forms.Label\r\n\t\t$validWindowsVersion.text = \"False\"\r\n\t\t$validWindowsVersion.AutoSize = $true\r\n\t\t$validWindowsVersion.width = 25\r\n\t\t$validWindowsVersion.height = 10\r\n\t\t$validWindowsVersion.location = New-Object System.Drawing.Point(24,150)\r\n\t\t$validWindowsVersion.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$validWindowsVersion.ForeColor = $errorColor\r\n\r\n\t\t$WindowsRelease = New-Object system.Windows.Forms.Label\r\n\t\t$WindowsRelease.text = \"False\"\r\n\t\t$WindowsRelease.AutoSize = $true\r\n\t\t$WindowsRelease.width = 25\r\n\t\t$WindowsRelease.height = 10\r\n\t\t$WindowsRelease.location = New-Object System.Drawing.Point(24,195)\r\n\t\t$WindowsRelease.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$WindowsRelease.ForeColor = $orangeColor\r\n\r\n\t\t$RunningVM = New-Object system.Windows.Forms.Label\r\n\t\t$RunningVM.text = \"False\"\r\n\t\t$RunningVM.AutoSize = $true\r\n\t\t$RunningVM.width = 25\r\n\t\t$RunningVM.height = 10\r\n\t\t$RunningVM.location = New-Object System.Drawing.Point(24,240)\r\n\t\t$RunningVM.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$RunningVM.ForeColor = $orangeColor\r\n\r\n\t\t$usernameContainsSpaces = New-Object system.Windows.Forms.Label\r\n\t\t$usernameContainsSpaces.text = \"False\"\r\n\t\t$usernameContainsSpaces.AutoSize = $true\r\n\t\t$usernameContainsSpaces.width = 25\r\n\t\t$usernameContainsSpaces.height = 10\r\n\t\t$usernameContainsSpaces.location = New-Object System.Drawing.Point(24,285)\r\n\t\t$usernameContainsSpaces.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$usernameContainsSpaces.ForeColor = $errorColor\r\n\r\n\t\t$EnoughHardStorage = New-Object system.Windows.Forms.Label\r\n\t\t$EnoughHardStorage.text = \"False\"\r\n\t\t$EnoughHardStorage.AutoSize = $true\r\n\t\t$EnoughHardStorage.width = 25\r\n\t\t$EnoughHardStorage.height = 10\r\n\t\t$EnoughHardStorage.location = New-Object System.Drawing.Point(24,322)\r\n\t\t$EnoughHardStorage.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$EnoughHardStorage.ForeColor = $orangeColor\r\n\r\n\t\t$internetConnectivity = New-Object system.Windows.Forms.Label\r\n\t\t$internetConnectivity.text = \"False\"\r\n\t\t$internetConnectivity.AutoSize = $true\r\n\t\t$internetConnectivity.width = 25\r\n\t\t$internetConnectivity.height = 10\r\n\t\t$internetConnectivity.location = New-Object System.Drawing.Point(24,368)\r\n\t\t$internetConnectivity.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$internetConnectivity.ForeColor = $errorColor\r\n\r\n\t\t$WindowsDefender = New-Object system.Windows.Forms.Label\r\n\t\t$WindowsDefender.text = \"False\"\r\n\t\t$WindowsDefender.AutoSize = $true\r\n\t\t$WindowsDefender.width = 25\r\n\t\t$WindowsDefender.height = 10\r\n\t\t$WindowsDefender.location = New-Object System.Drawing.Point(24,409)\r\n\t\t$WindowsDefender.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t$WindowsDefender.ForeColor = $orangeColor\r\n\r\n\t\t################################# Check Tooltip Controls #################################\r\n\t\t$verticalPosition = 41\r\n\r\n\t\t# $PSVersionTooltip\r\n\t\t$PSVersionTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$PSVersionTooltip.text = \"Powershell version must be >= 5 (mandatory)\"\r\n\t\t$PSVersionTooltip.AutoSize = $true\r\n\t\t$PSVersionTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$PSVersionTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$PSVersionTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $RunningAsAdminTooltip\r\n\t\t$RunningAsAdminTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$RunningAsAdminTooltip.text = \"You must run the script as Administrator (mandatory)\"\r\n\t\t$RunningAsAdminTooltip.AutoSize = $true\r\n\t\t$RunningAsAdminTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$RunningAsAdminTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$RunningAsAdminTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $ExecutionPolicyTooltip\r\n\t\t$ExecutionPolicyTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$ExecutionPolicyTooltip.text = \"You must enable script execution (mandatory)\"\r\n\t\t$ExecutionPolicyTooltip.AutoSize = $true\r\n\t\t$ExecutionPolicyTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$ExecutionPolicyTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$ExecutionPolicyTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $validWindowsVersionToolTip\r\n\t\t$validWindowsVersionToolTip = New-Object system.Windows.Forms.Label\r\n\t\t$validWindowsVersionToolTip.text = \"Only Windows Version >= 10 is supported (mandatory)\"\r\n\t\t$validWindowsVersionToolTip.AutoSize = $true\r\n\t\t$validWindowsVersionToolTip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$validWindowsVersionToolTip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$validWindowsVersionToolTip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $WindowsReleaseTooltip\r\n\t\t$WindowsReleaseTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$WindowsReleaseTooltip.text = \"You might run into issues when using a non tested version\"\r\n\t\t$WindowsReleaseTooltip.AutoSize = $true\r\n\t\t$WindowsReleaseTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$WindowsReleaseTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$WindowsReleaseTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $RunningVMTooltip\r\n\t\t$RunningVMTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$RunningVMTooltip.text = \"Only run this script inside a Virtual Machine (VM)\"\r\n\t\t$RunningVMTooltip.AutoSize = $true\r\n\t\t$RunningVMTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$RunningVMTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$RunningVMTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $usernameContainsSpacesToolTip\r\n\t\t$usernameContainsSpacesToolTip = New-Object system.Windows.Forms.Label\r\n\t\t$usernameContainsSpacesToolTip.text = \"Username cannot contain spaces (mandatory)\"\r\n\t\t$usernameContainsSpacesToolTip.AutoSize = $true\r\n\t\t$usernameContainsSpacesToolTip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$usernameContainsSpacesToolTip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$usernameContainsSpacesToolTip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $EnoughHardStorageTooltip\r\n\t\t$EnoughHardStorageTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$EnoughHardStorageTooltip.text = \"A minimum of 60 GB hard drive space is preferred\"\r\n\t\t$EnoughHardStorageTooltip.AutoSize = $true\r\n\t\t$EnoughHardStorageTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$EnoughHardStorageTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$EnoughHardStorageTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $internetConnectivityTooltip\r\n\t\t$internetConnectivityTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$internetConnectivityTooltip.text = \"You must have internet connection (mandatory)\"\r\n\t\t$internetConnectivityTooltip.AutoSize = $true\r\n\t\t$internetConnectivityTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$internetConnectivityTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$internetConnectivityTooltip.ForeColor = $grayedColor\r\n\t\t$verticalPosition += 44\r\n\r\n\t\t# $WindowsDefenderTooltip\r\n\t\t$WindowsDefenderTooltip = New-Object system.Windows.Forms.Label\r\n\t\t$WindowsDefenderTooltip.text = \"Disable Windows Defender and Tamper Protection\"\r\n\t\t$WindowsDefenderTooltip.AutoSize = $true\r\n\t\t$WindowsDefenderTooltip.location = New-Object System.Drawing.Point(15,$verticalPosition)\r\n\t\t$WindowsDefenderTooltip.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$WindowsDefenderTooltip.ForeColor = $grayedColor\r\n\r\n\r\n\t\t################################# Check Completion Controls #################################\r\n\r\n\t\t$breakInstallationLabel                = New-Object system.Windows.Forms.Label\r\n\t\t$breakInstallationLabel.Text           = $exit_message\r\n\t\t$breakInstallationLabel.AutoSize       = $true\r\n\t\t$breakInstallationLabel.location       = New-Object System.Drawing.Point(40,530)\r\n\t\t$breakInstallationLabel.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',12)\r\n\t\t$breakInstallationLabel.ForeColor      = $errorColor\r\n\t\t$breakInstallationLabel.Visible        = $false\r\n\r\n\t\t$BreakMyInstallCheckbox          = New-Object system.Windows.Forms.CheckBox\r\n\t\t$BreakMyInstallCheckbox.Visible  = $false\r\n\t\t$BreakMyInstallCheckbox.text     = \"I understand that continuing without satisfying all pre-install checks might cause install issues\"\r\n\t\t$BreakMyInstallCheckbox.AutoSize = $true\r\n\t\t$BreakMyInstallCheckbox.width    = 324\r\n\t\t$BreakMyInstallCheckbox.height   = 21\r\n\t\t$BreakMyInstallCheckbox.location = New-Object System.Drawing.Point(30,510)\r\n\t\t$BreakMyInstallCheckbox.Font     = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n        $snapshotCheckBox \t             = New-Object system.Windows.Forms.CheckBox\r\n\t\t$snapshotCheckBox.Text           = \"I have taken a VM snapshot to ensure I can revert to pre-installation state\"\r\n\t\t$snapshotCheckBox.AutoSize       = $true\r\n\t\t$snapshotCheckBox.location       = New-Object System.Drawing.Point(30,532)\r\n\t\t$snapshotCheckBox.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$snapshotCheckBox.Visible \t     = $false\r\n\r\n\t\t$ChecksCompleteButton            = New-Object system.Windows.Forms.Button\r\n\t\t$ChecksCompleteButton.text       = \"Continue\"\r\n\t\t$ChecksCompleteButton.width      = 97\r\n\t\t$ChecksCompleteButton.height     = 37\r\n\t\t$ChecksCompleteButton.enabled    = $false\r\n\t\t$ChecksCompleteButton.DialogResult   = [System.Windows.Forms.DialogResult]::OK\r\n\t\t$ChecksCompleteButton.location   = New-Object System.Drawing.Point(420,565)\r\n\t\t$ChecksCompleteButton.Font       = New-Object System.Drawing.Font('Microsoft Sans Serif',12)\r\n\t\t$ChecksCompleteButton.Add_Click({\r\n\t\t\t$script:checksPassed = $true\r\n\t\t\t[void]$formChecksManager.Close()\r\n\t\t})\r\n\r\n\t\t$checksCancelButton            = New-Object system.Windows.Forms.Button\r\n\t\t$checksCancelButton.Text       = \"Cancel\"\r\n\t\t$checksCancelButton.width      = 97\r\n\t\t$checksCancelButton.height     = 37\r\n\t\t$checksCancelButton.location   = New-Object System.Drawing.Point(519,565)\r\n\t\t$checksCancelButton.Font       = New-Object System.Drawing.Font('Microsoft Sans Serif',12)\r\n\t\t$checksCancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel\r\n\r\n\t\t$InstallChecksGroup.controls.AddRange(@($ChecksPanel,$RunningAsAdminLabel,$ExecutionPolicyLabel,$WindowsDefenderLabel,$WindowsReleaseLabel,$RunningVMLabel,$PSVersionLabel,$internetConnectivityLabel,$validWindowsVersionLabel,$validWindowsVersionToolTip,$RunningAsAdminTooltip,$ExecutionPolicyTooltip,$WindowsDefenderTooltip,$WindowsReleaseTooltip,$RunningVMTooltip,$EnoughHardStorageLabel, $EnoughHardStorageTooltip,$PSVersionTooltip,$internetConnectivityTooltip,$usernameContainsSpacesLabel,$usernameContainsSpacesToolTip,$RunningAsAdmin,$EnoughHardStorage))\r\n\t\t$formChecksManager.controls.AddRange(@($InstallChecksGroup,$ChecksCompleteButton,$checksCancelButton,$BreakMyInstallCheckbox,$snapshotCheckBox,$breakInstallationLabel))\r\n\t\t$ChecksPanel.controls.AddRange(@($RunningAsAdmin, $ExecutionPolicy,$WindowsDefender,$WindowsRelease,$RunningVM, $EnoughHardStorage, $PSVersion, $internetConnectivity, $validWindowsVersion,$usernameContainsSpaces ))\r\n\r\n\t    # Make sure that the user completed all pre-install steps\r\n\t\t$error_info = Test-Admin\r\n\t\tif ($error_info){\r\n\t\t\t$RunningAsAdmin.Text = $error_info\r\n\t\t\t$RunningAsAdmin.Forecolor = $errorColor\r\n            $mandatoryChecksPassed = $false\r\n        } else {\r\n\t\t\t$RunningAsAdmin.Text = \"True\"\r\n            $RunningAsAdmin.ForeColor = $successColor\r\n        }\r\n\t\t$error_info = Test-ExecutionPolicy\r\n\t\tif ($error_info){\r\n\t\t\t$ExecutionPolicyTooltip.Text = $error_info\r\n\t\t\t$ExecutionPolicyTooltip.Forecolor = $errorColor\r\n            $mandatoryChecksPassed = $false\r\n        } else {\r\n\t\t\t$ExecutionPolicy.Text = \"True\"\r\n            $ExecutionPolicy.ForeColor = $successColor\r\n        }\r\n\t\t$error_info = Test-DefenderAndTamperProtection\r\n\t\tif ($error_info){\r\n\t\t\t$WindowsDefenderTooltip.Text = $error_info\r\n\t\t\t$WindowsDefenderTooltip.Forecolor = $orangeColor\r\n            $script:checksPassed = $false\r\n        } else {\r\n\t\t\t$WindowsDefender.Text = \"True\"\r\n            $WindowsDefender.ForeColor = $successColor\r\n        }\r\n\r\n\t\t$error_info = Test-TestedOS\r\n\t\tif ($error_info){\r\n            $WindowsReleaseTooltip.Text = $error_info\r\n\t\t\t$WindowsReleaseTooltip.Forecolor = $orangeColor\r\n            $script:checksPassed = $false\r\n        } else {\r\n\t\t\t$WindowsRelease.Text = \"True\"\r\n            $WindowsRelease.ForeColor = $successColor\r\n        }\r\n\t\t$error_info = Test-VM\r\n\t\tif ($error_info){\r\n            $RunningAsAdminTooltip.Text = $error_info\r\n\t\t\t$RunningAsAdminTooltip.Forecolor = $orangeColor\r\n            $script:checksPassed = $false\r\n        } else {\r\n\t\t\t$RunningVM.Text = \"True\"\r\n            $RunningVM.ForeColor = $successColor\r\n        }\r\n\t\t$error_info = Test-Storage\r\n\t\tif ($error_info){\r\n            $EnoughHardStorageTooltip.Forecolor = $orangeColor\r\n\t\t\t$EnoughHardStorageTooltip.Text = $error_info\r\n            $script:checksPassed = $false\r\n        } else {\r\n\t\t\t$EnoughHardStorage.Text = \"True\"\r\n            $EnoughHardStorage.ForeColor = $successColor\r\n        }\r\n\t\t$error_info = Test-PSVersion\r\n\t\tif ($error_info){\r\n\t\t\t$PSVersionTooltip.Text = $error_info\r\n\t\t\t$PSVersionTooltip.Forecolor = $errorColor\r\n            $MandatoryChecksPassed = $false\r\n\t\t} else {\r\n\t\t\t$PSVersion.Text = \"True\"\r\n\t\t\t$PSVersion.ForeColor = $successColor\r\n        }\r\n\r\n\t\t$error_info = Test-WebConnection 'google.com'\r\n\t\tif ($error_info){\r\n\t\t\t$internetConnectivityTooltip.Text = $error_info\r\n\t\t\t$internetConnectivityTooltip.Forecolor = $errorColor\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}else {\r\n\t\t\t$error_info = Test-WebConnection 'github.com'\r\n\t\t\tif ($error_info){\r\n\t\t\t\t$internetConnectivityTooltip.Text = $error_info\r\n\t\t\t\t$internetConnectivityTooltip.Forecolor = $errorColor\r\n\t\t\t    $mandatoryChecksPassed = $false\r\n\t\t\t}else {\r\n\t\t\t\t$error_info = Test-WebConnection 'raw.githubusercontent.com'\r\n\t\t\t\tif ($error_info){\r\n\t\t\t\t    $internetConnectivityTooltip.Text = $error_info\r\n\t\t\t\t\t$internetConnectivityTooltip.Forecolor = $errorColor\r\n\t\t\t        $mandatoryChecksPassed = $false\r\n\t\t\t    } else {\r\n\t                $internetConnectivity.Text = \"True\"\r\n\t\t\t        $internetConnectivity.ForeColor = $successColor\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t$error_info = Test-WindowsVersion\r\n\t\tif ($error_info){\r\n\t\t\t$validWindowsVersionToolTip.Text = $error_info\r\n\t\t\t$validWindowsVersionToolTip.Forecolor = $errorColor\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t} else {\r\n\t\t\t$validWindowsVersion.Text = \"True\"\r\n\t\t\t$validWindowsVersion.ForeColor = $successColor\r\n\t\t}\r\n\t\t$error_info = Test-SpaceUserName\r\n\t\tif ($error_info){\r\n\t\t\t$usernameContainsSpacesToolTip.Text = $error_info\r\n\t\t\t$usernameContainsSpacesToolTip.Forecolor = $errorColor\r\n\t\t\t$mandatoryChecksPassed = $false\r\n\t\t}else {\r\n\t\t\t$usernameContainsSpaces.Text = \"True\"\r\n\t\t\t$usernameContainsSpaces.ForeColor = $successColor\r\n\t\t}\r\n\r\n\t\t#only display the checkbox if some checks did not pass\r\n\t\tif ($mandatoryChecksPassed){\r\n\t\t\tif ($script:checksPassed){\r\n\t\t\t    $BreakMyInstallCheckbox.Visible = $false\r\n\t\t\t    $snapshotCheckBox.Visible = $true\r\n\t\t\t}else{\r\n\t\t\t\t$BreakMyInstallCheckbox.Visible = $true\r\n\t\t\t\t$snapshotCheckBox.Visible = $true\r\n\t\t\t}\r\n\t\t}else{\r\n\t\t\t$breakInstallationLabel.visible = $true\r\n\t\t}\r\n\r\n\t\t$snapshotCheckBox.Add_CheckStateChanged({\r\n\t\t\tif (($snapshotCheckBox.Checked) -and ($script:checksPassed)){\r\n\t\t\t\t$ChecksCompleteButton.enabled = $true\r\n\t\t\t} else {\r\n\t\t\t\tif (($snapshotCheckBox.Checked) -and (-not $script:checksPassed)){\r\n\t\t\t\t   $ChecksCompleteButton.enabled = $BreakMyInstallCheckbox.Checked\r\n\t\t\t    } else{\r\n\t\t\t\t    if (-not ($snapshotCheckBox.Checked)){\r\n\t\t\t\t        $ChecksCompleteButton.enabled = $false\r\n\t\t\t\t\t}\r\n\t\t\t    }\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t$BreakMyInstallCheckbox.Add_CheckStateChanged({\r\n\t\t\tif ($BreakMyInstallCheckbox.Checked){\r\n\t\t\t\t  $ChecksCompleteButton.enabled = $snapshotCheckBox.Checked\r\n\t\t\t} else{\r\n\t\t\t   $ChecksCompleteButton.enabled = $false\r\n\t\t\t}\r\n\t\t})\r\n        Open-CheckManager\r\n\t}\r\n    # init GUI controls of the install customization Window\r\n    $formEnv                   = New-Object system.Windows.Forms.Form\r\n    $formEnv.ClientSize        = New-Object System.Drawing.Point(750,350)\r\n    $formEnv.text              = \"FLARE VM Install Customization\"\r\n    $formEnv.TopMost           = $true\r\n    $formEnv.MaximizeBox       = $false\r\n    $formEnv.FormBorderStyle   = 'FixedDialog'\r\n    $formEnv.StartPosition     = 'CenterScreen'\r\n\r\n    $envVarGroup            = New-Object system.Windows.Forms.Groupbox\r\n    $envVarGroup.height     = 201\r\n    $envVarGroup.width      = 690\r\n    $envVarGroup.text       = \"Environment Variable Customization\"\r\n    $envVarGroup.location   = New-Object System.Drawing.Point(15,59)\r\n\r\n    $welcomeLabel           = New-Object system.Windows.Forms.Label\r\n    $welcomeLabel.text      = \"Welcome to FLARE VM's custom installer. Please select your options below.`nDefault values will be used if you make no modifications.\"\r\n    $welcomeLabel.AutoSize  = $true\r\n    $welcomeLabel.width     = 25\r\n    $welcomeLabel.height    = 10\r\n    $welcomeLabel.location  = New-Object System.Drawing.Point(15,14)\r\n    $welcomeLabel.Font      = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $vmCommonDirText                 = New-Object system.Windows.Forms.TextBox\r\n    $vmCommonDirText.multiline       = $false\r\n    $vmCommonDirText.width           = 385\r\n    $vmCommonDirText.height          = 20\r\n    $vmCommonDirText.location        = New-Object System.Drawing.Point(190,21)\r\n    $vmCommonDirText.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $vmCommonDirSelect               = New-Object system.Windows.Forms.Button\r\n    $vmCommonDirSelect.text          = \"Select Folder\"\r\n    $vmCommonDirSelect.width         = 95\r\n    $vmCommonDirSelect.height        = 30\r\n    $vmCommonDirSelect.location      = New-Object System.Drawing.Point(588,17)\r\n    $vmCommonDirSelect.Font          = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n    $selectFolderArgs1 = @{textBox=$vmCommonDirText; envVar=\"VM_COMMON_DIR\"}\r\n    $vmCommonDirSelect.Add_Click({Get-Folder @selectFolderArgs1})\r\n\r\n    $vmCommonDirLabel                = New-Object system.Windows.Forms.Label\r\n    $vmCommonDirLabel.text           = \"%VM_COMMON_DIR%\"\r\n    $vmCommonDirLabel.AutoSize       = $true\r\n    $vmCommonDirLabel.width          = 25\r\n    $vmCommonDirLabel.height         = 10\r\n    $vmCommonDirLabel.location       = New-Object System.Drawing.Point(2,24)\r\n    $vmCommonDirLabel.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',9.5,[System.Drawing.FontStyle]::Bold)\r\n\r\n    $vmCommonDirNote                 = New-Object system.Windows.Forms.Label\r\n    $vmCommonDirNote.text            = \"Shared module and metadata for VM (e.g., config, logs, etc...)\"\r\n    $vmCommonDirNote.AutoSize        = $true\r\n    $vmCommonDirNote.width           = 25\r\n    $vmCommonDirNote.height          = 10\r\n    $vmCommonDirNote.location        = New-Object System.Drawing.Point(190,46)\r\n    $vmCommonDirNote.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $toolListDirText                 = New-Object system.Windows.Forms.TextBox\r\n    $toolListDirText.multiline       = $false\r\n    $toolListDirText.width           = 385\r\n    $toolListDirText.height          = 20\r\n    $toolListDirText.location        = New-Object System.Drawing.Point(190,68)\r\n    $toolListDirText.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $toolListDirSelect               = New-Object system.Windows.Forms.Button\r\n    $toolListDirSelect.text          = \"Select Folder\"\r\n    $toolListDirSelect.width         = 95\r\n    $toolListDirSelect.height        = 30\r\n    $toolListDirSelect.location      = New-Object System.Drawing.Point(588,64)\r\n    $toolListDirSelect.Font          = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n    $selectFolderArgs2 = @{textBox=$toolListDirText; envVar=\"TOOL_LIST_DIR\"}\r\n    $toolListDirSelect.Add_Click({Get-Folder @selectFolderArgs2})\r\n\r\n    $toolListDirLabel                = New-Object system.Windows.Forms.Label\r\n    $toolListDirLabel.text           = \"%TOOL_LIST_DIR%\"\r\n    $toolListDirLabel.AutoSize       = $true\r\n    $toolListDirLabel.width          = 25\r\n    $toolListDirLabel.height         = 10\r\n    $toolListDirLabel.location       = New-Object System.Drawing.Point(2,71)\r\n    $toolListDirLabel.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',9.5,[System.Drawing.FontStyle]::Bold)\r\n\r\n    $toolListDirNote                 = New-Object system.Windows.Forms.Label\r\n    $toolListDirNote.text            = \"Folder to store tool categories and shortcuts\"\r\n    $toolListDirNote.AutoSize        = $true\r\n    $toolListDirNote.width           = 25\r\n    $toolListDirNote.height          = 10\r\n    $toolListDirNote.location        = New-Object System.Drawing.Point(190,94)\r\n    $toolListDirNote.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $rawToolsDirText                 = New-Object system.Windows.Forms.TextBox\r\n    $rawToolsDirText.multiline       = $false\r\n    $rawToolsDirText.width           = 385\r\n    $rawToolsDirText.height          = 20\r\n    $rawToolsDirText.location        = New-Object System.Drawing.Point(190,113)\r\n    $rawToolsDirText.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $rawToolsDirSelect               = New-Object system.Windows.Forms.Button\r\n    $rawToolsDirSelect.text          = \"Select Folder\"\r\n    $rawToolsDirSelect.width         = 95\r\n    $rawToolsDirSelect.height        = 30\r\n    $rawToolsDirSelect.location      = New-Object System.Drawing.Point(588,109)\r\n    $rawToolsDirSelect.Font          = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n    $selectFolderArgs4 = @{textBox=$rawToolsDirText; envVar=\"RAW_TOOLS_DIR\"}\r\n    $rawToolsDirSelect.Add_Click({Get-Folder @selectFolderArgs4})\r\n\r\n    $rawToolsDirLabel                = New-Object system.Windows.Forms.Label\r\n    $rawToolsDirLabel.text           = \"%RAW_TOOLS_DIR%\"\r\n    $rawToolsDirLabel.AutoSize       = $true\r\n    $rawToolsDirLabel.width          = 25\r\n    $rawToolsDirLabel.height         = 10\r\n    $rawToolsDirLabel.location       = New-Object System.Drawing.Point(2,116)\r\n    $rawToolsDirLabel.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',9.5,[System.Drawing.FontStyle]::Bold)\r\n\r\n    $rawToolsDirNote                 = New-Object system.Windows.Forms.Label\r\n    $rawToolsDirNote.text            = \"Folder to store downloaded tools\"\r\n    $rawToolsDirNote.AutoSize        = $true\r\n    $rawToolsDirNote.width           = 25\r\n    $rawToolsDirNote.height          = 10\r\n    $rawToolsDirNote.location        = New-Object System.Drawing.Point(190,137)\r\n    $rawToolsDirNote.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n    $okButton                        = New-Object system.Windows.Forms.Button\r\n    $okButton.text                   = \"Continue\"\r\n    $okButton.width                  = 97\r\n    $okButton.height                 = 37\r\n    $okButton.location               = New-Object System.Drawing.Point(480,280)\r\n    $okButton.Font                   = New-Object System.Drawing.Font('Microsoft Sans Serif',11)\r\n    $okButton.DialogResult = [System.Windows.Forms.DialogResult]::OK\r\n\r\n    $cancelButton                    = New-Object system.Windows.Forms.Button\r\n    $cancelButton.text               = \"Cancel\"\r\n    $cancelButton.width              = 97\r\n    $cancelButton.height             = 37\r\n    $cancelButton.location           = New-Object System.Drawing.Point(580,280)\r\n    $cancelButton.Font               = New-Object System.Drawing.Font('Microsoft Sans Serif',11)\r\n    $cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel\r\n\r\n    $formEnv.controls.AddRange(@($envVarGroup,$okButton,$cancelButton,$welcomeLabel))\r\n    $formEnv.AcceptButton = $okButton\r\n    $formEnv.CancelButton = $cancelButton\r\n\r\n    $envVarGroup.controls.AddRange(@($vmCommonDirText,$vmCommonDirSelect,$vmCommonDirLabel,$toolListDirText,$toolListDirSelect,$toolListDirLabel,$toolListShortCutText,$toolListShortcutSelect,$toolListShortcutLabel,$vmCommonDirNote,$toolListDirNote,$toolListShortcutNote,$rawToolsDirText,$rawToolsDirSelect,$rawToolsDirLabel,$rawToolsDirNote))\r\n\r\n}\r\nif (-not $noPassword.IsPresent) {\r\n    # Get user credentials for autologin during reboots\r\n    if ([string]::IsNullOrEmpty($password)) {\r\n        Write-Host \"[+] Getting user credentials ...\"\r\n        Set-ItemProperty \"HKLM:\\SOFTWARE\\Microsoft\\PowerShell\\1\\ShellIds\" -Name \"ConsolePrompting\" -Value $True\r\n        Start-Sleep -Milliseconds 500\r\n        $credentials = Get-Credential ${Env:UserName}\r\n    } else {\r\n        $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force\r\n        $credentials = New-Object -TypeName \"System.Management.Automation.PSCredential\" -ArgumentList ${Env:UserName}, $securePassword\r\n    }\r\n}\r\n\r\n# Check Boxstarter version\r\n$boxstarterVersionGood = $false\r\nif (${Env:ChocolateyInstall} -and (Test-Path \"${Env:ChocolateyInstall}\\bin\\choco.exe\")) {\r\n    choco info -l -r \"boxstarter\" | ForEach-Object { $name, $version = $_ -split '\\|' }\r\n    $boxstarterVersionGood = [System.Version]$version -ge [System.Version]\"3.0.2\"\r\n}\r\n\r\n# Install Boxstarter if needed\r\nif (-not $boxstarterVersionGood) {\r\n    Write-Host \"[+] Installing Boxstarter...\" -ForegroundColor Cyan\r\n    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072\r\n    Invoke-Expression ((New-Object System.Net.WebClient).DownloadString('https://boxstarter.org/bootstrapper.ps1'))\r\n    Get-Boxstarter -Force\r\n\r\n    Start-Sleep -Milliseconds 500\r\n}\r\nImport-Module \"${Env:ProgramData}\\boxstarter\\boxstarter.chocolatey\\boxstarter.chocolatey.psd1\" -Force\r\n\r\n# Check Chocolatey version\r\n$version = choco --version\r\n$chocolateyVersionGood = [System.Version]$version -ge [System.Version]\"2.0.0\"\r\n\r\n# Update Chocolatey if needed\r\nif (-not ($chocolateyVersionGood)) { choco upgrade chocolatey }\r\n\r\n# Attempt to disable updates (i.e., windows updates and store updates)\r\nWrite-Host \"[+] Attempting to disable updates...\"\r\nDisable-MicrosoftUpdate\r\ntry {\r\n  New-ItemProperty -Path \"HKLM:\\SOFTWARE\\Policies\\Microsoft\\WindowsStore\" -Name \"AutoDownload\" -PropertyType DWord -Value 2 -ErrorAction Stop -Force | Out-Null\r\n} catch {\r\n  Write-Host \"`t[!] Failed to disable Microsoft Store updates\" -ForegroundColor Yellow\r\n}\r\n\r\n# Set Boxstarter options\r\n$Boxstarter.RebootOk = (-not $noReboots.IsPresent)\r\n$Boxstarter.NoPassword = $noPassword.IsPresent\r\n$Boxstarter.AutoLogin = $true\r\n$Boxstarter.SuppressLogging = $True\r\n$VerbosePreference = \"SilentlyContinue\"\r\nSet-BoxstarterConfig -NugetSources \"$desktopPath;.;https://www.myget.org/F/vm-packages/api/v2;https://myget.org/F/vm-packages/api/v2;https://chocolatey.org/api/v2\"\r\nSet-WindowsExplorerOptions -EnableShowHiddenFilesFoldersDrives -EnableShowProtectedOSFiles -EnableShowFileExtensions -EnableShowFullPathInTitleBar\r\n\r\n# Set Chocolatey options\r\nWrite-Host \"[+] Updating Chocolatey settings...\"\r\nchoco sources add -n=\"vm-packages\" -s \"$desktopPath;.;https://www.myget.org/F/vm-packages/api/v2;https://myget.org/F/vm-packages/api/v2\" --priority 1\r\nchoco feature enable -n allowGlobalConfirmation\r\nchoco feature enable -n allowEmptyChecksums\r\n$cache = \"${Env:LocalAppData}\\ChocoCache\"\r\nNew-Item -Path $cache -ItemType directory -Force | Out-Null\r\nchoco config set cacheLocation $cache\r\n\r\n# Set power options to prevent installs from timing out\r\npowercfg -change -monitor-timeout-ac 0 | Out-Null\r\npowercfg -change -monitor-timeout-dc 0 | Out-Null\r\npowercfg -change -disk-timeout-ac 0 | Out-Null\r\npowercfg -change -disk-timeout-dc 0 | Out-Null\r\npowercfg -change -standby-timeout-ac 0 | Out-Null\r\npowercfg -change -standby-timeout-dc 0 | Out-Null\r\npowercfg -change -hibernate-timeout-ac 0 | Out-Null\r\npowercfg -change -hibernate-timeout-dc 0 | Out-Null\r\n\r\nWrite-Host \"[+] Checking for configuration file...\"\r\n$configPath = Join-Path $desktopPath \"config.xml\"\r\nif ([string]::IsNullOrEmpty($customConfig)) {\r\n    Write-Host \"[+] Using github configuration file...\"\r\n    $configSource = 'https://raw.githubusercontent.com/mandiant/flare-vm/main/config.xml'\r\n} else {\r\n    Write-Host \"[+] Using custom configuration file...\"\r\n    $configSource = $customConfig\r\n}\r\n\r\nGet-ConfigFile $configPath $configSource\r\n\r\nWrite-Host \"Configuration file path: $configPath\"\r\n\r\n# Check the configuration file exists\r\nif (-Not (Test-Path $configPath)) {\r\n    Write-Host \"`t[!] Configuration file missing: \" $configPath -ForegroundColor Red\r\n    Write-Host \"`t[-] Please download config.xml from $configPathUrl to your desktop\" -ForegroundColor Yellow\r\n    Write-Host \"`t[-] Is the file on your desktop? (Y/N): \" -ForegroundColor Yellow -NoNewline\r\n    $response = Read-Host\r\n    if ($response -notin @(\"y\",\"Y\")) {\r\n        exit 1\r\n    }\r\n    if (-Not (Test-Path $configPath)) {\r\n        Write-Host \"`t[!] Configuration file still missing: \" $configPath -ForegroundColor Red\r\n        Write-Host \"`t[!] Exiting...\" -ForegroundColor Red\r\n        Start-Sleep 3\r\n        exit 1\r\n    }\r\n}\r\n\r\n# Get config contents\r\nStart-Sleep 1\r\n$configXml = [xml](Get-Content $configPath)\r\n\r\n\r\n\r\n#########################################################################\r\n# GUI Functions\r\n#########################################################################\r\n\r\nfunction Get-Folder($textBox, $envVar) {\r\n\t$folderBrowserDialog = New-Object System.Windows.Forms.FolderBrowserDialog\r\n\t$folderBrowserDialog.RootFolder = 'MyComputer'\r\n\tif ($folderBrowserDialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {\r\n\t\t$textbox.text = (Join-Path $folderBrowserDialog.SelectedPath (Split-Path $envs[$envVar] -Leaf))\r\n\t}\r\n}\r\n\r\n# Function that accesses MyGet vm-packages API URL to process packages that are the latest version and have a category\r\n# Saves vm-packages.xml into disk and follows the link after the </entry> tag to retrieve a new version of the XML file\r\n# Returns $packagesByCategory, a hashtable of arrays, where each entry is a PSCustomObject\r\nfunction Get-Packages-Categories {\r\n   # MyGet API URL that contains a filter to display only the latest packages\r\n   # This URL displays the last two versions of a package\r\n   # Minimize the number of HTTP requests to display all the packages due to the number of versions a package might have\r\n   $vmPackagesUrl = \"https://www.myget.org/F/vm-packages/api/v2/Packages?$filter=IsLatestVersion%20eq%20true\"\r\n   $vmPackagesFile = \"${Env:VM_COMMON_DIR}\\vm-packages.xml\"\r\n   $packagesByCategory=@{}\r\n   do {\r\n\t  # Download the XML from MyGet API\r\n\t  Save-FileFromUrl -fileSource $vmPackagesUrl -fileDestination $vmPackagesFile --exitOnError\r\n\r\n\t  # Load the XML content\r\n\t  [xml]$vm_packages = Get-Content $vmPackagesFile\r\n\r\n\t  # Define the namespaces defined in vm-packages.xml to access nodes\r\n  # Each package resides in the entry node that is defined in the dataservices namespace\r\n\t  # Each node has properties that are defined in the metadata namespace\r\n\t  $ns = New-Object System.Xml.XmlNamespaceManager($vm_packages.NameTable)\r\n\t  $ns.AddNamespace(\"atom\", \"http://www.w3.org/2005/Atom\")\r\n\t  $ns.AddNamespace(\"d\", \"http://schemas.microsoft.com/ado/2007/08/dataservices\")\r\n\t  $ns.AddNamespace(\"m\", \"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\")\r\n\r\n\t  # Extract package information from the XML\r\n\t  $vm_packages.feed.entry | ForEach-Object {\r\n\t\t $isLatestVersion = $_.SelectSingleNode(\"m:properties/d:IsLatestVersion\", $ns).InnerText\r\n\t\t $category = $_.SelectSingleNode(\"m:properties/d:Tags\", $ns).InnerText\r\n\t\t # Select only packages that have the latest version, contain a category and the category is not excluded\r\n\t\t if (($isLatestVersion -eq \"true\") -and ($category -ne \"\") -and ($excludedCategories -notcontains $category)) {\r\n\t            $packageName = $_.properties.Id\r\n\t\t\t\t$description = $_.properties.Description\r\n\t\t\t\t$projectUrl = $_.properties.projectUrl\r\n\r\n\t\t\t\t# Initialize category as an empty array\r\n\t\t\t\tif (-not ($packagesByCategory.ContainsKey($category))) {\r\n\t\t\t\t\t $packagesByCategory[$category] = @()\r\n\t\t\t\t}\r\n\t\t\t\t$packageObject = [PSCustomObject]@{\r\n\t\t\t\tPackageName        = $packageName\r\n\t\t\t\tPackageDescription = $description\r\n\t\t\t\t}\r\n\t\t\t\t# Check if $projectUrl contains a valid URL\r\n\t\t\t\tif ($projectUrl -match \"^http\") {\r\n\t\t\t\t\tAdd-Member -InputObject $packageObject -MemberType NoteProperty -Name \"PackageUrl\" -Value $projectURl\r\n\t\t\t\t}\r\n\t\t\t\t# Add the PackageName and PackageDescription (and PackageUrl if present) to each entry in the array\r\n\t\t\t\t$packagesByCategory[$category] += $packageObject\r\n            }\r\n\t\t  }\r\n\t  # Check if there is a next link in the XML and set the API URL to that link if it exists\r\n\t  $nextLink = $vm_packages.SelectSingleNode(\"//atom:link[@rel='next']/@href\", $ns)\r\n\t  $vmPackagesUrl = $nextLink.\"#text\"\r\n\r\n   } while ($vmPackagesUrl)\r\n\r\n  return $packagesByCategory\r\n}\r\n\r\n# Function that returns an array of all the packages that are displayed sorted by category from $packagesByCategory\r\nfunction Get-AllPackages{\r\n\t$listedPackages = $packagesByCategory.Values | ForEach-Object { $_ } | Select-Object -ExpandProperty PackageName\r\n\treturn $listedPackages\r\n}\r\n\r\n# Function that returns additional packages from the config that are not displayed in the textboxes\r\n# which includes both Choco packages and packages from excluded categories\r\nfunction Get-AdditionalPackages{\r\n   $additionalPackages=@()\r\n\r\n   # Packages from the config that are not displayed\r\n   $additionalPackages = $packagesToInstall | where-Object { $listedPackages -notcontains $_}\r\n   return $additionalPackages\r\n}\r\n\r\nif (-not $noGui.IsPresent) {\r\n\r\n\tif ($script:checksPassed -or $noChecks.IsPresent) {\r\n        Write-Host \"[+] Beginning graphical install\"\r\n\r\n\t\t# Gather lists of packages\r\n\t\t$envs = [ordered]@{}\r\n\t\t$configXml.config.envs.env.ForEach({ $envs[$_.name] = $_.value })\r\n\t\t$excludedCategories=@('Command and Control','Credential Access','Exploitation','Forensic','Lateral Movement', 'Payload Development','Privilege Escalation','Reconnaissance','Wordlists','Web Application')\r\n\t\t# Read packages to install from the config\r\n\t\t$packagesToInstall = $configXml.config.packages.package.name\r\n\t\t$packagesByCategory = Get-Packages-Categories\r\n\t\t$listedPackages = Get-AllPackages\r\n\t\t$additionalPackages = Get-AdditionalPackages\r\n\r\n        $vmCommonDirText.text            = $envs['VM_COMMON_DIR']\r\n\t\t$rawToolsDirText.text            = $envs['RAW_TOOLS_DIR']\r\n\t\t$toolListDirText.text            = $envs['TOOL_LIST_DIR']\r\n\r\n\t\t$Result = $formEnv.ShowDialog()\r\n\r\n\t\tif ($Result -eq [System.Windows.Forms.DialogResult]::OK) {\r\n\t\t\t# Remove default environment variables\r\n\t\t\t$nodes = $configXml.SelectNodes('//config/envs/env')\r\n\t\t\tforeach($node in $nodes) {\r\n\t\t\t\t$node.ParentNode.RemoveChild($node) | Out-Null\r\n\t\t\t}\r\n\r\n\t\t\t# Add environment variables\r\n\t\t\t$envs = $configXml.SelectSingleNode('//envs')\r\n\t\t\t$newXmlNode = $envs.AppendChild($configXml.CreateElement(\"env\"))\r\n\t\t\t$newXmlNode.SetAttribute(\"name\", \"VM_COMMON_DIR\")\r\n\t\t\t$newXmlNode.SetAttribute(\"value\", $vmCommonDirText.text);\r\n\t\t\t$newXmlNode = $envs.AppendChild($configXml.CreateElement(\"env\"))\r\n\t\t\t$newXmlNode.SetAttribute(\"name\", \"TOOL_LIST_DIR\")\r\n\t\t\t$newXmlNode.SetAttribute(\"value\", $toolListDirText.text);\r\n\t\t\t$newXmlNode = $envs.AppendChild($configXml.CreateElement(\"env\"))\r\n\t\t\t$newXmlNode.SetAttribute(\"name\", \"RAW_TOOLS_DIR\")\r\n\t\t\t$newXmlNode.SetAttribute(\"value\", $rawToolsDirText.text)\r\n\r\n\t\t\t[void]$formEnv.Close()\r\n\r\n\t\t} else {\r\n\t\t\tWrite-Host \"[+] Cancel pressed, stopping installation...\"\r\n\t\t\tStart-Sleep 3\r\n\t\t\texit 1\r\n\t\t}\r\n\r\n\t\t################################################################################\r\n\t\t## PACKAGE SELECTION BY CATEGORY\r\n\t\t################################################################################\r\n\r\n\t\t# Function that adds the selected packages to the config.xml for the installation\r\n\t\tfunction Install-Selected-Packages{\r\n\t\t  $selectedPackages  = @()\r\n\t\t  $packages = $configXml.SelectSingleNode('//packages')\r\n\r\n\t\t  # Remove all child nodes inside <packages>\r\n\t\t  while ($packages.HasChildNodes) {\r\n\t\t\t$packages.RemoveChild($packages.FirstChild)\r\n\t\t  }\r\n\r\n\t\t  foreach ($checkBox in $checkboxesPackages){\r\n\t\t\tif ($checkBox.Checked){\r\n\t\t\t\t$package = $checkbox.Text.split(\":\")[0]\r\n\t\t\t\t$selectedPackages += $package\r\n\t\t\t}\r\n\t\t  }\r\n\r\n\t\t  foreach ($package in $additionalPackagesBox.Items){\r\n\t\t\t $selectedPackages += $package\r\n\t\t  }\r\n\t\t  # Add selected packages\r\n\t\t  foreach($package in $selectedPackages) {\r\n\t\t\t   $newXmlNode = $packages.AppendChild($configXml.CreateElement(\"package\"))\r\n\t\t\t   $newXmlNode.SetAttribute(\"name\", $package)\r\n\t\t  }\r\n\t\t}\r\n\r\n\t\t# Function that resets the checkboxes to match the config.xml\r\n\t\tfunction Set-InitialPackages {\r\n\t\t\tforeach ($checkBox in $checkboxesPackages){\r\n\t\t\t\t$package =$checkbox.Text.split(\":\")[0]\r\n\t\t\t\tif (($checkbox.Checked) -and ($package -notin $packagesToInstall)){\r\n\t\t\t\t\t$checkBox.Checked = $false\r\n\t\t\t\t}else{\r\n\t\t\t\t  if ((-not $checkbox.Checked ) -and ($package -in $packagesToInstall)){\r\n\t\t\t\t\t $checkBox.Checked = $true\r\n\t\t\t\t  }\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t# Function that returns an array of packages that belong to a specific category\r\n\t\tfunction Get-PackagesByCategory{\r\n\t\t\tparam (\r\n\t\t\t [string]$category\r\n\t\t\t)\r\n\t\t\treturn $packagesByCategory[$category]\r\n\t\t}\r\n\r\n\t\t# Function that returns additional packages from the config that are not displayed in the textboxes\r\n\t\t# which includes both Choco packages and packages from excluded categories\r\n\t\tfunction Get-AdditionalPackages{\r\n\t\t   $additionalPackages=@()\r\n\r\n\t\t   # Packages from the config that are not displayed\r\n\t\t   $additionalPackages = $packagesToInstall | where-Object { $listedPackages -notcontains $_}\r\n\t\t   return $additionalPackages\r\n\t\t}\r\n\r\n\t\t# Function that checks all the checkboxes\r\n\t\tfunction Select-AllPackages {\r\n\t\t\tforeach ($checkBox in $checkboxesPackages){\r\n\t\t\t\t$checkBox.Checked = $true\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t# Function that unchecks all the checkboxes\r\n\t\tfunction Clear-AllPackages {\r\n\t\t\tforeach ($checkBox in $checkboxesPackages){\r\n\t\t\t\t$checkBox.Checked = $false\r\n\t\t\t}\r\n\t\t\t$additionalPackagesBox.Items.clear()\r\n\t\t}\r\n\r\n\t\t# Function that adds a new package to the listBox of additional packages\r\n\t\t# If the package already exists it returns $false\r\n\t\tfunction Add-NewPackage {\r\n\t\t\tparam (\r\n\t\t\t[Parameter(Mandatory=$true)]\r\n\t\t\t[string]$packageName\r\n\t\t\t)\r\n\t\t\t#$packageName = $packageName.Trim()\r\n\t\t\t$packageName = $packageName -replace '^\\s+|\\s+$', ''\r\n\t\t\tif ($packageName -notin $additionalPackagesBox.Items){\r\n\t\t\t   $additionalPackagesBox.Items.Add($packageName) | Out-Null\r\n\t\t\t   return $true\r\n\t\t\t}\r\n\t\t\telse{\r\n\t\t\t   return $false\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\tfunction Get-ChocoPackage {\r\n\t\t\tparam (\r\n\t\t\t[Parameter(Mandatory=$true)]\r\n\t\t\t[string]$PackageName\r\n\t\t\t)\r\n\r\n\t\t\tchoco search $PackageName -e -r | ForEach-Object {\r\n\t\t\t\t$Name, $Version = $_ -split '\\|'\r\n\t\t\t\tNew-Object -TypeName psobject -Property @{\r\n\t\t\t\t\t'Name' = $Name\r\n\t\t\t\t\t'Version' = $Version\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t function Get-VMPackage {\r\n\t\t\tparam (\r\n\t\t\t[Parameter(Mandatory=$true)]\r\n\t\t\t[string]$PackageName\r\n\t\t\t)\r\n\t\t\tif ($PackageName -notlike \"*.vm\") {\r\n\t\t\t\t$PackageName = $PackageName + \".vm\"\r\n\t\t\t}\r\n\t\t\tchoco search $PackageName --exact -r -s \"https://www.myget.org/F/vm-packages/api/v2\" | ForEach-Object {\r\n\t\t\t\t$Name, $Version = $_ -split '\\|'\r\n\t\t\t\tNew-Object -TypeName psobject -Property @{\r\n\t\t\t\t\t'Name' = $Name\r\n\t\t\t\t\t'Version' = $Version\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfunction Set-AdditionalPackages {\r\n\t\t\t$additionalPackagesBox.Items.Clear()\r\n\t\t\tforeach($package in $additionalPackages)\r\n\t\t\t{\r\n\t\t\t\t$additionalPackagesBox.Items.Add($package) | Out-Null\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfunction Remove-SelectedPackages {\r\n\t\t\t$additionalPackagesBox.BeginUpdate()\r\n\t\t\twhile ($additionalPackagesBox.SelectedItems.count -gt 0) {\r\n\t\t\t\t$additionalPackagesBox.Items.RemoveAt($additionalPackagesBox.SelectedIndex)\r\n\t\t\t}\r\n\t\t\t$additionalPackagesBox.EndUpdate()\r\n\t\t}\r\n\r\n\t\tAdd-Type -AssemblyName System.Windows.Forms\r\n\t\t[System.Windows.Forms.Application]::EnableVisualStyles()\r\n\r\n\t\t$formCategories                            = New-Object system.Windows.Forms.Form\r\n\t\t$formCategories.ClientSize                 = New-Object System.Drawing.Point(1015,850)\r\n\t\t$formCategories.text                       = \"FLARE-VM Package selection\"\r\n\t\t$formCategories.StartPosition              = 'CenterScreen'\r\n\t\t$formCategories.TopMost                    = $true\r\n\r\n\t\tif ([string]::IsNullOrEmpty($customConfig)) {\r\n\t\t\t$textLabel = \"The default configuration (recommended) is pre-selected. Click on the reset button to restore the default configuration.\"\r\n\t\t} else {\r\n\t\t\t$textLabel = \"The provided custom configuration is pre-selected. Click on the reset button to restore the custom configuration.\"\r\n\t\t}\r\n\r\n\t\t$labelCategories                = New-Object system.Windows.Forms.Label\r\n\t\t$labelCategories.text           = \"Select packages to install\"\r\n\t\t$labelCategories.AutoSize       = $true\r\n\t\t$labelCategories.width          = 25\r\n\t\t$labelCategories.height         = 10\r\n\t\t$labelCategories.location       = New-Object System.Drawing.Point(30,20)\r\n\t\t$labelCategories.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',10,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\r\n\t\t$labelCategories2                = New-Object system.Windows.Forms.Label\r\n\t\t$labelCategories2.text           = $textLabel\r\n\t\t$labelCategories2.AutoSize       = $true\r\n\t\t$labelCategories2.location       = New-Object System.Drawing.Point(30,40)\r\n\t\t$labelCategories2.Font           = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n\t\t$panelCategories                = New-Object system.Windows.Forms.Panel\r\n\t\t$panelCategories.height         = 530\r\n\t\t$panelCategories.width          = 970\r\n\t\t$panelCategories.location       = New-Object System.Drawing.Point(30,60)\r\n\t\t$panelCategories.AutoScroll     = $true\r\n\r\n\t\t$resetButton                 = New-Object system.Windows.Forms.Button\r\n\t\t$resetButton.text            = \"Reset\"\r\n\t\t$resetButton.AutoSize        = $true\r\n\t\t$resetButton.location        = New-Object System.Drawing.Point(50,800)\r\n\t\t$resetButton.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$resetButton.Add_Click({\r\n\t\t\t\t\t\tSet-InitialPackages\r\n\t\t\t\t\t\tSet-AdditionalPackages\r\n\t\t\t\t\t})\r\n\r\n\t\t$allPackagesButton                 = New-Object system.Windows.Forms.Button\r\n\t\t$allPackagesButton.text            = \"Select All\"\r\n\t\t$allPackagesButton.AutoSize        = $true\r\n\t\t$allPackagesButton.location        = New-Object System.Drawing.Point(130,800)\r\n\t\t$allPackagesButton.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$allPackagesButton.Add_Click({\r\n\t\t   [System.Windows.Forms.MessageBox]::Show('Selecting all packages considerable increases installation time and it is not desirable for most use cases','Warning')\r\n\t\t   Select-AllPackages\r\n\t\t})\r\n\r\n\t\t$clearPackagesButton\t         = New-Object system.Windows.Forms.Button\r\n\t\t$clearPackagesButton.text            = \"Clear\"\r\n\t\t$clearPackagesButton.AutoSize        = $true\r\n\t\t$clearPackagesButton.location        = New-Object System.Drawing.Point(210,800)\r\n\t\t$clearPackagesButton.Font            = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$clearPackagesButton.Add_Click({Clear-AllPackages})\r\n\r\n\t\t$installButton            = New-Object system.Windows.Forms.Button\r\n\t\t$installButton.text       = \"Install\"\r\n\t\t$installButton.width      = 97\r\n\t\t$installButton.height     = 37\r\n\t\t$installButton.DialogResult   = [System.Windows.Forms.DialogResult]::OK\r\n\t\t$installButton.location   = New-Object System.Drawing.Point(750,800)\r\n\t\t$installButton.Font       = New-Object System.Drawing.Font('Microsoft Sans Serif',12)\r\n\r\n\t\t$cancelButton            = New-Object system.Windows.Forms.Button\r\n\t\t$cancelButton.text       = \"Cancel\"\r\n\t\t$cancelButton.width      = 97\r\n\t\t$cancelButton.height     = 37\r\n\t\t$cancelButton.location   = New-Object System.Drawing.Point(850,800)\r\n\t\t$cancelButton.Font       = New-Object System.Drawing.Font('Microsoft Sans Serif',12)\r\n\t\t$cancelButton.DialogResult = [System.Windows.Forms.DialogResult]::Cancel\r\n\r\n\t\t$formCategories.AcceptButton = $installButton\r\n\t\t$formCategories.CancelButton = $cancelButton\r\n\r\n\t\t# Create checkboxes for each package\r\n\t\t$checkboxesPackages = New-Object System.Collections.Generic.List[System.Object]\r\n\t\t# Initial vertical position for checkboxes\r\n\t\t$verticalPosition = 25\r\n\t\t$numCheckBoxPackages = 1\r\n\t\t$packages = @()\r\n\t\tforeach ($category in $packagesByCategory.Keys |Sort-Object) {\r\n\t\t\t# Create Labels for categories\r\n\t\t\t$labelCategory = New-Object System.Windows.Forms.Label\r\n\t\t\t$labelCategory.Text = $category\r\n\t\t\t$labelCategory.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',11,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\t\t\t$labelCategory.AutoSize = $true\r\n\t\t\t$labelCategory.Location = New-Object System.Drawing.Point(10, $verticalPosition)\r\n\t\t\t$panelCategories.Controls.Add($labelCategory)\r\n\r\n\t\t\t$NumPackages = 0\r\n\t\t\t$verticalPosition2 = $verticalPosition + 20\r\n\t\t\t$packages= Get-PackagesByCategory -category $category\r\n\t\t\tforeach ($package in $packages)\r\n\t\t\t{\r\n\t\t\t\t$NumPackages++\r\n\t\t\t\t$checkBox = New-Object System.Windows.Forms.CheckBox\r\n\t\t\t\t$checkBox.Text = $package.PackageName + \": \" + $package.PackageDescription\r\n\t\t\t\t$checkBox.Font = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t\t\t$checkBox.AutoSize = $true\r\n\t\t\t\t$checkBox.Location = New-Object System.Drawing.Point(10, $verticalPosition2)\r\n\t\t\t\t$checkBox.Name = \"checkBox$numCheckBoxPackages\"\r\n\t\t\t\t$checkboxesPackages.Add($checkBox)\r\n\t\t\t\t$panelCategories.Controls.Add($checkBox)\r\n\t\t\t    $url = $package.PackageUrl\r\n\t\t\t\tif ($url){\r\n\t\t\t\t\t$linkProjectUrl = New-Object System.Windows.Forms.linkLabel\r\n\t\t\t\t\t$linkProjectUrl.Top = $checkbox.Top + 2\r\n\t\t\t\t\t$linkProjectUrl.Left = $checkbox.Right - 3\r\n\t\t\t\t\t$linkProjectUrl.AutoSize                    = $true\r\n\t\t\t\t\t$linkProjectUrl.Font                        = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t\t\t\t$linkProjectUrl.LinkColor                   = \"BLUE\";\r\n\t\t\t\t\t$linkProjectUrl.ActiveLinkColor             = \"RED\"\r\n\t\t\t\t\t$linkProjectUrl.Text                        = \"Link\"\r\n\t\t\t\t\t$linkProjectUrl.Links.Add(0, 4, $url)| Out-Null\r\n\t\t\t\t\t$linkProjectUrl.add_Click({ Start-Process $this.Links.LinkData })\r\n\t\t\t\t\t$panelCategories.Controls.Add($linkProjectUrl)\r\n\t\t\t\t}\r\n\t\t\t\t$verticalPosition2 += 20\r\n\t\t\t\t$numCheckBoxPackages ++\r\n\t\t\t}\r\n\t\t\t\t# Increment to space checkboxes vertically\r\n\t\t\t$verticalPosition += 20 * ($NumPackages ) + 30\r\n\t\t\t$numCategories ++\r\n\t\t}\r\n\r\n\t\t# Create empty label and add it to the form categories to add some space\r\n\t\t$posEnd = $verticalPosition2 +10\r\n\t\t$emptyLabel                = New-Object system.Windows.Forms.Label\r\n\t\t$emptyLabel.Width = 20\r\n\t\t$emptyLabel.Height = 10\r\n\t\t$emptyLabel.location       = New-Object System.Drawing.Point(10,$posEnd)\r\n\t\t$panelCategories.Controls.Add($emptyLabel)\r\n\r\n\t\t# Select packages that are in the config.xml\r\n\t\tSet-InitialPackages\r\n\r\n\t\t$additionalPackagesLabel                          = New-Object system.Windows.Forms.Label\r\n\t\t$additionalPackagesLabel.text                     = \"Additional packages to install\"\r\n\t\t$additionalPackagesLabel.AutoSize                 = $true\r\n\t\t$additionalPackagesLabel.width                    = 25\r\n\t\t$additionalPackagesLabel.height                   = 10\r\n\t\t$additionalPackagesLabel.location                 = New-Object System.Drawing.Point(30,615)\r\n\t\t$additionalPackagesLabel.Font                     = New-Object System.Drawing.Font('Microsoft Sans Serif',10,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$additionalPackagesBox                 = New-Object system.Windows.Forms.ListBox\r\n\t\t$additionalPackagesBox.text            = \"listBox\"\r\n\t\t$additionalPackagesBox.SelectionMode   = 'MultiSimple'\r\n\t\t$additionalPackagesBox.Sorted          = $true\r\n\t\t$additionalPackagesBox.width           = 130\r\n\t\t$additionalPackagesBox.height          = 140\r\n\t\t$additionalPackagesBox.location        = New-Object System.Drawing.Point(50,640)\r\n\r\n\t\t$deletePackageButton          = New-Object system.Windows.Forms.Button\r\n\t\t$deletePackageButton.text     = \"-\"\r\n\t\t$deletePackageButton.width    = 24\r\n\t\t$deletePackageButton.height   = 22\r\n\t\t$deletePackageButton.enabled   = $true\r\n\t\t$deletePackageButton.location  = New-Object System.Drawing.Point(190,670)\r\n\t\t$deletePackageButton.Font      = New-Object System.Drawing.Font('Microsoft Sans Serif',12,[System.Drawing.FontStyle]::Bold)\r\n\t\t$deletePackageButton.Add_Click({Remove-SelectedPackages})\r\n\r\n\t\t$packageLabel                          = New-Object system.Windows.Forms.Label\r\n\t\t$packageLabel.text                     = \"FLARE-VM uses Chocolatey packages. You can add additional packages from:\"\r\n\t\t$packageLabel.width                    = 260\r\n\t\t$packageLabel.height                   = 35\r\n\t\t$packageLabel.AutoSize                 = $true\r\n\t\t$packageLabel.location                 = New-Object System.Drawing.Point(300,640)\r\n\t\t$packageLabel.Font                     = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n\t\t$labelChoco                             = New-Object System.Windows.Forms.Label\r\n\t\t$labelChoco.Location                    = New-Object System.Drawing.Point(300,660)\r\n\t\t$labelChoco.Size                        = New-Object System.Drawing.Size(280,20)\r\n\t\t$labelChoco.AutoSize                    = $true\r\n\t\t$labelChoco.Font                        = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$labelChoco.Text                        = \"Community Packages\"\r\n\r\n\t\t$linkLabelChoco                             = New-Object System.Windows.Forms.linkLabel\r\n\t\t$linkLabelChoco.Location                    = New-Object System.Drawing.Point(440,660)\r\n\t\t$linkLabelChoco.AutoSize                    = $true\r\n\t\t$linkLabelChoco.Font                        = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$linkLabelChoco.LinkColor                   = \"BLUE\"\r\n\t\t$linkLabelChoco.ActiveLinkColor             = \"RED\"\r\n\t\t$linkLabelChoco.Text                        = \"https://community.chocolatey.org/packages\"\r\n\t\t$linkLabelChoco.add_Click({Start-Process \"https://community.chocolatey.org/packages\"})\r\n\r\n\t\t$labelFlarevm                             = New-Object System.Windows.Forms.Label\r\n\t\t$labelFlarevm.Location                    = New-Object System.Drawing.Point(300,680)\r\n\t\t$labelFlarevm.Size                        = New-Object System.Drawing.Size(280,20)\r\n\t\t$labelFlarevm.AutoSize                     = $true\r\n\t\t$labelFlarevm.Font                        = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$labelFlarevm.Text                        = \"FLARE-VM Packages\"\r\n\r\n\t\t$linkLabelFlarevm                             = New-Object System.Windows.Forms.linkLabel\r\n\t\t$linkLabelFlarevm.Location                    = New-Object System.Drawing.Point(440,680)\r\n\t\t$linkLabelFlarevm.AutoSize                    = $true\r\n\t\t$linkLabelFlarevm.Font                        = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$linkLabelFlarevm.LinkColor                   = \"BLUE\"\r\n\t\t$linkLabelFlarevm.ActiveLinkColor             = \"RED\"\r\n\t\t$linkLabelFlarevm.Text                        = \"https://github.com/mandiant/VM-Packages/wiki/Packages\"\r\n\t\t$linkLabelFlarevm.add_Click({Start-Process \"https://github.com/mandiant/VM-Packages/wiki/Packages\"})\r\n\r\n\t\tSet-AdditionalPackages\r\n\r\n\t\t$chocoPackageLabel                          = New-Object system.Windows.Forms.Label\r\n\t\t$chocoPackageLabel.text                     = \"Enter package name:\"\r\n\t\t$chocoPackageLabel.AutoSize                 = $true\r\n\t\t$chocoPackageLabel.width                    = 25\r\n\t\t$chocoPackageLabel.height                   = 10\r\n\t\t$chocoPackageLabel.location                 = New-Object System.Drawing.Point(300,715)\r\n\t\t$chocoPackageLabel.Font                     = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\r\n\t\t$packageTextBox                        = New-Object system.Windows.Forms.TextBox\r\n\t\t$packageTextBox.multiline              = $false\r\n\t\t$packageTextBox.width                  = 210\r\n\t\t$packageTextBox.height                 = 20\r\n\t\t$packageTextBox.location               = New-Object System.Drawing.Point(300,735)\r\n\t\t$packageTextBox.Font                   = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$packageTextBox.Add_TextChanged({\r\n\t\t\t\t  if ($addPackageButton.Enabled -eq $true){\r\n\t\t\t\t\t  $addPackageButton.Enabled = $false\r\n\t\t\t\t  }\r\n\t\t})\r\n\r\n\t\t$chocoPackageErrorLabel                          = New-Object system.Windows.Forms.Label\r\n\t\t$chocoPackageErrorLabel.text                     = \"\"\r\n\t\t$chocoPackageErrorLabel.AutoSize                 = $true\r\n\t\t$chocoPackageErrorLabel.visible                  = $false\r\n\t\t$chocoPackageErrorLabel.width                    = 25\r\n\t\t$chocoPackageErrorLabel.height                   = 10\r\n\t\t$chocoPackageErrorLabel.location                 = New-Object System.Drawing.Point(300,765)\r\n\t\t$chocoPackageErrorLabel.Font                     = New-Object System.Drawing.Font('Microsoft Sans Serif',10,[System.Drawing.FontStyle]([System.Drawing.FontStyle]::Bold))\r\n\r\n\t\t$findPackageButton          = New-Object system.Windows.Forms.Button\r\n\t\t$findPackageButton.text     = \"Find Package\"\r\n\t\t$findPackageButton.width    = 118\r\n\t\t$findPackageButton.height   = 30\r\n\t\t$findPackageButton.enabled   = $true\r\n\t\t$findPackageButton.location  = New-Object System.Drawing.Point(520,730)\r\n\t\t$findPackageButton.Font      = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$findPackageButton.Add_Click({\r\n\t\t\t$chocoPackageErrorLabel.Visible = $true\r\n\t\t\t$chocoPackageErrorLabel.text = \"Finding package ...\"\r\n\t\t\t$vmPackage = Get-VMPackage -PackageName $packageTextBox.Text.Trim()\r\n\t\t\tif ($vmPackage){\r\n\t\t\t\t$packageName = $vmPackage | Select-Object -ExpandProperty Name\r\n\t\t\t\t$chocoPackageErrorLabel.text = \"Found VM package\"\r\n\t\t\t\t$chocoPackageErrorLabel.ForeColor = $successColor\r\n\t\t\t\t$packageTextBox.Text = $packageName\r\n\t\t\t\t$addPackageButton.enabled = $true\r\n\t\t\t} else {\r\n\t\t\t\t$chocoPackage = Get-ChocoPackage -PackageName $packageTextBox.Text\r\n\t\t\t\tif ($chocoPackage) {\r\n\t\t\t\t   $chocoPackageErrorLabel.text = \"Found Choco package\"\r\n\t\t\t\t   $chocoPackageErrorLabel.ForeColor = $successColor\r\n\t\t\t\t   $addPackageButton.enabled = $true\r\n\t\t\t\t} else {\r\n\t\t\t\t   $chocoPackageErrorLabel.text = \"Package not found\"\r\n\t\t\t\t   $chocoPackageErrorLabel.ForeColor = $errorColor\r\n\t\t\t\t   $addPackageButton.enabled = $false\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t})\r\n\r\n\t\t$addPackageButton          = New-Object system.Windows.Forms.Button\r\n\t\t$addPackageButton.text     = \"Add Package\"\r\n\t\t$addPackageButton.width    = 118\r\n\t\t$addPackageButton.height   = 30\r\n\t\t$addPackageButton.enabled   = $false\r\n\t\t$addPackageButton.location  = New-Object System.Drawing.Point(650,730)\r\n\t\t$addPackageButton.Font      = New-Object System.Drawing.Font('Microsoft Sans Serif',10)\r\n\t\t$addPackageButton.Add_Click({\r\n\t\t\t\t\t  if (Add-NewPackage -PackageName $packageTextBox.Text){\r\n\t\t\t\t\t\t  $chocoPackageErrorLabel.ForeColor = $successColor\r\n\t\t\t\t\t\t  $chocoPackageErrorLabel.text = \"Package added\"\r\n\t\t\t\t\t  }else {\r\n\t\t\t\t\t\t  $chocoPackageErrorLabel.ForeColor = $errorColor\r\n\t\t\t\t\t\t  $chocoPackageErrorLabel.text = \"Error to add the package: duplicated\"\r\n\t\t\t\t\t  }\r\n\t\t\t\t\t  $addPackageButton.enabled = $false\r\n\t\t\t  })\r\n\r\n\t\t$formCategories.controls.AddRange(@($additionalPackagesLabel,$packageLabel,$labelChoco,$labelFlarevm,$linkLabelChoco,$linkLabelFlarevm,$linkLabelFlarevm,$additionalPackagesBox,$deletePackageButton,$chocoPackageButton,$chocoPackageLabel,$packageTextBox,$chocoPackageErrorLabel,$findPackageButton,$addPackageButton))\r\n\t\t$formCategories.controls.AddRange(@($labelCategories,$labelCategories2,$panelCategories,$installButton,$resetButton,$allPackagesButton,$cancelButton,$clearPackagesButton))\r\n\t\t$formCategories.Add_Shown({$formCategories.Activate()})\r\n\t\t$resultCategories = $formCategories.ShowDialog()\r\n\t\tif ($resultCategories -eq [System.Windows.Forms.DialogResult]::OK){\r\n\t\t\tInstall-Selected-Packages\r\n\t\t} else {\r\n\t\t\tWrite-Host \"[+] Cancel pressed, stopping installation...\"\r\n\t\t\tStart-Sleep 3\r\n\t\t\texit 1\r\n\t\t}\r\n\t}\r\n\t\t################################################################################\r\n\t\t## END GUI\r\n\t\t################################################################################\r\n}\r\n\r\n# Save the config file\r\nWrite-Host \"[+] Saving configuration file...\"\r\n$configXml.save($configPath)\r\n\r\n# Parse config and set initial environment variables\r\nWrite-Host \"[+] Parsing configuration file...\"\r\nforeach ($env in $configXml.config.envs.env) {\r\n    $path = [Environment]::ExpandEnvironmentVariables($($env.value))\r\n    Write-Host \"`t[+] Setting %$($env.name)% to: $path\" -ForegroundColor Green\r\n    [Environment]::SetEnvironmentVariable(\"$($env.name)\", $path, \"Machine\")\r\n    [Environment]::SetEnvironmentVariable('VMname', 'FLARE-VM', [EnvironmentVariableTarget]::Machine)\r\n}\r\nrefreshenv\r\n\r\n# Install the common module\r\n# This creates all necessary folders based on custom environment variables\r\nWrite-Host \"[+] Installing shared module...\"\r\nchoco install common.vm -y --force\r\nrefreshenv\r\n\r\n# Use single config\r\n$configXml.save((Join-Path ${Env:VM_COMMON_DIR} \"config.xml\"))\r\n$configXml.save((Join-Path ${Env:VM_COMMON_DIR} \"packages.xml\"))\r\n\r\n# Custom Start Layout setup\r\nWrite-Host \"[+] Checking for custom Start Layout file...\"\r\n$layoutPath = Join-Path \"C:\\Users\\Default\\AppData\\Local\\Microsoft\\Windows\\Shell\" \"LayoutModification.xml\"\r\nif ([string]::IsNullOrEmpty($customLayout)) {\r\n    $layoutSource = 'https://raw.githubusercontent.com/mandiant/flare-vm/main/LayoutModification.xml'\r\n} else {\r\n    $layoutSource = $customLayout\r\n}\r\n\r\nGet-ConfigFile $layoutPath $layoutSource\r\n\r\n# Log basic system information to assist with troubleshooting\r\nWrite-Host \"[+] Logging basic system information to assist with any future troubleshooting...\"\r\nImport-Module \"${Env:VM_COMMON_DIR}\\vm.common\\vm.common.psm1\" -Force -DisableNameChecking\r\nVM-Get-Host-Info\r\n\r\nWrite-Host \"[+] Installing the debloat.vm debloater and performance package\"\r\nchoco install debloat.vm -y --force\r\n\r\n# Download FLARE VM background image\r\n$backgroundImage = \"${Env:VM_COMMON_DIR}\\background.png\"\r\nSave-FileFromUrl -fileSource 'https://raw.githubusercontent.com/mandiant/flare-vm/main/Images/flarevm-background.png' -fileDestination $backgroundImage\r\n# Use background image for lock screen as well\r\n$lockScreenImage = \"${Env:VM_COMMON_DIR}\\lockscreen.png\"\r\nCopy-Item $backgroundImage $lockScreenImage\r\n\r\nif (-not $noWait.IsPresent) {\r\n    # Show install notes and wait for timeout\r\n    function Wait-ForInstall ($seconds) {\r\n        $doneDT = (Get-Date).AddSeconds($seconds)\r\n        while($doneDT -gt (Get-Date)) {\r\n            $secondsLeft = $doneDT.Subtract((Get-Date)).TotalSeconds\r\n            $percent = ($seconds - $secondsLeft) / $seconds * 100\r\n            Write-Progress -Activity \"Please read install notes on console below\" -Status \"Beginning install in...\" -SecondsRemaining $secondsLeft -PercentComplete $percent\r\n            [System.Threading.Thread]::Sleep(500)\r\n        }\r\n        Write-Progress -Activity \"Waiting\" -Status \"Beginning install...\" -SecondsRemaining 0 -Completed\r\n    }\r\n\r\n    Write-Host @\"\r\n[!] INSTALL NOTES - PLEASE READ CAREFULLY [!]\r\n\r\n- This install is not 100% unattended. Please monitor the install for possible failures. If install\r\nfails, you may restart the install by re-running the install script with the following command:\r\n\r\n    .\\install.ps1 -password <password> -noWait -noGui -noChecks\r\n\r\n- You can check which packages failed to install by listing the C:\\ProgramData\\chocolatey\\lib-bad\r\ndirectory. Failed packages are stored by folder name. You may attempt manual installation with the\r\nfollowing command:\r\n\r\n    choco install -y <package_name>\r\n\r\n- For any issues, please submit to GitHub:\r\n\r\n    Installer related: https://github.com/mandiant/flare-vm\r\n    Package related:   https://github.com/mandiant/VM-Packages\r\n\r\n[!] Please copy this note for reference [!]\r\n\"@ -ForegroundColor Red -BackgroundColor White\r\n    Wait-ForInstall -seconds 30\r\n}\r\n\r\n# Begin the package install\r\nWrite-Host \"[+] Beginning install of configured packages...\" -ForegroundColor Green\r\n$PackageName = \"installer.vm\"\r\nif ($noPassword.IsPresent) {\r\n    Install-BoxstarterPackage -packageName $PackageName\r\n} else {\r\n    Install-BoxstarterPackage -packageName $PackageName -credential $credentials\r\n}\r\n\r\n"
  },
  {
    "path": "scripts/lint.ps1",
    "content": "# Exclude rules that make the code less readable or involve changing the functionality\r\n$excludedRules = \"PSAvoidUsingPlainTextForPassword\", \"PSAvoidUsingConvertToSecureStringWithPlainText\", \"PSAvoidUsingWriteHost\", \"PSUseShouldProcessForStateChangingFunctions\", \"PSUseSingularNouns\", \"PSAvoidUsingInvokeExpression\"\r\n\r\nchoco install psscriptanalyzer --version 1.23.0 --no-progress\r\n\r\n# Manually iterate over all files instead of using -Recurse because\r\n# PSScriptAnalyzer only outputs the script name (and most have the name\r\n# chocolateyinstall.ps1)\r\n$scripts = Get-ChildItem . -Filter *.ps*1 -Recurse -File -Name\r\n$errorsCount = 0\r\nforeach ($script in $scripts) {\r\n  Write-Host -ForegroundColor Yellow $script\r\n  ($errors = Invoke-ScriptAnalyzer $script -Recurse -ReportSummary -ExcludeRule $excludedRules)\r\n  $errorsCount += $errors.Count\r\n}\r\n\r\nExit($errorsCount)\r\n"
  },
  {
    "path": "virtualbox/README.md",
    "content": "# VirtualBox scripts\n\n**This folder contains several scripts related to enhance building, exporting, and using FLARE-VM in VirtualBox.**\nThe scripts have been tested in Debian 12 with GNOME 44.9.\n\n\n## Clean up snapshots\n\nIt is not possible to select and delete several snapshots in VirtualBox, making cleaning up your virtual machine (VM) manually after having creating a lot snapshots time consuming and tedious (possible errors when deleting several snapshots simultaneously).\n\n[`vbox-clean-snapshots.py`](vbox-clean-snapshots.py) cleans a VirtualBox VM up by deleting a snapshot and its children recursively skipping snapshots with a substring in the name.\n\n### Example\n\n```\n$ ./vbox-remove-snapshots.py FLARE-VM.20240604 --protected empty,clean,done,important\n\nSnapshots with the following strings in the name (case insensitive) won't be deleted:\n  clean\n  done\n\nCleaning FLARE-VM.20240604 🫧 Snapshots to delete:\n  Snapshot 1\n  wip unpacked\n  JS downloader deobfuscated \n  Snapshot 6\n  C2 decoded\n  Snapshot 5\n  wip\n  Snapshot 4\n  Snapshot 3\n  Snapshot 2\n  complicated chain - all samples ready\n\nVM state: Paused\n⚠️  Snapshot deleting is slower in a running VM and may fail in a changing state\n\nConfirm deletion (press 'y'):y\n\nDeleting... (this may take some time, go for an 🍦!)\n  🫧 DELETED 'Snapshot 1'\n  🫧 DELETED 'wip unpacked'\n  🫧 DELETED 'JS downloader deobfuscated '\n  🫧 DELETED 'Snapshot 6'\n  🫧 DELETED 'C2 decoded'\n  🫧 DELETED 'Snapshot 5'\n  🫧 DELETED 'wip'\n  🫧 DELETED 'Snapshot 4'\n  🫧 DELETED 'Snapshot 3'\n  🫧 DELETED 'Snapshot 2'\n  🫧 DELETED 'complicated chain - all samples ready'\n\nSee you next time you need to clean up your VMs! ✨\n\n```\n\n##### Before\n\n![Before](../Images/vbox-clean-snapshots_before.png)\n\n##### After\n\n![After](../Images/vbox-clean-snapshots_after.png)\n\n\n## Check internet adapter status\n\n[`vbox-adapter-check.py`](vbox-adapter-check.py) prints the status of all internet adapters of all VMs in VirtualBox.\nIf the argument `--dynamic_only` is provided, the script only print the status of the dynamic analysis VM (with `.dynamic` in the name).\nUnless the argument `--do_not_modify` is provided, if internet is detected in any dynamic analysis VM, the script sends a notification and changes the adapters type to Host-Only.\nThe script is useful to detect internet access, which is undesirable for dynamic malware analysis.\n\n### Example\n\n```\n$ ~/github/flare-vm/virtualbox/vbox-adapter-check.py\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} ⚠️  FLARE-VM.testing is connected to the internet on adapter(s): 1\nVM {a23c0c37-2062-4cf0-882b-9e9747dd33b6} ✅ REMnux.20241217.dynamic network configuration is ok\nVM {fa0b3733-50cb-43fd-8428-745d0e9159cb} ✅ FLARE-VM.Win10.20250211.dynamic network configuration is ok\nVM {e5f509ed-cbc8-4abc-b052-664246207e89} ⚠️  FLARE-VM.Win10.20250211.full.dynamic is connected to the internet on adapter(s): 1, 2\nVM {e5f509ed-cbc8-4abc-b052-664246207e89} ⚙️  FLARE-VM.Win10.20250211.full.dynamic set adapter 1 to hostonly\nVM {e5f509ed-cbc8-4abc-b052-664246207e89} ⚙️  FLARE-VM.Win10.20250211.full.dynamic set adapter 2 to hostonly\n```\n\n#### Notification\n\n![Notification](../Images/vbox-adapter-check_notification.png)\n\n\n## Export snapshot\n\n[`vbox-export-snapshot.py`](vbox-export-snapshot.py) exports a VirtualBox snapshot as an Open Virtual Appliance (OVA) file.\nThe script configures the exported VM with a single Host-Only network interface, and the resulting OVA file is named after the snapshot.\nA separate file containing the SHA256 hash of the OVA is also generated for verification.\nThe script accepts an optional description for the OVA and the name of the export directory within the user's home directory (`$HOME`) where the OVA and SHA256 hash file will be saved.\nIf no export directory is provided, the default directory name is `EXPORTED VMS`.\n\n### Example\n\n```\n$ ./vbox-export-snapshots.py \"FLARE-VM.testing\" \"FLARE-VM\" --description \"Windows 10 VM with FLARE-VM default configuration\"\n\nExporting snapshot \"FLARE-VM\" from \"FLARE-VM.testing\" {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d}...\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} ✨ restored snapshot \"FLARE-VM\"\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} state: saved. Starting VM...\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} state: running. Shutting down VM...\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} ⚙️  network set to single hostonly adapter\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} 🔄 power cycling before export... (it will take some time, go for an 🍦!)\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} state: poweroff. Starting VM...\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} state: running. Shutting down VM...\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} 🚧 exporting ... (it will take some time, go for an 🍦!)\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} ✅ EXPORTED \"/home/anamg/None/FLARE-VM.ova\"\nVM {2bc66f50-9ecb-4b10-a4dd-0cc329bc383d} ✅ GENERATED \"/home/anamg/None/FLARE-VM.ova.sha256\": 987eed68038ce7c5072e7dc219ba82d11745267d8ab2ea7f76158877c13e3aa9\n```\n\n## Build FLARE-VM VM(s)\n\n[`vbox-build-flare-vm.py`](vbox-build-flare-vm.py) automates the creation and export of customized FLARE-VM VMs.\nThe script begins by restoring a pre-existing `BUILD-READY` snapshot of a clean Windows installation.\nThe script then copies the required installation files (such as the IDA Pro installer, FLARE-VM configuration, and legal notices) into the guest VM.\nAfter installing FLARE-VM, a `base` snapshot is taken.\nThis snapshot serves as the foundation for generating subsequent snapshots and exporting OVA images, all based on the configuration provided in a YAML file.\nThis configuration file specifies the VM name, the exported VM name, and details for each snapshot.\nIndividual snapshot configurations can include custom commands to be executed within the guest, legal notices to be applied, and file/folder exclusions for the automated cleanup process.\nSee the configuration example file [`configs/win10_flare-vm.yaml`](configs/win10_flare-vm.yaml).\n\nThe `BUILD-READY` snapshot is expected to be an empty Windows installation that satisfies the FLARE-VM installation requirements and has UAC disabled.\nTo disable UAC execute in a cmd console with admin rights and restart the VM for the change to take effect:\n```\n%windir%\\System32\\reg.exe ADD HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /v EnableLUA /t REG_DWORD /d 0 /f\n```\n\n## Build REMnux VM\n\nSimilarly to [`vbox-build-flare-vm.py`](vbox-build-flare-vm.py), [`vbox-build-remnux.py`](vbox-build-remnux.py) automates the creation and export of customized REMnux virtual machines (VMs).\nThe script begins by restoring a pre-existing \"BUILD-READY\" snapshot of a clean REMnux OVA.\nRequired installation files (such as the IDA Pro installer and ZIPs with GNOME extensions) are then copied into the guest VM.\nThe configuration file specifies the VM name, the exported VM name, and details for each snapshot.\nIndividual snapshot configurations include the extension, description, and custom commands to be executed within the guest.\nSee the configuration example file [`configs/remnux.yaml`](configs/remnux.yaml).\n"
  },
  {
    "path": "virtualbox/configs/remnux.yaml",
    "content": "VM_NAME: REMnux.testing\nEXPORTED_VM_NAME: REMnux\nSNAPSHOT:\n  extension: \".dynamic\"\n  description: \"REMnux (based on Ubuntu) with improved configuration\"\nCMDS:\n  - |\n    # Install additional useful packages\n    sudo apt-get --assume-yes install libwrap0-dev gdb-multiarch qemu gcc-multilib libcurl4:i386 qemu-user libc6-mips64-mips-cross libc6-mipsel-cross libc6-arm64-cross libc6-armel-cross libc6-armhf-cross libc6-ppc64-cross libc6-powerpc-cross\n\n\n  - |\n    # Uninstall distro-info to fix \"Invalid version: '0.23ubuntu1'\" Python install warning\n    # Uninstall it in both the default and Python 3.9\n    sudo pip uninstall -y distro-info\n    sudo /usr/bin/python3.9 -m pip uninstall -y distro-info\n\n  - |\n    # Install additional Python libraries\n    pip install -U pip\n    pip install rpyc flare-capa lznt1\n\n  - |\n    # Install additional Python libraries using Python 3.9\n    /usr/bin/python3.9 -m pip install -U pip\n    /usr/bin/python3.9 -m pip install rpyc flare-capa lznt1\n\n  - |\n    # Fix fakenet issue: https://github.com/mandiant/flare-fakenet-ng/tree/master?tab=readme-ov-file#dns-not-resolving-names\n    sudo systemctl stop systemd-resolved\n    sudo systemctl disable systemd-resolved\n\n  - |\n    # Install IDA\n    # Expected IDA 9 installer in the Desktop\n    cd /home/remnux/Desktop\n    sudo chmod +x ida-pro_*.run\n    ./ida-pro_*.run --mode unattended\n\n    # Add IDA to favourite apps on startup (/usr/local/share/remnux/gnome-config.sh replaces it on startup)\n    ida_app=$(basename /home/remnux/.local/share/applications/com.hex_rays.IDA.pro*.desktop)\n    favourite_apps=$(gsettings get org.gnome.shell favorite-apps | sed 's/.$//')\n    echo '' | sudo tee -a /usr/local/share/remnux/gnome-config.sh\n    echo \"gsettings set org.gnome.shell favorite-apps \\\"$favourite_apps, '$ida_app']\\\"\" | sudo tee -a /usr/local/share/remnux/gnome-config.sh\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    sync\n\n  - |\n    # Install Dash to Panel extension\n    # Expected a ZIP with a version for the GNOME shell 3.36 in the Desktop\n    cd /home/remnux/Desktop\n    gnome-extensions install dash-to-panel*.shell-extension.zip --force\n\n    # Enable Dash to Panel extension on startup as logout is needed after install\n    echo gnome-extensions enable dash-to-panel@jderose9.github.com | sudo tee -a /usr/local/share/remnux/gnome-config.sh\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    sync\n\n"
  },
  {
    "path": "virtualbox/configs/win10_flare-vm-edu.yaml",
    "content": "VM_NAME: FLARE-VM.testing\nEXPORTED_VM_NAME: FLARE-VM.Win10\nSNAPSHOTS:\n- extension: \".EDU\"\n  description: \"Windows 10 VM with FLARE-VM default configuration + FLARE-EDU materials\"\n  cmd: |\n    $desktop = \"C:\\Users\\flare\\Desktop\";\n    Set-Location $desktop;\n\n    # Unzip EDU labs\n    VM-Unzip-Recursively;\n\n    # Install Office 2016. the installation takes 30 minutes\n    $path = \"$desktop\\en_office_professional_plus_2016_x86_x64_dvd_6962141.iso\";\n    $drive = (Mount-DiskImage -ImagePath $path | Get-Volume).DriveLetter;\n    Set-Location \"$drive`:\\\";\n    .\\setup.exe;\n    Start-Sleep 1800;\n    Dismount-DiskImage -ImagePath $path;\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    # sync is installed by sysinternals\n    sync;\n  legal_notice: \"legal_notice_edu.txt\"\n  protected_folders: \"'ATMA', 'MACC', 'MAF', 'MDA'\"\n  protected_files: \"'Labs.zip', 'MICROSOFT Windows 10 License Terms.txt', 'MICROSOFT Office 2016 License Terms.txt'\"\n"
  },
  {
    "path": "virtualbox/configs/win10_flare-vm.yaml",
    "content": "VM_NAME: FLARE-VM.testing\nEXPORTED_VM_NAME: FLARE-VM.Win10\nSNAPSHOTS:\n- extension: \".dynamic\"\n  description: \"Windows 10 VM with FLARE-VM default configuration + idapro.vm\"\n  cmd: |\n    choco install idapro.vm;\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    # sync is installed by sysinternals\n    sync;\n  legal_notice: \"legal_notice.txt\"\n- extension: \".full.dynamic\"\n  description: \"Windows 10 VM with FLARE-VM default configuration + idapro.vm + pdbs.pdbresym.vm + visualstudio.vm\"\n  cmd: |\n    choco install idapro.vm pdbs.pdbresym.vm visualstudio.vm --execution-timeout 10000;\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    # sync is installed by sysinternals\n    sync;\n  legal_notice: \"legal_notice.txt\"\n"
  },
  {
    "path": "virtualbox/configs/win11_flare-vm.yaml",
    "content": "VM_NAME: FLARE-VM.Win11.testing\nEXPORTED_VM_NAME: FLARE-VM.Win11\nSNAPSHOTS:\n- extension: \".dynamic\"\n  description: \"Windows 11 VM with FLARE-VM default configuration + idapro.vm\"\n  cmd: |\n    choco install idapro.vm;\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    # sync is installed by sysinternals\n    sync;\n  legal_notice: \"legal_notice.txt\"\n- extension: \".full.dynamic\"\n  description: \"Windows 11 VM with FLARE-VM default configuration + idapro.vm + pdbs.pdbresym.vm + visualstudio.vm\"\n  cmd: |\n    choco install idapro.vm pdbs.pdbresym.vm visualstudio.vm --execution-timeout 10000;\n\n    # Ensure files are written to persistent storage as the script shut down the VM abruptly\n    # sync is installed by sysinternals\n    sync;\n  legal_notice: \"legal_notice.txt\"\n"
  },
  {
    "path": "virtualbox/install.sh",
    "content": "#!/bin/bash\n\n# This script configures the vbox-adapter-check file to run automatically. It performs setup of a cron task.\n\n# --- Configuration ---\nINSTALL_DIR=\"$HOME/vbox\"\n\nset -e\n\necho_step() {\n  echo -e \"\\n\\033[1;34m==> $1\\033[0m\"\n}\n\necho_info() {\n  echo \"$1\"\n}\n\necho_success() {\n  echo -e \"\\033[1;32m✅ $1\\033[0m\"\n}\n\necho_error() {\n  echo -e \"\\n\\033[1;31m❌ ERROR: $1\\033[0m\" >&2\n  exit 1\n}\n\n# Step 1: Create installation directory and copy files\necho_step \"Setting up installation directory...\"\nmkdir -p \"$INSTALL_DIR\"\nSCRIPT_DIR=$(dirname \"$0\")\nif [ -f \"$SCRIPT_DIR/vbox-adapter-check\" -a -f \"$SCRIPT_DIR/vbox-clean-snapshots\" ]; then\n    cp \"$SCRIPT_DIR/vbox-adapter-check\" \"$INSTALL_DIR/\"\n    cp \"$SCRIPT_DIR/vbox-clean-snapshots\" \"$INSTALL_DIR/\"\nelif [ -f \"vbox-adapter-check\" -a -f \"vbox-clean-snapshots\" ]; then\n    cp \"vbox-adapter-check\" \"$INSTALL_DIR/\"\n    cp \"vbox-clean-snapshots\" \"$INSTALL_DIR/\"\nelse\n    echo_error \"The 'vbox-adapter-check' and 'vbox-clean-snapshots' binaries are not in the directory of the script or the current directory.\"\nfi\necho_info \"Copied 'vbox-adapter-check' and 'vbox-clean-snapshots' to $INSTALL_DIR\"\n\n# Step 2: Make files executable\necho_step \"Making tools in $INSTALL_DIR executable...\"\nif ! chmod +x \"$INSTALL_DIR\"/*; then\n  echo_error \"Failed to set execute permissions on files in $INSTALL_DIR.\"\nfi\necho_info \"File permissions updated.\"\n\n# Step 3: Run vbox-adapter-check\necho_step \"Running vbox-adapter-check\"\n$INSTALL_DIR/vbox-adapter-check\n\n# Step 4: Schedule the cron job if it doesn't exist\necho_step \"Scheduling background task...\"\nCRON_JOB=\"*/5 * * * * (echo \\\"# \\$(date)\\\"; $INSTALL_DIR/vbox-adapter-check) >> \\\"$INSTALL_DIR/vbox-adapter-check.log\\\" 2>&1\"\n\n# Check if the job already exists\nif crontab -l 2>/dev/null | grep -Fq \"vbox-adapter-check\"; then\n    echo_info \"Cron job for vbox-adapter-check already exists. Skipping.\"\nelse\n    echo_info \"Adding cron job...\"\n    (crontab -l 2>/dev/null; echo \"$CRON_JOB\") | crontab -\n    echo_info \"Cron job scheduled.\"\nfi\n\necho_success \"Installation Successful!\"\necho_info \"The vbox tools are installed in: $INSTALL_DIR\"\necho_info \"vbox-adapter-check writes logging information every 5 minutes to: $INSTALL_DIR/vbox-adapter-check.log\"\n\necho_step \"MANUAL ACTION REQUIRED: Add to PATH\"\necho_info \"To run the 'vbox' commands easily, you must add the installation directory to your shell's PATH.\"\necho_info \"Choose the command for your shell and add it to your startup file (e.g., ~/.bashrc, ~/.zshrc):\"\necho \"\"\necho \"    # For bash or zsh shells:\"\necho \"    echo 'export PATH=\\\"\\$HOME/vbox:\\$PATH\\\"' >> ~/.bashrc  # Or ~/.zshrc\"\necho \"\"\necho \"    # For fish shell:\"\necho \"    echo 'set -U fish_user_paths \\$HOME/vbox \\$fish_user_paths' >> ~/.config/fish/config.fish\"\necho \"\"\necho_info \"After updating your config file, restart your shell or run 'source ~/.bashrc' (or equivalent) to apply the changes.\"\n"
  },
  {
    "path": "virtualbox/vbox-adapter-check.py",
    "content": "#!/usr/bin/python3\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nimport argparse\nimport re\nimport sys\nimport textwrap\n\nimport gi\nfrom vboxcommon import ensure_hostonlyif_exists, get_vm_state, run_vboxmanage\n\ngi.require_version(\"Notify\", \"0.7\")\nfrom gi.repository import Notify  # noqa: E402\n\nDYNAMIC_VM_NAME = \".dynamic\"\nDISABLED_ADAPTER_TYPE = \"hostonly\"\nALLOWED_ADAPTER_TYPES = (\"hostonly\", \"intnet\", \"none\")\n\nDESCRIPTION = f\"\"\"Print the status of all internet adapters of all VMs in VirtualBox.\nOptionally, if any VM with {DYNAMIC_VM_NAME} in the name has an adapter whose type is not allowed,\nsend a notification and change the type of the adapters with non-allowed type to {DISABLED_ADAPTER_TYPE}.\nThis is useful to detect internet access which is undesirable for dynamic malware analysis.\"\"\"\n\n\nEPILOG = textwrap.dedent(\n    f\"\"\"\n    Example usage:\n      # Print status of all interfaces. For the VMs whose name contain {DYNAMIC_VM_NAME},\n      # show a notification and disable internet if enabled.\n      vbox-adapter-check.vm\n\n      # For the VMs whose name contain {DYNAMIC_VM_NAME}, print the status of their interfaces.\n      # If internet is enabled, show a notification and disable internet.\n      vbox-adapter-check.vm --dynamic_only\n\n      # Print status of all interfaces without modifying any of them.\n      vbox-adapter-check.vm --do_not_modify\n\n      # Print status of all interfaces in VMs whose name contain {DYNAMIC_VM_NAME} without modifying any of them.\n      vbox-adapter-check.vm --dynamic_only --do_not_modify\n    \"\"\"\n)\n\n\ndef get_vms(dynamic_only):\n    \"\"\"Get the names and UUID of the VirtualBox VMs using 'VBoxManage list vms'.\n\n    Args:\n        dynamic_only: If true, only the VMs containing DYNAMIC_VM_NAME in the name are returned.\n\n    Returns:\n        A list of tuples, where each tuple contains the VM name (str) and VM UUID (str).\n        Returns an empty list if no VMs are found.\n    \"\"\"\n    vms_list = []\n    # regex VM name and extract the GUID\n    # Example of `VBoxManage list vms` output:\n    # \"FLARE-VM.testing\" {b76d628b-737f-40a3-9a16-c5f66ad2cfcc}\n    # \"FLARE-VM\" {a23c0c37-2062-4cf0-882b-9e9747dd33b6}\n    vms_info = run_vboxmanage([\"list\", \"vms\"])\n\n    vms = re.findall(r'\"(.*?)\" (\\{.*?\\})', vms_info)\n    for vm_name, vm_uuid in vms:\n        # Get only the VMs containing DYNAMIC_VM_NAME in the name if dynamic_only is true\n        if not (dynamic_only and (DYNAMIC_VM_NAME in vm_name)):\n            vms_list.append((vm_name, vm_uuid))\n    return vms_list\n\n\ndef get_nics(vm_uuid, only_nic=None):\n    \"\"\"\n    Retrieves the configured network interfaces and their types for a given virtual machine.\n\n    Args:\n        vm_uuid: The unique identifier (UUID) of the virtual machine.\n        only_nic: An optional string specifying a specific NIC number to retrieve\n                  (e.g., \"1\" for nic1). If None, information for all configured NICs\n                  will be returned.\n\n    Returns:\n        A list of tuples, where each tuple contains:\n        - The NIC number as a string (e.g., \"1\", \"2\")\n        - The NIC value (e.g., \"hostonly\", \"nat\")\n    \"\"\"\n\n    # Example of `VBoxManage showvm_info <VM_UUID> --machinereadable` relevant output:\n    # nic1=\"hostonly\"\n    # nictype1=\"82540EM\"\n    # nicspeed1=\"0\"\n    # nic2=\"none\"\n    # nic3=\"none\"\n    # nic4=\"none\"\n    # nic5=\"none\"\n    # nic6=\"none\"\n    # nic7=\"none\"\n    # nic8=\"none\"\n    vm_info = run_vboxmanage([\"showvminfo\", vm_uuid, \"--machinereadable\"])\n\n    # If no nic provided, get all possible numbers using RegExp\n    only_nic = r\"\\d+\"\n\n    # Get adapters numbers and their values as a list: [(nic_number, nic_value)]\n    return re.findall(rf'^nic({only_nic})=\"(\\S+)\"', vm_info, flags=re.M)\n\n\ndef disable_adapter(vm_uuid, nic_number, hostonly_ifname):\n    \"\"\"Disable the network adapter of the VM by setting it to DISABLED_ADAPTER_TYPE\n\n    Args:\n        vm_uuid: VM UUID\n        nic_number: nic to disable\n\n    Raises:\n        RuntimeError: If the nic type is not changed to DISABLED_ADAPTER_TYPE\n    \"\"\"\n    # We need to run a different command if the machine is running.\n    if get_vm_state(vm_uuid) in (\"poweroff\", \"aborted\"):\n        run_vboxmanage(\n            [\n                \"modifyvm\",\n                vm_uuid,\n                f\"--nic{nic_number}\",\n                DISABLED_ADAPTER_TYPE,\n            ]\n        )\n        # Set the hostonlyadapter for nic as \"VBoxManage modifyvm --nic\" does not set it\n        # If hostonlyadapter is empty, starting the VM raises an error\n        run_vboxmanage(\n            [\n                \"modifyvm\",\n                vm_uuid,\n                f\"--hostonlyadapter{nic_number}\",\n                hostonly_ifname,\n            ]\n        )\n    else:\n        run_vboxmanage(\n            [\n                \"controlvm\",\n                vm_uuid,\n                f\"nic{nic_number}\",\n                DISABLED_ADAPTER_TYPE,\n                hostonly_ifname,\n            ]\n        )\n\n    # Verify nic has been modify as the command may return code 0 even if it fails to set the adapter\n    _, nic_value = get_nics(vm_uuid, nic_number)[0]\n    if nic_value != DISABLED_ADAPTER_TYPE:\n        raise RuntimeError(f\"nic{nic_number} has type '{nic_value}'\")\n\n\ndef list_to_str(string_list):\n    \"\"\"Joins a list of strings with \", \".\"\"\"\n    return \", \".join(string_list)\n\n\ndef verify_network_adapters(vm_uuid, vm_name, hostonly_ifname, modify_and_notify):\n    \"\"\"Verify and optionally correct network adapter configurations for a given VM.\n\n    Check the network adapter types of a given VM against a list of allowed types (`ALLOWED_ADAPTER_TYPES`).\n    If not allowed adapter types are found, print a warning and, if `do_not_modify` is False, disable the adapters and sends a desktop notification.\n\n    Args:\n        vm_uuid: The unique identifier (UUID) of the VM.\n        vm_name: The name of the VM.\n        hostonly_ifname: The name of the host-only network interface. This is passed for potential use in\n                         disabling adapters (though not directly used in the verification logic).\n        modify_and_notify: A boolean flag. If False, invalid adapters will only be reported, without automatic modification and notification.\n    \"\"\"\n    try:\n        invalid_nics = []\n        for nic_number, nic_value in get_nics(vm_uuid):\n            if nic_value not in ALLOWED_ADAPTER_TYPES:\n                invalid_nics.append(nic_number)\n\n        if not invalid_nics:\n            print(f\"VM {vm_uuid} ✅ {vm_name} network configuration is ok\")\n            return\n\n        invalid_nics_msg = list_to_str(invalid_nics)\n        print(f\"VM {vm_uuid} ⚠️  {vm_name} is connected to the internet on adapter(s): {invalid_nics_msg}\")\n\n        if modify_and_notify:\n            # Disable invalid nics\n            for nic in invalid_nics:\n                try:\n                    disable_adapter(vm_uuid, nic, hostonly_ifname)\n                    print(f\"VM {vm_uuid} ⚙️  {vm_name} set adapter {nic} to {DISABLED_ADAPTER_TYPE}\")\n                except Exception as e:\n                    print(f\"VM {vm_uuid} ❌ {vm_name} unable to disable adapter {nic}: {e}\")\n\n            message = (\n                f\"{vm_name} may be connected to the internet on adapter(s): {invalid_nics_msg}.\"\n                \"The network adapter(s) may have been disabled automatically to prevent an undesired internet connectivity.\"\n                \"Please double check your VMs settings.\"\n            )\n            # Show notification using PyGObject\n            Notify.init(\"VirtualBox adapter check\")\n            notification = Notify.Notification.new(f\"⚠️  INTERNET IN VM: {vm_name}\", message, \"dialog-error\")\n            # Set highest priority\n            notification.set_urgency(2)\n            notification.show()\n\n    except Exception as e:\n        print(f\"VM {vm_uuid} {vm_name} ❌ Unable to verify network adapters: {e}\")\n\n\ndef main(argv=None):\n    if argv is None:\n        argv = sys.argv[1:]\n\n    parser = argparse.ArgumentParser(\n        description=DESCRIPTION,\n        epilog=EPILOG,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n    )\n    parser.add_argument(\n        \"--do_not_modify\",\n        action=\"store_true\",\n        help=\"Only print the status of the internet adapters without modifying them and without showing a notification.\",\n    )\n    parser.add_argument(\n        \"--dynamic_only\",\n        action=\"store_true\",\n        help=\"Only scan VMs with .dynamic in the name\",\n    )\n    args = parser.parse_args(args=argv)\n\n    hostonly_ifname = ensure_hostonlyif_exists()\n    vms = get_vms(args.dynamic_only)\n    if len(vms) > 0:\n        for vm_name, vm_uuid in vms:\n            # Never modify VMs without DYNAMIC_VM_NAME in the name (only check the status)\n            modify_and_notify = (DYNAMIC_VM_NAME in vm_name) and (not args.do_not_modify)\n            verify_network_adapters(vm_uuid, vm_name, hostonly_ifname, modify_and_notify)\n    else:\n        print(\"⚠️  No VMs found!\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "virtualbox/vbox-build-flare-vm.py",
    "content": "#!/usr/bin/python3\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport argparse\nimport os\nimport sys\nimport time\nfrom datetime import datetime\n\nimport yaml\nfrom vboxcommon import (\n    LONG_WAIT,\n    control_guest,\n    ensure_vm_running,\n    export_vm,\n    get_vm_state,\n    get_vm_uuid,\n    restore_snapshot,\n    set_network_to_hostonly,\n    take_snapshot,\n)\n\nDESCRIPTION = \"\"\"\nAutomates the creation and export of customized FLARE-VM virtual machines (VMs).\nBegins by restoring a pre-existing \"BUILD-READY\" snapshot of a clean Windows installation (with UAC disabled).\nRequired installation files (such as the IDA Pro installer, FLARE-VM configuration, and legal notices) are then copied into the guest VM.\nAfter installing FLARE-VM, a \"base\" snapshot is taken.\nThis snapshot serves as the foundation for generating subsequent snapshots and exporting OVA images,\nall based on the configuration provided in a YAML file.\nThis configuration file specifies the VM name, the exported VM name, and details for each snapshot.\nIndividual snapshot configurations can include custom commands to be executed within the guest, legal notices to be applied,\nand file/folder exclusions for the automated cleanup process.\n\"\"\"\n\nEPILOG = \"\"\"\nExample usage:\n  # Build FLARE-VM and export several OVAs using the information in the provided configuration file, using '19930906' as date\n  # ./vbox-build-flare-vm.py configs/win10_flare-vm.yaml --custom_config --date='19930906'\n\"\"\"\n\n# The base snapshot is expected to be an empty Windows installation that satisfies the FLARE-VM installation requirements and has UAC disabled\n# To disable UAC execute in a cmd console with admin rights and restart the VM for the change to take effect:\n# %windir%\\System32\\reg.exe ADD HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System /v EnableLUA /t REG_DWORD /d 0 /f\nBASE_SNAPSHOT = \"BUILD-READY\"\n\n# Guest username and password, needed to execute commands in the guest\nGUEST_USERNAME = \"flare\"\nGUEST_PASSWORD = \"password\"\n\n# Logs\nLOGS_DIR = os.path.expanduser(\"~/FLARE-VM LOGS\")\nLOG_FILE_GUEST = r\"C:\\ProgramData\\_VM\\log.txt\"\nLOG_FILE_HOST = rf\"{LOGS_DIR}/flare-vm-log.txt\"\nFAILED_PACKAGES_GUEST = r\"C:\\ProgramData\\_VM\\failed_packages.txt\"\nFAILED_PACKAGES_HOST = rf\"{LOGS_DIR}/flare-vm-failed_packages.txt\"\n\n# Required files\nREQUIRED_FILES_DIR = os.path.expanduser(\"~/FLARE-VM REQUIRED FILES\")\nREQUIRED_FILES_DEST = rf\"C:\\Users\\{GUEST_USERNAME}\\Desktop\"\n\n# Executable paths in guest\nPOWERSHELL_PATH = r\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\"\nCMD_PATH = r\"C:\\Windows\\System32\\cmd.exe\"\n\n# Cleanup command to be executed in cmd to delete the PowerShell logs\n# Run sync (installed by sysinternals) to ensure files are written to persistent storage as the script shut down the VM abruptly\nCMD_CLEANUP_CMD = r\"/C rmdir /s /q %UserProfile%\\Desktop\\PS_Transcripts && sync\"\n\n\ndef run_command(vm_uuid, cmd, executable=\"PS\"):\n    \"\"\"Run a command in the guest of the specified VM, displaying the output in real time to the console.\n\n    Args:\n        vm_uuid: VM UUID\n        cmd: The command string to execute in the guest.\n        executable: Specifies the executable to use for running the command, either `PS` (`powershell.exe) or `CMD` (`cmd.exe`).\n    \"\"\"\n    ensure_vm_running(vm_uuid)\n\n    exe_path = POWERSHELL_PATH if executable == \"PS\" else CMD_PATH\n\n    print(f\"VM {vm_uuid} 🚧 {executable}: {cmd}\")\n    control_guest(vm_uuid, GUEST_USERNAME, GUEST_PASSWORD, [\"run\", exe_path, cmd], True)\n\n\ndef create_log_folder():\n    \"\"\"Ensure log folder exists and is empty.\"\"\"\n    # Create directory if it does not exist\n    os.makedirs(LOGS_DIR, exist_ok=True)\n    print(f\"Log folder: {LOGS_DIR}\\n\")\n\n    # Remove all files in the logs directory. Note the directory only files (the logs).\n    for file_name in os.listdir(LOGS_DIR):\n        file_path = os.path.join(LOGS_DIR, file_name)\n        os.remove(file_path)\n\n\ndef install_flare_vm(vm_uuid, snapshot_name, custom_config):\n    \"\"\"Install FLARE-VM\"\"\"\n    additional_arg = r\"-customConfig '$desktop\\config.xml'\" if custom_config else \"\"\n    flare_vm_installation_cmd = rf\"\"\"\n    $desktop=[Environment]::GetFolderPath(\"Desktop\")\n    cd $desktop\n    Set-ExecutionPolicy Unrestricted -Force\n    $url=\"https://raw.githubusercontent.com/mandiant/flare-vm/main/install.ps1\"\n    $file = \"$desktop\\install.ps1\"\n    (New-Object net.webclient).DownloadFile($url,$file)\n    Unblock-File .\\install.ps1\n\n    start powershell \"$file -password password -noWait -noGui -noChecks {additional_arg}\"\n    \"\"\"\n    run_command(vm_uuid, flare_vm_installation_cmd)\n    print(f\"VM {vm_uuid} ✅ FLARE-VM is being installed...{LONG_WAIT}\")\n\n    index = 0\n    while True:\n        time.sleep(120)  # Wait 2 minutes\n        try:\n            control_guest(\n                vm_uuid,\n                GUEST_USERNAME,\n                GUEST_PASSWORD,\n                [\"copyfrom\", f\"--target-directory={FAILED_PACKAGES_HOST}\", FAILED_PACKAGES_GUEST],\n            )\n            break\n        except RuntimeError:\n            index += 1\n            if (index % 10) == 0:  # Print an \"I am alive\" message every ~20 minutes\n                time_str = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n                print(f\"VM {vm_uuid} 🕑 {time_str} still waiting\")\n                # Take snaphost that can be restore if the VM crashes\n                # Avoid taking a snapshot during a restart (as it could crash the VM) by checking the VM is running\n                if get_vm_state(vm_uuid) == \"running\":\n                    wip_snapshot_name = f\"WIP {snapshot_name} {time_str}\"\n                    take_snapshot(vm_uuid, wip_snapshot_name)\n\n    print(f\"VM {vm_uuid} ✅ FLARE-VM installed!\")\n\n    control_guest(\n        vm_uuid, GUEST_USERNAME, GUEST_PASSWORD, [\"copyfrom\", f\"--target-directory={LOG_FILE_HOST}\", LOG_FILE_GUEST]\n    )\n    print(f\"VM {vm_uuid} 📁 Copied FLARE-VM log: {REQUIRED_FILES_DIR}\")\n\n    # Read failed packages from log file and print them\n    try:\n        if os.path.getsize(FAILED_PACKAGES_HOST):\n            print(\"  ❌ FAILED PACKAGES\")\n            with open(FAILED_PACKAGES_HOST, \"r\") as f:\n                for failed_package in f:\n                    print(f\"     - {failed_package}\")\n    except Exception:\n        print(f\"  ❌ Reading {FAILED_PACKAGES_HOST} failed\")\n\n\ndef build_vm(vm_name, exported_vm_name, snapshots, date, custom_config, do_not_install_flare_vm):\n    \"\"\"\n    Build and export multiple FLARE-VM VMs as OVAs based on provided configurations.\n\n    This function first prepares a base FLARE-VM VM by restoring a BASE_SNAPSHOT,\n    copying necessary files, and installing the FLARE-VM software. A base snapshot\n    of this installation is then taken. Subsequently, for each configuration\n    specified in the `snapshots` list, the base snapshot is restored, customized\n    with specific commands and settings, and then exported as an OVA.\n\n    Args:\n        vm_name: The name of the VM.\n        exported_vm_name: The base name to use for naming the exported VMs and their snapshots.\n        snapshots: A list of dictionaries, where each dictionary defines the configuration for a specific exported VM.\n                   Each dictionary can contain the following keys:\n                     - cmd: A command to execute in the guest VM.\n                     - legal_notice: The filename of a legal notice to set on the VM.\n                     - protected_files: A string of files to exclude during the cleanup process.\n                     - protected_folders:: A string of folders to exclude during the cleanup process.\n                     - extension: An extension to add to the exported VM's filename.\n                     - description: A description to embed in the exported OVA.\n        date: A date string appended to the names of the base snapshot and exported OVAs.\n        custom_config: Custom configuration parameters passed to the FLARE-VM installation script.\n        do_not_install_flare_vm: If True, the FLARE-VM installation step is skipped and an existent base snapshot used.\n                                 It also does not copy the required files.\n    \"\"\"\n    vm_uuid = get_vm_uuid(vm_name)\n    if not vm_uuid:\n        print(f'❌ ERROR: \"{vm_name}\" not found')\n        exit()\n\n    print(f'\\nGetting the installation VM \"{vm_name}\" {vm_uuid} ready...')\n    create_log_folder()\n\n    base_snapshot_name = f\"{exported_vm_name}.{date}.base\"\n\n    if not do_not_install_flare_vm:\n        restore_snapshot(vm_uuid, BASE_SNAPSHOT)\n\n        # Copy required files\n        control_guest(\n            vm_uuid,\n            GUEST_USERNAME,\n            GUEST_PASSWORD,\n            [\"copyto\", \"--recursive\", f\"--target-directory={REQUIRED_FILES_DEST}\", REQUIRED_FILES_DIR],\n        )\n        print(f\"VM {vm_uuid} 📁 Copied required files in: {REQUIRED_FILES_DIR}\")\n\n        install_flare_vm(vm_uuid, exported_vm_name, custom_config)\n        take_snapshot(vm_uuid, base_snapshot_name, False, True)\n\n    for snapshot in snapshots:\n        restore_snapshot(vm_uuid, base_snapshot_name)\n\n        # Run snapshot configured command\n        cmd = snapshot.get(\"cmd\", None)\n        if cmd:\n            run_command(vm_uuid, cmd)\n\n        set_network_to_hostonly(vm_uuid)\n\n        # Set snapshot configured legal notice\n        notice_file_name = snapshot.get(\"legal_notice\", None)\n        if notice_file_name:\n            notice_file_path = rf\"C:\\Users\\{GUEST_USERNAME}\\Desktop\\{notice_file_name}\"\n            set_notice_cmd = (\n                f\"Import-Module $env:VM_COMMON_DIR\\\\vm.common\\\\vm.common.psm1; \"\n                f\"VM-Set-Legal-Notice (Get-Content '{notice_file_path}' -Raw)\"\n            )\n            run_command(vm_uuid, set_notice_cmd)\n\n        # Perform clean up: run 'VM-Clean-Up' excluding configured files and folders\n        ps_cleanup_cmd = \"Import-Module $env:VM_COMMON_DIR\\\\vm.common\\\\vm.common.psm1; VM-Clean-Up\"\n        protected_files = snapshot.get(\"protected_files\", None)\n        if protected_files:\n            ps_cleanup_cmd += f\" -excludeFiles {protected_files}\"\n        protected_folders = snapshot.get(\"protected_folders\", None)\n        if protected_folders:\n            ps_cleanup_cmd += f\" -excludeFolders {protected_folders}\"\n        run_command(vm_uuid, ps_cleanup_cmd)\n\n        # Perform clean up: delete PowerShells logs (using cmd.exe)\n        run_command(vm_uuid, CMD_CLEANUP_CMD, \"CMD\")\n\n        # Take snapshot turning the VM off\n        extension = snapshot.get(\"extension\", \"\")\n        snapshot_name = f\"{exported_vm_name}.{date}{extension}\"\n        take_snapshot(vm_uuid, snapshot_name, True, True)\n\n        # Export the snapshot with the configured description\n        export_vm(vm_uuid, snapshot_name, snapshot.get(\"description\", \"\"))\n\n\ndef main(argv=None):\n    if argv is None:\n        argv = sys.argv[1:]\n\n    parser = argparse.ArgumentParser(\n        description=DESCRIPTION,\n        epilog=EPILOG,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n    )\n    parser.add_argument(\"config_path\", help=\"path of the YAML configuration file.\")\n    parser.add_argument(\n        \"--date\",\n        help=\"Date to include in the snapshots and the exported VMs in YYYYMMDD format. Today's date by default.\",\n        default=datetime.today().strftime(\"%Y%m%d\"),\n    )\n    parser.add_argument(\n        \"--custom_config\",\n        action=\"store_true\",\n        default=False,\n        help=f\"flag to use a custom configuration file named 'config.xml' (expected to be in {REQUIRED_FILES_DIR}) for the FLARE-VM installation.\",\n    )\n    parser.add_argument(\n        \"--do-not-install-flare-vm\",\n        action=\"store_true\",\n        default=False,\n        help=\"flag to not install FLARE-VM and used an existent base snapshot. It also does not copy the required files.\",\n    )\n    args = parser.parse_args(args=argv)\n\n    try:\n        with open(args.config_path) as f:\n            config = yaml.safe_load(f)\n    except Exception as e:\n        print(f'Invalid \"{args.config_path}\": {e}')\n        exit()\n\n    build_vm(\n        config[\"VM_NAME\"],\n        config[\"EXPORTED_VM_NAME\"],\n        config[\"SNAPSHOTS\"],\n        args.date,\n        args.custom_config,\n        args.do_not_install_flare_vm,\n    )\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "virtualbox/vbox-build-remnux.py",
    "content": "#!/usr/bin/python3\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport argparse\nimport os\nimport sys\nfrom datetime import datetime\n\nimport yaml\nfrom vboxcommon import (\n    control_guest,\n    ensure_vm_running,\n    export_vm,\n    get_vm_uuid,\n    restore_snapshot,\n    set_network_to_hostonly,\n    take_snapshot,\n)\n\nDESCRIPTION = \"\"\"\nAutomates the creation and export of customized REMnux virtual machines (VMs).\nBegins by restoring a pre-existing \"BUILD-READY\" snapshot of a clean REMnux OVA.\nRequired installation files (such as the IDA Pro installer and ZIPs with GNOME extensions) are then copied into the guest VM.\nThe configuration file specifies the VM name, the exported VM name, and details for each snapshot.\nIndividual snapshot configurations include the extension, description, and custom commands to be executed within the guest.\n\"\"\"\n\nEPILOG = \"\"\"\nExample usage:\n  #./vbox-build-remnux.py configs/remnux.yaml --date='19930906'\n\"\"\"\n\nBASE_SNAPSHOT = \"BUILD-READY\"\n\n# Guest username and password, needed to execute commands in the guest\nGUEST_USERNAME = \"remnux\"\nGUEST_PASSWORD = \"malware\"\n\n# Required files\nREQUIRED_FILES_DIR = os.path.expanduser(\"~/REMNUX REQUIRED FILES\")\nREQUIRED_FILES_DEST = rf\"/home/{GUEST_USERNAME}/Desktop\"\n\n\ndef run_command(vm_uuid, cmd):\n    \"\"\"Run a command in the guest of the specified VM, displaying the output in real time to the console.\n\n    Args:\n        vm_uuid: VM UUID\n        cmd: The command string to execute in the guest using `/bin/sh -c`.\n    \"\"\"\n    ensure_vm_running(vm_uuid)\n\n    executable = \"/bin/sh\"\n    print(f\"VM {vm_uuid} 🚧 {executable}: {cmd}\")\n    control_guest(vm_uuid, GUEST_USERNAME, GUEST_PASSWORD, [\"run\", executable, \"--\", \"-c\", cmd], True)\n\n\ndef build_vm(vm_name, exported_vm_name, snapshot, cmds, date, do_not_upgrade):\n    \"\"\"\n    Build a REMnux VM and export it as OVA.\n\n    Build a REMnux VM by restoring the BASE_SNAPSHOT, upgrading the REMnux distro,\n    copying required files, running given commands, removing copied required files.\n    Take several snapshots that can be used for debugging issues.\n    Set the network to hostonly and export the resulting VM as OVA.\n\n    Args:\n        vm_name: The name of the VM.\n        exported_vm_name: The base name to use for the final exported VM and snapshots.\n        snapshot: A dictionary containing information about the final snapshot,\n                  including optional `extension` and `description`.\n        cmds: A list of string commands to execute sequentially within the guest VM.\n              A snapshot is taken after executing each command.\n        date: A date string to incorporate into snapshot names and the exported OVA.\n        do_not_upgrade: If True, the initial upgrade step is skipped and an existent UPGRADED snapshot used.\n                        It also does not copy the required files.\n    \"\"\"\n    vm_uuid = get_vm_uuid(vm_name)\n    if not vm_uuid:\n        print(f'❌ ERROR: \"{vm_name}\" not found')\n        exit()\n\n    print(f'\\nGetting the installation VM \"{vm_name}\" {vm_uuid} ready...')\n\n    base_snapshot_name = f\"UPGRADED.{date}\"\n\n    if not do_not_upgrade:\n        restore_snapshot(vm_uuid, BASE_SNAPSHOT)\n\n        # Copy required files\n        control_guest(\n            vm_uuid,\n            GUEST_USERNAME,\n            GUEST_PASSWORD,\n            [\"copyto\", \"--recursive\", f\"--target-directory={REQUIRED_FILES_DEST}\", REQUIRED_FILES_DIR],\n        )\n        print(f\"VM {vm_uuid} 📁 Copied required files in: {REQUIRED_FILES_DIR}\")\n\n        # Update REMnux distro and take a snapshot\n        run_command(vm_uuid, \"sudo remnux upgrade\")\n        take_snapshot(vm_uuid, base_snapshot_name)\n    else:\n        restore_snapshot(vm_uuid, base_snapshot_name)\n\n    # Run snapshot configured commands taking a snapshot after running every command\n    for i, cmd in enumerate(cmds):\n        run_command(vm_uuid, cmd)\n        take_snapshot(vm_uuid, f\"{exported_vm_name}.{date} CMD {cmd.splitlines()[0]}\")\n\n    # Delete required files copied to the VM\n    files = f\"{REQUIRED_FILES_DEST}/*\"\n    # Sync is needed ti ensure the files deletion is written to persistent storage as the script shut down the VM abruptly\n    run_command(vm_uuid, f\"ls {files}; rm {files}; sync\")\n\n    set_network_to_hostonly(vm_uuid)\n\n    # Take snapshot turning the VM off\n    extension = snapshot.get(\"extension\", \"\")\n    snapshot_name = f\"{exported_vm_name}.{date}{extension}\"\n    take_snapshot(vm_uuid, snapshot_name, True)\n\n    # Export the snapshot with the configured description\n    export_vm(vm_uuid, snapshot_name, snapshot.get(\"description\", \"\"))\n\n\ndef main(argv=None):\n    if argv is None:\n        argv = sys.argv[1:]\n\n    parser = argparse.ArgumentParser(\n        description=DESCRIPTION,\n        epilog=EPILOG,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n    )\n    parser.add_argument(\"config_path\", help=\"path of the YAML configuration file.\")\n    parser.add_argument(\n        \"--date\",\n        help=\"Date to include in the snapshots and the exported VMs in YYYYMMDD format. Today's date by default.\",\n        default=datetime.today().strftime(\"%Y%m%d\"),\n    )\n    parser.add_argument(\n        \"--do-not-upgrade\",\n        action=\"store_true\",\n        default=False,\n        help=\"flag to not upgrade the REMnux distro and use an existent UPGRADED snapshot. It also does not copy the required files.\",\n    )\n    args = parser.parse_args(args=argv)\n\n    try:\n        with open(args.config_path) as f:\n            config = yaml.safe_load(f)\n    except Exception as e:\n        print(f'Invalid \"{args.config_path}\": {e}')\n        exit()\n\n    build_vm(\n        config[\"VM_NAME\"],\n        config[\"EXPORTED_VM_NAME\"],\n        config[\"SNAPSHOT\"],\n        config[\"CMDS\"],\n        args.date,\n        args.do_not_upgrade,\n    )\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "virtualbox/vbox-clean-snapshots.py",
    "content": "#!/usr/bin/python3\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\nimport argparse\nimport re\nimport sys\nimport textwrap\n\nfrom vboxcommon import get_vm_state, run_vboxmanage\n\nDESCRIPTION = \"Clean a VirtualBox VM up by deleting a snapshot and its children recursively skipping snapshots with a substring in the name.\"\n\nEPILOG = textwrap.dedent(\n    \"\"\"\n    Example usage:\n      # Delete all snapshots excluding the default protected ones (with 'clean' or 'done' in the name, case insensitive) in the 'FLARE-VM.20240604' VM\n      vbox-clean-snapshots.py FLARE-VM.20240604\n\n      # Delete all snapshots that do not include 'clean', 'done', or 'important' (case insensitive) in the name in the 'FLARE-VM.20240604' VM\n      vbox-clean-snapshots.py FLARE-VM.20240604 --protected_snapshots \"clean,done,important\"\n\n      # Delete the 'Snapshot 3' snapshot and its children recursively skipping the default protected ones in the 'FLARE-VM.20240604' VM\n      vbox-clean-snapshots.py FLARE-VM.20240604 --root_snapshot \"Snapshot 3\"\n\n      # Delete the 'CLEAN with IDA 8.4\"' children snapshots recursively skipping the default protected ones in the 'FLARE-VM.20240604' VM\n      # NOTE: the 'CLEAN with IDA 8.4' root snapshot is skipped in this case\n      vbox-clean-snapshots.py FLARE-VM.20240604 --root_snapshot \"CLEAN with IDA 8.4\"\n\n      # Delete all snapshots in the 'FLARE-VM.20240604' VM\n      vbox-clean-snapshots.py FLARE-VM.20240604 --protected_snapshots \"\"\n    \"\"\"\n)\n\n\ndef is_protected(protected_snapshots, snapshot_name):\n    \"\"\"Check if snapshot_name contains any of the strings in the protected_snapshots list (case insensitive)\"\"\"\n    return any(p.lower() in snapshot_name.lower() for p in protected_snapshots)\n\n\ndef get_snapshot_children(vm_name, root_snapshot_name, protected_snapshots):\n    \"\"\"Get the children of a snapshot (including the snapshot) using 'VBoxManage snapshot' with the 'list' option.\n\n    Args:\n      vm_name: The name of the VM.\n      root_snapshot_name: The name of the root snapshot we want the children of. If no provided or not found, return all snapshots.\n      protected_snapshots: Snapshots we ignore and do not include in the returned list.\n\n    Returns:\n      A list of snapshot names that are children of the given snapshot. The list is ordered by dependent relationships.\n    \"\"\"\n    # Example of `VBoxManage snapshot VM_NAME list --machinereadable` output:\n    # SnapshotName=\"ROOT\"\n    # SnapshotUUID=\"86b38fc9-9d68-4e4b-a033-4075002ab570\"\n    # SnapshotName-1=\"Snapshot 1\"\n    # SnapshotUUID-1=\"e383e702-fee3-4e0b-b1e0-f3b869dbcaea\"\n    # CurrentSnapshotName=\"Snapshot 1\"\n    # CurrentSnapshotUUID=\"e383e702-fee3-4e0b-b1e0-f3b869dbcaea\"\n    # CurrentSnapshotNode=\"SnapshotName-1\"\n    # SnapshotName-1-1=\"Snapshot 2\"\n    # SnapshotUUID-1-1=\"8cc12787-99df-466e-8a51-80e373d3447a\"\n    # SnapshotName-2=\"Snapshot 3\"\n    # SnapshotUUID-2=\"f42533a8-7c14-4855-aa66-7169fe8187fe\"\n    #\n    # ROOT\n    #   ├─ Snapshot 1\n    #   │   └─ Snapshot 2\n    #   └─ Snapshot 3\n    snapshots_info = run_vboxmanage([\"snapshot\", vm_name, \"list\", \"--machinereadable\"])\n\n    root_snapshot_index = \"\"\n    if root_snapshot_name:\n        # Find root snapshot: first snapshot with name root_snapshot_name (case sensitive)\n        root_snapshot_regex = rf'^SnapshotName(?P<index>(?:-\\d+)*)=\"{root_snapshot_name}\"\\n'\n        root_snapshot = re.search(root_snapshot_regex, snapshots_info, flags=re.M)\n        if root_snapshot:\n            root_snapshot_index = root_snapshot[\"index\"]\n        else:\n            print(f\"\\n⚠️  Root snapshot not found: {root_snapshot_name} 🫧 Cleaning all snapshots in the VM\")\n\n    # Find all root and child snapshots as (snapshot_name, snapshot_id)\n    # Children of a snapshot share the same prefix index\n    index_regex = rf\"{root_snapshot_index}(?:-\\d+)*\"\n    snapshot_regex = f'^SnapshotName{index_regex}=\"(.*?)\"\\nSnapshotUUID{index_regex}=\"(.*?)\"'\n    snapshots = re.findall(snapshot_regex, snapshots_info, flags=re.M)\n\n    # Return non protected snapshots as list of (snapshot_name, snapshot_id)\n    return [snapshot for snapshot in snapshots if not is_protected(protected_snapshots, snapshot[0])]\n\n\ndef delete_snapshot_and_children(vm_name, snapshot_name, protected_snapshots):\n    snaps_to_delete = get_snapshot_children(vm_name, snapshot_name, protected_snapshots)\n\n    if protected_snapshots:\n        print(\"\\nSnapshots with the following strings in the name (case insensitive) won't be deleted:\")\n        for protected_snapshot in protected_snapshots:\n            print(f\"  {protected_snapshot}\")\n\n    if snaps_to_delete:\n        print(f\"\\nCleaning {vm_name} 🫧 Snapshots to delete:\")\n        for snapshot_name, _ in snaps_to_delete:\n            print(f\"  {snapshot_name}\")\n\n        vm_state = get_vm_state(vm_name)\n        if vm_state not in (\"poweroff\", \"saved\"):\n            print(\n                f\"\\nVM state: {vm_state}\\n⚠️  Snapshot deleting is slower in a running VM and may fail in a changing state\"\n            )\n\n        answer = input(\"\\nConfirm deletion (press 'y'): \")\n        if answer.lower() == \"y\":\n            print(\"\\nDELETING SNAPSHOTS... (this may take some time, go for an 🍦!)\")\n            # Delete snapshots in reverse order to avoid issues with child snapshots,\n            # as a snapshot with more than 1 child can not be deleted\n            for snapshot_name, snapshot_id in reversed(snaps_to_delete):\n                try:\n                    run_vboxmanage([\"snapshot\", vm_name, \"delete\", snapshot_id])\n                    print(f\"🫧 DELETED '{snapshot_name}'\")\n                except Exception as e:\n                    print(f\"❌ ERROR '{snapshot_name}'\\n{e}\")\n    else:\n        print(f\"\\n{vm_name} is clean 🫧\")\n\n    print(\"\\nSee you next time you need to clean up your VMs! ✨\\n\")\n\n\ndef main(argv=None):\n    if argv is None:\n        argv = sys.argv[1:]\n\n    epilog = EPILOG\n    parser = argparse.ArgumentParser(\n        description=DESCRIPTION,\n        epilog=epilog,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n    )\n    parser.add_argument(\"vm_name\", help=\"Name of the VM to clean up\")\n    parser.add_argument(\n        \"--root_snapshot\",\n        help=\"\"\"Snapshot name (case sensitive) to delete (and its children recursively).\n                Leave empty to clean all snapshots in the VM.\"\"\",\n    )\n    parser.add_argument(\n        \"--protected_snapshots\",\n        default=\"clean,done\",\n        type=lambda s: s.split(\",\") if s else [],\n        help='''Comma-separated list of strings.\n                Snapshots with any of the strings included in the name (case insensitive) are not deleted.\n                Default: \"clean,done\"''',\n    )\n    args = parser.parse_args(args=argv)\n\n    delete_snapshot_and_children(args.vm_name, args.root_snapshot, args.protected_snapshots)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "virtualbox/vbox-export-snapshot.py",
    "content": "#!/usr/bin/python3\n# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport argparse\nimport sys\n\nfrom vboxcommon import (\n    EXPORT_DIR_NAME,\n    LONG_WAIT,\n    ensure_vm_running,\n    export_vm,\n    get_vm_uuid,\n    restore_snapshot,\n    set_network_to_hostonly,\n)\n\nDESCRIPTION = \"\"\"Export a snapshot to OVA (named after the snapshot) with a single Host-Only network interface.\nGenerate a file containing the SHA256 hash of the OVA that can be used for verification.\"\"\"\n\nEPILOG = \"\"\"\nExample usage:\n  # Export snapshot \"FLARE-VM\" from the \"FLARE-VM.testing\" VM with a description\n  ./vbox-export-snapshot.py \"FLARE-VM.testing\" \"FLARE-VM\" --description \"Windows 10 VM with FLARE-VM default configuration\"\n\"\"\"\n\n\ndef export_snapshot(vm_name, snapshot, description, export_dir_name):\n    \"\"\"Restore a snapshot, set the network to hostonly and then export it with the snapshot as name.\"\"\"\n    vm_uuid = get_vm_uuid(vm_name)\n    if not vm_uuid:\n        print(f'❌ ERROR: \"{vm_name}\" not found')\n        exit()\n\n    print(f'\\nExporting snapshot \"{snapshot}\" from \"{vm_name}\" {vm_uuid}...')\n    try:\n        restore_snapshot(vm_uuid, snapshot)\n\n        set_network_to_hostonly(vm_uuid)\n\n        # Start the VM to ensure everything is good\n        print(f\"VM {vm_uuid} 🔄 power cycling before export{LONG_WAIT}\")\n        ensure_vm_running(vm_uuid)\n        export_vm(vm_uuid, snapshot, description, export_dir_name)\n    except Exception as e:\n        print(f'VM {vm_uuid} ❌ ERROR exporting \"{snapshot}\": {e}\\n')\n\n\ndef main(argv=None):\n    if argv is None:\n        argv = sys.argv[1:]\n\n    parser = argparse.ArgumentParser(\n        description=DESCRIPTION,\n        epilog=EPILOG,\n        formatter_class=argparse.RawDescriptionHelpFormatter,\n    )\n    parser.add_argument(\"vm_name\", help=\"name of the VM to export a snapshot from.\")\n    parser.add_argument(\"snapshot\", help=\"name of the snapshot to export.\")\n    parser.add_argument(\"--description\", help=\"description of the exported OVA. Empty by default.\")\n    parser.add_argument(\n        \"--export_dir_name\",\n        help=f\"name of the directory in HOME to export the VMs The directory is created if it does not exist. Default: {EXPORT_DIR_NAME}\",\n    )\n    args = parser.parse_args(args=argv)\n\n    export_snapshot(args.vm_name, args.snapshot, args.description, args.export_dir_name)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "virtualbox/vboxcommon.py",
    "content": "# Copyright 2024 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport hashlib\nimport os\nimport re\nimport subprocess\nimport sys\nimport time\nfrom datetime import datetime\n\n# Message to add to the output when waiting for a long operation to complete.\nLONG_WAIT = \"... (it will take some time, go for an 🍦!)\"\n\n# Default name of the directory in HOME to export VMs to\nEXPORT_DIR_NAME = \"EXPORTED VMS\"\n\n\ndef format_arg(arg):\n    \"\"\"Add quotes to the string arg if it contains special characters like spaces.\"\"\"\n    if any(c in arg for c in (\" \", \"\\\\\", \"/\")):\n        if \"'\" not in arg:\n            return f\"'{arg}'\"\n        if '\"' not in arg:\n            return f'\"{arg}\"'\n    return arg\n\n\ndef cmd_to_str(cmd):\n    \"\"\"Convert a list of string arguments to a string.\"\"\"\n    return \" \".join(format_arg(arg) for arg in cmd)\n\n\ndef __run_vboxmanage(cmd, real_time=False):\n    \"\"\"Run a command using 'subprocess.run' and return the output.\n\n    Args:\n        cmd: list with the command and its arguments\n        real_time: Boolean that determines if displaying the output in realtime or returning it.\n    \"\"\"\n    # When running as a PyInstaller bundle, LD_LIBRARY_PATH is set,\n    # which can cause conflicts with external binaries like VBoxManage.\n    # We create a clean environment for the subprocess to use the system's libraries.\n    env = os.environ.copy()\n    if getattr(sys, \"frozen\", False) and \"LD_LIBRARY_PATH\" in env:\n        # 'sys.frozen' is True when running from a PyInstaller executable.\n        # We can either remove the variable or, more safely, restore the original\n        # one if PyInstaller saved it. PyInstaller often saves it as LD_LIBRARY_PATH_ORIG.\n        if \"LD_LIBRARY_PATH_ORIG\" in env:\n            env[\"LD_LIBRARY_PATH\"] = env[\"LD_LIBRARY_PATH_ORIG\"]\n        else:\n            del env[\"LD_LIBRARY_PATH\"]\n\n    if real_time:\n        return subprocess.run(cmd, stderr=sys.stderr, stdout=sys.stdout, env=env)\n    else:\n        return subprocess.run(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env)\n\n\ndef run_vboxmanage(cmd, real_time=False):\n    \"\"\"Run a VBoxManage command and return the output.\n\n    Args:\n        cmd: list of string arguments to pass to VBoxManage\n        real_time: Boolean that determines if displaying the output in realtime or returning it.\n    \"\"\"\n    cmd = [\"VBoxManage\"] + cmd\n    result = __run_vboxmanage(cmd, real_time)\n\n    if result.returncode:\n        # Check if we are affect by the following VERR_NO_LOW_MEMORY bug: https://www.virtualbox.org/ticket/22185\n        # and re-run the command every minute until the VERR_NO_LOW_MEMORY error is resolved\n        while result.stdout and \"VERR_NO_LOW_MEMORY\" in result.stdout:\n            print(\"❌ VirtualBox VERR_NO_LOW_MEMORY error (likely https://www.virtualbox.org/ticket/22185)\")\n            print(\"🩹 Fit it running 'echo 3 | sudo tee /proc/sys/vm/drop_caches'\")\n            print(\"⏳ I'll re-try the command in ~ 1 minute\\n\")\n            time.sleep(60)  # wait 1 minutes\n\n            # Re-try command\n            result = __run_vboxmanage(cmd, real_time)\n\n    if result.returncode:\n        error = f\"Command '{cmd_to_str(cmd)}' failed\"\n        # Use only the first \"VBoxManage: error:\" line to prevent using the long\n        # VBoxManage help message or noisy information like the details and context.\n        if result.stdout:\n            match = re.search(\"^VBoxManage: error: (?P<err_info>.*)\", result.stdout, flags=re.M)\n            if match:\n                error += f\": {match['err_info']}\"\n        raise RuntimeError(error)\n\n    return result.stdout\n\n\ndef control_guest(vm_uuid, user, password, args, real_time=False):\n    \"\"\"Run a 'VBoxManage guestcontrol' command providing the username and password.\n    Args:\n        vm_uuid: VM UUID\n        args: list of arguments starting with the guestcontrol sub-command\n        real_time: Boolean that determines if displaying the output in realtime or returning it.\n    \"\"\"\n    # VM must be running to control the guest\n    ensure_vm_running(vm_uuid)\n    cmd = [\"guestcontrol\", vm_uuid, f\"--username={user}\", f\"--password={password}\"] + args\n    try:\n        return run_vboxmanage(cmd, real_time)\n    except RuntimeError:\n        # The guest additions take a bit to load after the user is logged in\n        # In slow environments this may cause the command to fail, wait a bit and re-try\n        time.sleep(120)  # Wait 2 minutes\n        return run_vboxmanage(cmd, real_time)\n\n\ndef get_hostonlyif_name():\n    \"\"\"Get the name of the host-only interface. Return None if there is no host-only interface\"\"\"\n    # Example of `VBoxManage list hostonlyifs` relevant output:\n    # Name:            vboxnet0\n    hostonlyifs_info = run_vboxmanage([\"list\", \"hostonlyifs\"])\n\n    match = re.search(r\"^Name: *(?P<hostonlyif_name>\\S+)\", hostonlyifs_info, flags=re.M)\n    if match:\n        return match[\"hostonlyif_name\"]\n\n\ndef ensure_hostonlyif_exists():\n    \"\"\"Get the name of the host-only interface. Create the interface if it doesn't exist.\"\"\"\n    hostonlyif_name = get_hostonlyif_name()\n\n    if not hostonlyif_name:\n        # No host-only interface found, create one\n        run_vboxmanage([\"hostonlyif\", \"create\"])\n\n        hostonlyif_name = get_hostonlyif_name()\n        if not hostonlyif_name:\n            raise RuntimeError(\"Failed to create new hostonly interface.\")\n\n        print(f\"Hostonly interface created: {hostonlyif_name}\")\n\n    return hostonlyif_name\n\n\ndef set_network_to_hostonly(vm_uuid):\n    \"\"\"Set the NIC 1 to hostonly and disable the rest.\"\"\"\n    # VM must be shutdown before changing the adapters\n    ensure_vm_shutdown(vm_uuid)\n\n    # Ensure a hostonly interface exists to prevent issues starting the VM\n    ensure_hostonlyif_exists()\n\n    # Example of `VBoxManage showvminfo <VM_UUID> --machinereadable` relevant output:\n    # nic1=\"none\"\n    # bridgeadapter2=\"wlp9s0\"\n    # macaddress2=\"0800271DDA9D\"\n    # cableconnected2=\"on\"\n    # nic2=\"bridged\"\n    # nictype2=\"82540EM\"\n    # nicspeed2=\"0\"\n    # nic3=\"none\"\n    # nic4=\"none\"\n    # nic5=\"none\"\n    # nic6=\"none\"\n    # nic7=\"none\"\n    # nic8=\"none\"\n    vm_info = run_vboxmanage([\"showvminfo\", vm_uuid, \"--machinereadable\"])\n\n    # Set all NICs to none to avoid running into strange situations\n    for nic_number, nic_value in re.findall(r'^nic(\\d+)=\"(\\S+)\"', vm_info, flags=re.M):\n        if nic_value != \"none\":  # Ignore NICs that are already none\n            run_vboxmanage([\"modifyvm\", vm_uuid, f\"--nic{nic_number}\", \"none\"])\n\n    # Set NIC 1 to hostonly\n    run_vboxmanage([\"modifyvm\", vm_uuid, \"--nic1\", \"hostonly\"])\n\n    # Ensure changes applied\n    vm_info = run_vboxmanage([\"showvminfo\", vm_uuid, \"--machinereadable\"])\n    nic_values = re.findall(r'^nic\\d+=\"(\\S+)\"', vm_info, flags=re.M)\n    if nic_values[0] != \"hostonly\" or any(nic_value != \"none\" for nic_value in nic_values[1:]):\n        raise RuntimeError(f\"Unable to change NICs to a single hostonly in VM {vm_uuid}\")\n\n    print(f\"VM {vm_uuid} ⚙️  network set to single hostonly adapter\")\n\n\ndef sha256_file(filepath):\n    \"\"\"Return the SHA256 of the content of the file provided as argument.\"\"\"\n    with open(filepath, \"rb\") as f:\n        return hashlib.file_digest(f, \"sha256\").hexdigest()\n\n\ndef export_vm(vm_uuid, exported_vm_name, description=\"\", export_dir_name=EXPORT_DIR_NAME):\n    \"\"\"Export VM as OVA and generate a file with the SHA256 of the exported OVA.\"\"\"\n    # Create export directory\n    export_directory = os.path.expanduser(f\"~/{export_dir_name}\")\n    os.makedirs(export_directory, exist_ok=True)\n\n    exported_ova_filepath = os.path.join(export_directory, f\"{exported_vm_name}.ova\")\n\n    # Rename OVA if it already exists (for example if the script is called twice) or exporting will fail\n    if os.path.exists(exported_ova_filepath):\n        time_str = datetime.now().strftime(\"%H_%M\")\n        old_ova_filepath = os.path.join(export_directory, f\"{exported_vm_name}.{time_str}.ova\")\n        os.rename(exported_ova_filepath, old_ova_filepath)\n        print(f\"⚠️  Renamed old OVA to export new one: {old_ova_filepath}\")\n\n    # Turn off VM and export it to .ova\n    ensure_vm_shutdown(vm_uuid)\n    print(f\"VM {vm_uuid} 🚧 exporting {LONG_WAIT}\")\n    run_vboxmanage(\n        [\n            \"export\",\n            vm_uuid,\n            f\"--output={exported_ova_filepath}\",\n            \"--vsys=0\",  # We need to specify the index of the VM, 0 as we only export 1 VM\n            f\"--vmname={exported_vm_name}\",\n            f\"--description={description}\",\n        ]\n    )\n    print(f'VM {vm_uuid} ✅ EXPORTED \"{exported_ova_filepath}\"')\n\n    # Generate file with SHA256\n    sha256 = sha256_file(exported_ova_filepath)\n    sha256_filepath = f\"{exported_ova_filepath}.sha256\"\n    with open(sha256_filepath, \"w\") as f:\n        f.write(sha256)\n\n    print(f'VM {vm_uuid} ✅ GENERATED \"{sha256_filepath}\": {sha256}\\n')\n\n\ndef get_vm_uuid(vm_name):\n    \"\"\"Get the machine UUID for a given VM name using 'VBoxManage list vms'. Return None if not found.\"\"\"\n    # regex VM name and extract the GUID\n    # Example of `VBoxManage list vms` output:\n    # \"FLARE-VM.testing\" {b76d628b-737f-40a3-9a16-c5f66ad2cfcc}\n    # \"FLARE-VM\" {a23c0c37-2062-4cf0-882b-9e9747dd33b6}\n    vms_info = run_vboxmanage([\"list\", \"vms\"])\n\n    match = re.search(rf'^\"{vm_name}\" (?P<uuid>\\{{.*?\\}})', vms_info, flags=re.M)\n    if match:\n        return match.group(\"uuid\")\n\n\ndef get_vm_state(vm_uuid):\n    \"\"\"Get the VM state using 'VBoxManage showvminfo'.\"\"\"\n    # Example of `VBoxManage showvminfo <VM_UUID> --machinereadable` relevant output:\n    # VMState=\"poweroff\"\n    vm_info = run_vboxmanage([\"showvminfo\", vm_uuid, \"--machinereadable\"])\n\n    match = re.search(r'^VMState=\"(?P<state>\\S+)\"', vm_info, flags=re.M)\n    if match:\n        return match[\"state\"]\n\n    raise Exception(f\"Unable to get state of VM {vm_uuid}\")\n\n\ndef get_num_logged_in_users(vm_uuid):\n    \"\"\"Return the number of logged in users using 'VBoxManage guestproperty'.\"\"\"\n    # Examples of 'VBoxManage guestproperty get <VM_UUID> \"/VirtualBox/GuestInfo/OS/LoggedInUsers\"' output:\n    # - 'Value: 1'\n    # - 'Value: 0'\n    # - 'No value set!'\n    logged_in_users_info = run_vboxmanage([\"guestproperty\", \"get\", vm_uuid, \"/VirtualBox/GuestInfo/OS/LoggedInUsers\"])\n\n    if logged_in_users_info:\n        match = re.search(r\"^Value: (?P<logged_in_users>\\d+)\", logged_in_users_info)\n        if match:\n            return int(match[\"logged_in_users\"])\n    return 0\n\n\ndef wait_until(vm_uuid, condition):\n    \"\"\"Wait for VM to verify a condition\n\n    Return True if the condition is met within one minute.\n    Return False otherwise.\n    \"\"\"\n    timeout = 600  # seconds (10 minutes)\n    check_interval = 5  # seconds\n    start_time = time.time()\n    while time.time() - start_time < timeout:\n        if eval(condition):\n            time.sleep(5)  # wait a bit to be careful and avoid any weird races\n            return True\n        time.sleep(check_interval)\n    return False\n\n\ndef ensure_vm_running(vm_uuid):\n    \"\"\"Start the VM if its state is not 'running' and ensure the user is logged in.\"\"\"\n    vm_state = get_vm_state(vm_uuid)\n    if not vm_state == \"running\":\n        print(f\"VM {vm_uuid} state: {vm_state}. Starting VM...\")\n        run_vboxmanage([\"startvm\", vm_uuid, \"--type\", \"gui\"])\n\n    # Wait until at least 1 user is logged in.\n    if not wait_until(vm_uuid, \"get_num_logged_in_users(vm_uuid)\"):\n        raise RuntimeError(f\"Unable to start VM {vm_uuid}.\")\n\n\ndef ensure_vm_shutdown(vm_uuid):\n    \"\"\"Shut down the VM if its state is not 'poweroff'. If the VM status is 'saved' start it before shutting it down.\"\"\"\n    vm_state = get_vm_state(vm_uuid)\n    if vm_state == \"poweroff\":\n        return\n\n    # If the state is aborted, the VM is not running and can't be turned off\n    # Log the state and return\n    if vm_state in (\"aborted-saved\", \"aborted\"):\n        print(f\"VM {vm_uuid} state: {vm_state}\")\n        return\n\n    if vm_state == \"saved\":\n        ensure_vm_running(vm_uuid)\n        vm_state = get_vm_state(vm_uuid)\n\n    print(f\"VM {vm_uuid} state: {vm_state}. Shutting down VM...\")\n    run_vboxmanage([\"controlvm\", vm_uuid, \"poweroff\"])\n\n    if not wait_until(vm_uuid, \"get_vm_state(vm_uuid) == 'poweroff'\"):\n        raise RuntimeError(f\"Unable to shutdown VM {vm_uuid}.\")\n\n\ndef restore_snapshot(vm_uuid, snapshot_name):\n    \"\"\"Restore a given snapshot in the given VM.\"\"\"\n    # VM must be shutdown before restoring snapshot\n    ensure_vm_shutdown(vm_uuid)\n\n    run_vboxmanage([\"snapshot\", vm_uuid, \"restore\", snapshot_name])\n    print(f'VM {vm_uuid} ✨ restored snapshot \"{snapshot_name}\"')\n\n\ndef rename_old_snapshot(vm_uuid, snapshot_name):\n    \"\"\"Append 'OLD' to the name of all snapshots with the given name within the specified VM.\n\n    Args:\n        vm_uuid: VM UUID\n        snapshot_name: The current name of the snapshot(s) to rename.\n    \"\"\"\n    # Example of 'VBoxManage snapshot VM_NAME list --machinereadable' output:\n    # SnapshotName=\"ROOT\"\n    # SnapshotUUID=\"86b38fc9-9d68-4e4b-a033-4075002ab570\"\n    # SnapshotName-1=\"Snapshot 1\"\n    # SnapshotUUID-1=\"e383e702-fee3-4e0b-b1e0-f3b869dbcaea\"\n    snapshots_info = run_vboxmanage([\"snapshot\", vm_uuid, \"list\", \"--machinereadable\"])\n\n    # Find how many snapshots have the given name and edit a snapshot with that name as many times\n    snapshots = re.findall(rf'^SnapshotName(-\\d+)*=\"{snapshot_name}\"\\n', snapshots_info, flags=re.M)\n    for _ in range(len(snapshots)):\n        run_vboxmanage([\"snapshot\", vm_uuid, \"edit\", snapshot_name, f\"--name='{snapshot_name} OLD\"])\n\n\ndef take_snapshot(vm_uuid, snapshot_name, shutdown=False, rename=False):\n    \"\"\"Take a snapshot of the specified VM with the given name, optionally shutting down first and renaming duplicates.\n\n    Args:\n        vm_uuid: VM UUID\n        snapshot_name: The name for the new snapshot.\n        shutdown: If True, shut down the VM before taking the snapshot.\n        rename: If True, renames any existing snapshots with the same `snapshot_name`\n                by appending ' OLD' to their names before taking the new snapshot.\n    \"\"\"\n    if shutdown:\n        ensure_vm_shutdown(vm_uuid)\n\n    if rename:\n        rename_old_snapshot(vm_uuid, snapshot_name)\n\n    run_vboxmanage([\"snapshot\", vm_uuid, \"take\", snapshot_name])\n    print(f'VM {vm_uuid} 📷 took snapshot \"{snapshot_name}\"')\n"
  }
]