├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yml ├── LICENSE ├── LICENSE-BSD ├── Makefile ├── README.md ├── artifacts ├── artifactcollector.go ├── artifactcollector_test.go ├── artifactdefinition.go ├── decoding.go ├── decoding_test.go ├── expansion.go ├── expansion_test.go ├── expansion_unix_test.go ├── expansion_windows_test.go ├── filter.go └── filter_test.go ├── assets ├── artifacts.generated.go ├── bin.generated.go └── config.generated.go ├── build ├── bin │ └── .keep ├── go │ ├── context │ │ └── context.go │ └── fs │ │ ├── fs.go │ │ ├── readdir.go │ │ ├── readfile.go │ │ └── stat.go ├── win │ ├── artifactcollector.exe.manifest │ ├── artifactcollector.exe.user.manifest │ ├── artifactcollector.ico │ ├── artifactcollector32.exe.manifest │ └── artifactcollector32.exe.user.manifest ├── win2k │ └── Dockerfile └── winxp │ └── Dockerfile ├── collect ├── collect.go ├── collect_test.go ├── createstore.go ├── log.go └── run.go ├── collector ├── collector.go ├── collector_test.go ├── configuration.go ├── file.go ├── file_test.go ├── process.go ├── process_test.go ├── registry_windows.go ├── registrydummy_unix.go ├── resolve.go ├── resolve_test.go ├── types.go ├── wmi.go ├── wmi_unix.go ├── wmi_windows.go └── wmi_windows_test.go ├── config ├── ac.yaml └── artifacts │ ├── README.md │ ├── collections.yaml │ ├── linux.yaml │ ├── macos.yaml │ ├── style_guide.md │ ├── webbrowser.yaml │ ├── windows.yaml │ ├── windows_logs.yaml │ ├── windows_persistence.yaml │ └── windows_usb.yaml ├── docs └── ac.png ├── doublestar ├── LICENSE ├── README.md ├── doublestar.go ├── doublestar_test.go └── example_test.go ├── go.mod ├── go.sum ├── main.go ├── store ├── aczip │ ├── example_test.go │ ├── extra.go │ ├── extra_test.go │ ├── struct.go │ ├── testdata │ │ └── readme.zip │ ├── writer.go │ ├── writer_test.go │ └── zip_test.go └── zipstore.go ├── test └── artifacts │ ├── collect_1.yaml │ ├── collect_2.yaml │ ├── collect_3.yaml │ ├── collect_4.yaml │ ├── collect_5.yaml │ ├── collect_6.yaml │ ├── invalid │ ├── artifact_os.yaml │ ├── attributes_1.yaml │ ├── attributes_10.yaml │ ├── attributes_11.yaml │ ├── attributes_12.yaml │ ├── attributes_13.yaml │ ├── attributes_14.yaml │ ├── attributes_15.yaml │ ├── attributes_16.yaml │ ├── attributes_2.yaml │ ├── attributes_3.yaml │ ├── attributes_4.yaml │ ├── attributes_5.yaml │ ├── attributes_6.yaml │ ├── attributes_7.yaml │ ├── attributes_8.yaml │ ├── attributes_9.yaml │ ├── custom.yaml │ ├── deprecated_vars.yaml │ ├── doc_long.yaml │ ├── ending.yml │ ├── file_1.yaml │ ├── file_2.yaml │ ├── file_3.yaml │ ├── group_member_exist.yaml │ ├── linux_name_prefix_1.yaml │ ├── mac_os_double_path_1.yaml │ ├── mac_os_double_path_2.yaml │ ├── macos_name_prefix_2.yaml │ ├── name_case_1.yaml │ ├── name_case_2.yaml │ ├── name_type_suffix_1.yaml │ ├── name_type_suffix_2.yaml │ ├── name_unique.yaml │ ├── no_cycles_1.yaml │ ├── no_cycles_2.yaml │ ├── no_windows_homedir.yaml │ ├── not_provided_1.yaml │ ├── not_provided_2.yaml │ ├── registry_current_control_set_1.yaml │ ├── registry_current_control_set_2.yaml │ ├── registry_hkey_current_user_1.yaml │ ├── registry_hkey_current_user_2.yaml │ ├── registry_key_unique.yaml │ ├── registry_value_unique.yaml │ ├── source_os.yaml │ ├── source_type.yaml │ ├── windows_name_prefix_3.yaml │ ├── windows_os_specific_1.yaml │ └── windows_os_specific_2.yaml │ ├── linux_test.yaml │ ├── macos_test.yaml │ ├── valid │ ├── double_star.yaml │ ├── mac_os_double_path_3.yaml │ ├── mac_os_double_path_4.yaml │ ├── name_type_suffix_3.yaml │ ├── processing.yaml │ └── valid.yaml │ └── windows_test.yaml └── tools ├── artifactvalidator ├── main.go ├── main_test.go ├── validator.go └── validator_test.go ├── go.mod ├── go.sum ├── resources ├── LICENSE ├── README.md ├── cmd.go ├── resources.go ├── resources_test.go └── testdata │ ├── 12.bin │ ├── 123.bin │ ├── patrick.txt │ ├── query.sql │ └── test.txt └── yaml2go └── main.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | release: { types: [ published ] } 7 | 8 | jobs: 9 | fmt: 10 | name: fmt 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/setup-go@v5 14 | with: { go-version: '1.23' } 15 | - uses: actions/checkout@v4 16 | - run: make install generate-win fmt_linux 17 | - run: git diff --exit-code 18 | 19 | validate: 20 | name: validate 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/setup-go@v5 24 | with: { go-version: '1.23' } 25 | - uses: actions/checkout@v4 26 | - run: make validate 27 | 28 | lint: 29 | name: lint 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/setup-go@v5 33 | with: { go-version: '1.23' } 34 | - uses: actions/checkout@v4 35 | - uses: golangci/golangci-lint-action@v6 36 | with: { version: 'v1.61' } 37 | 38 | test: 39 | name: test 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | matrix: 43 | os: [ macos-latest, ubuntu-latest, windows-latest ] 44 | fail-fast: false 45 | defaults: { run: { shell: bash } } 46 | steps: 47 | - uses: actions/setup-go@v5 48 | with: { go-version: '1.23' } 49 | - uses: actions/checkout@v4 50 | - run: make test 51 | 52 | backwards-compatibility: 53 | name: backwards-compatibility 54 | runs-on: windows-latest 55 | defaults: { run: { shell: bash } } 56 | steps: 57 | - uses: actions/setup-go@v5 58 | with: { go-version: '1.23' } 59 | - uses: actions/checkout@v4 60 | with: { path: repo } 61 | - run: go mod vendor 62 | working-directory: repo 63 | - name: Move packages 64 | run: | 65 | mkdir -p path 66 | mv repo/vendor path/src 67 | mkdir -p path/src/github.com/forensicanalysis 68 | mv repo/build/go/context path/src 69 | mkdir -p path/src/io 70 | mv repo/build/go/fs path/src/io 71 | mv repo path/src/github.com/forensicanalysis/artifactcollector 72 | find path 73 | - name: Build with go 1.9.7 74 | run: | 75 | mkdir -p go1.9.7 76 | curl -Lso go1.9.7.windows-amd64.zip https://golang.org/dl/go1.9.7.windows-amd64.zip 77 | unzip -q go1.9.7.windows-amd64.zip -d go1.9.7 78 | GOPATH=$GITHUB_WORKSPACE/path GOROOT=$GITHUB_WORKSPACE/go1.9.7/go go1.9.7/go/bin/go build -o artifactcollectorxp.exe github.com/forensicanalysis/artifactcollector 79 | - name: Build with go 1.2.2 80 | run: | 81 | mkdir -p go1.2.2 82 | curl -Lso go1.2.2.windows-amd64.zip https://golang.org/dl/go1.2.2.windows-amd64.zip 83 | unzip -q go1.2.2.windows-amd64.zip -d go1.2.2 84 | GOPATH=$GITHUB_WORKSPACE/path GOROOT=$GITHUB_WORKSPACE/go1.2.2/go go1.2.2/go/bin/go build -o artifactcollector2k.exe github.com/forensicanalysis/artifactcollector 85 | 86 | 87 | linux: 88 | name: artifactcollector (linux) 89 | runs-on: ubuntu-latest 90 | steps: 91 | - uses: actions/setup-go@v5 92 | with: { go-version: '1.23' } 93 | - uses: actions/checkout@v4 94 | 95 | - run: go build . 96 | 97 | - run: zip -r linux.zip artifactcollector 98 | - uses: actions/upload-artifact@v4 99 | with: 100 | name: artifactcollector (linux) 101 | path: linux.zip 102 | - uses: softprops/action-gh-release@v2 103 | with: 104 | files: linux.zip 105 | if: github.event_name == 'release' 106 | 107 | - run: sudo ./artifactcollector 108 | 109 | - name: move files 110 | run: | 111 | mkdir output 112 | mv *.zip output 113 | 114 | - uses: actions/upload-artifact@v4 115 | with: 116 | name: output linux 117 | path: output 118 | 119 | macos: 120 | name: artifactcollector (macos) 121 | runs-on: macos-latest 122 | steps: 123 | - uses: actions/setup-go@v5 124 | with: { go-version: '1.23' } 125 | - uses: actions/checkout@v4 126 | 127 | - run: go build . 128 | - run: zip -r macos.zip artifactcollector 129 | 130 | - uses: actions/upload-artifact@v4 131 | with: 132 | name: artifactcollector (macos) 133 | path: macos.zip 134 | - uses: softprops/action-gh-release@v2 135 | with: 136 | files: macos.zip 137 | if: github.event_name == 'release' 138 | 139 | - run: sudo ./artifactcollector 140 | 141 | - name: move files 142 | run: | 143 | mkdir output 144 | mv *.zip output 145 | 146 | - uses: actions/upload-artifact@v4 147 | with: 148 | name: output macos 149 | path: output 150 | 151 | win: 152 | name: artifactcollector (win) 153 | runs-on: windows-latest 154 | defaults: 155 | run: 156 | shell: bash 157 | steps: 158 | - uses: actions/setup-go@v5 159 | with: { go-version: '1.23' } 160 | - uses: actions/checkout@v4 161 | 162 | - run: make build-win 163 | - run: 7z a windows.zip ./build/bin/artifactcollector.exe 164 | 165 | - uses: actions/upload-artifact@v4 166 | with: 167 | name: artifactcollector (windows) 168 | path: windows.zip 169 | 170 | - uses: softprops/action-gh-release@v2 171 | with: 172 | files: windows.zip 173 | if: github.event_name == 'release' 174 | 175 | - run: ./build/bin/artifactcollector.exe 176 | 177 | - name: move files 178 | run: | 179 | mkdir output 180 | mv *.zip output 181 | 182 | - uses: actions/upload-artifact@v4 183 | with: 184 | name: output windows 185 | path: output 186 | 187 | win2k: 188 | name: artifactcollector (win2k) 189 | runs-on: ubuntu-latest 190 | steps: 191 | - uses: actions/setup-go@v5 192 | with: { go-version: '1.23' } 193 | - uses: actions/checkout@v4 194 | 195 | - run: make build-win2k 196 | - run: mv ./build/bin/artifactcollector2k.exe ./artifactcollector2k.exe 197 | - run: zip -r win2k.zip ./artifactcollector2k.exe 198 | 199 | - uses: actions/upload-artifact@v4 200 | with: 201 | name: artifactcollector (Windows 2000, 32bit) 202 | path: win2k.zip 203 | 204 | - uses: softprops/action-gh-release@v2 205 | with: 206 | files: win2k.zip 207 | if: github.event_name == 'release' 208 | 209 | winxp: 210 | name: artifactcollector (winxp) 211 | runs-on: ubuntu-latest 212 | steps: 213 | - uses: actions/setup-go@v5 214 | with: { go-version: '1.23' } 215 | - uses: actions/checkout@v4 216 | 217 | - run: make build-winxp 218 | - run: mv ./build/bin/artifactcollectorxp.exe ./artifactcollectorxp.exe 219 | - run: zip -r winxp.zip ./artifactcollectorxp.exe 220 | 221 | - uses: actions/upload-artifact@v4 222 | with: 223 | name: artifactcollector (Windows XP, 32bit) 224 | path: winxp.zip 225 | 226 | - uses: softprops/action-gh-release@v2 227 | with: 228 | files: winxp.zip 229 | if: github.event_name == 'release' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # go 2 | vendor 3 | 4 | # binaries 5 | artifactcollector 6 | artifactcollector.exe 7 | *.syso 8 | build/bin/* 9 | !build/bin/.keep 10 | 11 | # store 12 | ac 13 | *.log 14 | collect/*.log 15 | collect/*.zip 16 | 17 | # test 18 | coverage.out 19 | .benchmarks -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 3m 3 | tests: false 4 | 5 | linters: 6 | enable-all: true 7 | disable: 8 | - depguard 9 | - err113 10 | - errcheck 11 | - errorlint 12 | - exhaustruct 13 | - forbidigo 14 | - gochecknoglobals 15 | - godox 16 | - gofumpt 17 | - gomnd 18 | - gosec 19 | - ireturn 20 | - lll 21 | - mnd 22 | - nilnil 23 | - nonamedreturns 24 | - perfsprint 25 | - prealloc 26 | - staticcheck 27 | - tagliatelle 28 | - varnamelen 29 | - wrapcheck 30 | 31 | linters-settings: 32 | gci: 33 | sections: 34 | - standard 35 | - default 36 | - prefix(github.com/forensicanalysis/artifactcollector) 37 | 38 | issues: 39 | exclude: 40 | - block should not end with a whitespace 41 | - strings.ReplaceAll 42 | - error strings should not be capitalized 43 | exclude-dirs: 44 | - build/go 45 | - store/aczip 46 | exclude-files: 47 | - ".*\\.generated\\.*." -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Siemens AG 4 | Copyright (c) 2020-2025 Jonas Plum 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009 The Go Authors. All rights reserved. 2 | Redistribution and use in source and binary forms, with or without 3 | modification, are permitted provided that the following conditions are 4 | met: 5 | * Redistributions of source code must retain the above copyright 6 | notice, this list of conditions and the following disclaimer. 7 | * Redistributions in binary form must reproduce the above 8 | copyright notice, this list of conditions and the following disclaimer 9 | in the documentation and/or other materials provided with the 10 | distribution. 11 | * Neither the name of Google Inc. nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install 2 | install: 3 | @echo "Installing..." 4 | go install github.com/bombsimon/wsl/v4/cmd...@v4.4.1 5 | go install mvdan.cc/gofumpt@v0.6.0 6 | go install github.com/daixiang0/gci@v0.13.4 7 | 8 | .PHONY: fmt_darwin 9 | fmt_darwin: 10 | @echo "Formatting..." 11 | go mod tidy 12 | go fmt ./... 13 | gci write -s standard -s default -s "prefix(github.com/forensicanalysis/artifactcollector)" . 14 | gofumpt -l -w . 15 | find . -type f -name "*.go" -print0 | xargs -0 sed -i '' -e 's/ 0o/ 0/g' 16 | wsl -fix ./... || true 17 | 18 | .PHONY: fmt_linux 19 | fmt_linux: 20 | @echo "Formatting..." 21 | go mod tidy 22 | go fmt ./... 23 | gci write -s standard -s default -s "prefix(github.com/forensicanalysis/artifactcollector)" . 24 | gofumpt -l -w . 25 | find . -type f -name "*.go" -print0 | xargs -0 sed -i -e 's/ 0o/ 0/g' 26 | wsl -fix ./... || true 27 | 28 | .PHONY: vendor 29 | vendor: 30 | @echo "Vendoring..." 31 | go mod tidy 32 | go mod vendor 33 | 34 | .PHONY: lint 35 | lint: 36 | @echo "Linting..." 37 | golangci-lint version 38 | golangci-lint run --config .golangci.yml ./... 39 | 40 | .PHONY: test 41 | test: 42 | @echo "Testing..." 43 | go test -v ./... 44 | 45 | .PHONY: test-coverage 46 | test-coverage: 47 | @echo "Testing with coverage..." 48 | go test -coverpkg=./... -coverprofile=coverage.out -count 1 ./... 49 | go tool cover -func=coverage.out 50 | go tool cover -html=coverage.out 51 | 52 | .PHONY: validate 53 | validate: 54 | @echo "Validating..." 55 | cd tools/artifactvalidator && go build -o ../../build/bin/artifactvalidator . 56 | ./build/bin/artifactvalidator -entrypoints=DefaultCollection1 config/artifacts/*.yaml 57 | 58 | .PHONY: generate 59 | generate: 60 | @echo "Generating..." 61 | go install golang.org/x/tools/cmd/goimports@v0.1.7 62 | cd tools/yaml2go && go build -o ../../build/bin/yaml2go . 63 | ./build/bin/yaml2go config/ac.yaml config/artifacts/*.yaml 64 | cd tools/resources && go build -o ../../build/bin/resources . 65 | ./build/bin/resources -package assets -output assets/bin.generated.go config/bin/* 66 | 67 | .PHONY: generate-win 68 | generate-win: generate 69 | @echo "Generating for Windows..." 70 | go install github.com/akavel/rsrc@v0.10.2 71 | rsrc -arch amd64 -manifest build/win/artifactcollector.exe.manifest -ico build/win/artifactcollector.ico -o build/win/artifactcollector.syso 72 | rsrc -arch 386 -manifest build/win/artifactcollector32.exe.manifest -ico build/win/artifactcollector.ico -o build/win/artifactcollector32.syso 73 | rsrc -arch amd64 -manifest build/win/artifactcollector.exe.user.manifest -ico build/win/artifactcollector.ico -o build/win/artifactcollector.user.syso 74 | rsrc -arch 386 -manifest build/win/artifactcollector32.exe.user.manifest -ico build/win/artifactcollector.ico -o build/win/artifactcollector32.user.syso 75 | 76 | .PHONY: build 77 | build: generate 78 | @echo "Building..." 79 | go build -o build/bin/artifactcollector . 80 | 81 | .PHONY: build-win 82 | build-win: generate-win 83 | @echo "Building for Windows..." 84 | mv build/win/artifactcollector.syso artifactcollector.syso 85 | GOOS=windows GOARCH=amd64 go build -o build/bin/artifactcollector.exe . 86 | rm artifactcollector.syso 87 | 88 | .PHONY: build-win2k 89 | build-win2k: vendor 90 | @echo "Building for Windows 2000..." 91 | docker build -t artifactcollector-win2k -f build/win2k/Dockerfile . 92 | docker run --rm -v $(shell pwd)/build/bin:/build artifactcollector-win2k 93 | 94 | .PHONY: build-winxp 95 | build-winxp: vendor 96 | @echo "Building for Windows XP..." 97 | docker build -t artifactcollector-winxp -f build/winxp/Dockerfile . 98 | docker run --rm -v $(shell pwd)/build/bin:/build artifactcollector-winxp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

artifactcollector

2 | 3 |

4 | doc 5 |

6 | 7 | The artifactcollector is a tool to collect forensic artifacts on a system. 8 | It can be used in forensic investigations to extract specific data instead of creating full disk images. 9 | The artifactextractor can collect low-level (like $MFT) 10 | and high-level file artifacts as well as registry keys (e.g. run keys) 11 | which can then be used in forensic investigations. 12 | 13 | ![Running the artifactextractor on Windows.](docs/ac.png) 14 | _Running the artifactextractor on Windows._ 15 | 16 | The artifactcollector is a single binary that can be transferred to computers 17 | which are part of a forensic investigation. 18 | 19 | ## Features 20 | 21 | The artifactcollector offers the following features 22 | 23 | - ️🖥️ Runs on **Windows**, **Linux** and **macOS** 24 | - 🏛️ Supports also old Windows versions like **Windows 2000** or **Windows XP** 25 | - 🛍️ Can extract **files**, **directories**, **registry entries**, **command output**, and **WMI output** 26 | - ⭐ Uses the configurable and extensible [**Forensics Artifacts**](https://github.com/forensicanalysis/artifacts) 27 | - 🧩 Can run additional **embedded executables** 28 | - 🕊️ **Open source** 29 | 30 | ## Download 31 | 32 | All releases of the artifactcollector can be downloaded from [Releases](https://github.com/forensicanalysis/artifactcollector/releases). 33 | Prebuild artifactcollectors for Windows, Linux and macOS are available. 34 | Those artifactcollectors collect a predefined set of artifacts which are mostly taken from the Sans FOR500 training. 35 | Sans provides a comprehensive [poster](https://www.sans.org/security-resources/posters/windows-forensic-analysis/170/download) 36 | explaining those artifacts. 37 | 38 | ## Usage 39 | 40 | > [!WARNING] 41 | > The artifactcollector behaves similar to malware as it collects critical system files 42 | > and might be detected as a virus or malware. 43 | 44 | On Windows the `artifactcollector.exe` can be executed by double-clicking it on the investigated machine. 45 | The user will be provided with a [UAC prompt](https://en.wikipedia.org/wiki/User_Account_Control) because the 46 | artifactcollector required administrator rights to run. 47 | The collection takes some minutes, depending on processing power and contained artifacts. 48 | 49 | On Linux and macOS the `artifactcollector` needs to be executed as root, e.g. `sudo artifactcollector`. 50 | macOS can still prevent the execution, in this case right-click the artifactcollector, 51 | select "Open", confirm "Open" and then try again with `sudo artifactcollector`. 52 | 53 | ## Output 54 | 55 | The artifactcollecor will create a zip file and a log file. 56 | The log file serves two purposes: 57 | inform an investigator about errors during the collection but 58 | also give the user a way to know what elements were extracted. 59 | The zip file contains the results of the extraction and needs to be transferred back to the investigator. 60 | 61 | ## Build your own artifactcollector 62 | 63 | 1. Clone the repository: `git clone https://github.com/forensicanalysis/artifactcollector`. 64 | 2. Add and edit artifact definition yaml files as needed in `config/artifacts`. 65 | 3. Edit `config/ac.yaml` and add the artifacts you want to collect. 66 | 4. Run `make build` to generate the artifactcollector binary. 67 | 1. You can also use `make build-win` to cross-compile for Windows. 68 | 69 | ## Embed binaries 70 | 71 | Binaries can be added to `config/bin` and then included into the artifactcollector 72 | in the `go build` step. Additionally, a corresponding COMMAND artifact like 73 | the following is required: 74 | 75 | ```yaml 76 | name: Autoruns 77 | sources: 78 | - type: COMMAND 79 | attributes: 80 | cmd: autorunsc.exe 81 | args: [ "-x" ] 82 | supported_os: [ Windows ] 83 | ``` 84 | 85 | The command output to stdout and stderr is saved, but generated files are not collected. 86 | 87 | ## Acknowledgement 88 | 89 | The artifactcollector uses on the following great projects: 90 | 91 | - [config/artifacts](config/artifacts) is based on the awesome [Forensic Artifacts](https://github.com/ForensicArtifacts/artifacts) project. 92 | - [doublestar](doublestar) is based on [Bob Matcuk's](https://github.com/bmatcuk) great [doublestar](https://github.com/bmatcuk/doublestar) package. 93 | - [store/aczip](store/aczip) and [build/go](build/go) contain code from the Go standard library. 94 | 95 | ## License 96 | 97 | The directories `store/aczip` and `build/go` contain code from the Go standard library 98 | which is licensed under the [BSD-3-Clause license](LICENSE-BSD). 99 | Everything else is licensed under the MIT License. The directories `doublestar` and `tools/resources` contain their own original MIT license files, [LICENSE](LICENSE) covers the other files. -------------------------------------------------------------------------------- /artifacts/artifactcollector.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package artifacts 23 | 24 | import ( 25 | "io/fs" 26 | ) 27 | 28 | // ArtifactCollector is an interface that can resolve parameters in artifact 29 | // definitions and collect artifacts. 30 | type ArtifactCollector interface { 31 | Resolve(parameter string) ([]string, error) 32 | Collect(name string, source Source) 33 | 34 | FS() fs.FS 35 | Registry() fs.FS 36 | Prefixes() []string 37 | } 38 | -------------------------------------------------------------------------------- /artifacts/artifactcollector_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package artifacts 23 | 24 | import ( 25 | "errors" 26 | "io/fs" 27 | ) 28 | 29 | type TestCollector struct { 30 | fs fs.FS 31 | Collected map[string][]Source 32 | } 33 | 34 | func (r *TestCollector) Collect(name string, source Source) { 35 | source = ExpandSource(source, r) 36 | 37 | if r.Collected == nil { 38 | r.Collected = map[string][]Source{} 39 | } 40 | 41 | r.Collected[name] = append(r.Collected[name], source) 42 | } 43 | 44 | func (r *TestCollector) FS() fs.FS { 45 | return r.fs 46 | } 47 | 48 | func (r *TestCollector) Registry() fs.FS { 49 | return r.fs 50 | } 51 | 52 | func (r *TestCollector) Prefixes() []string { 53 | return nil 54 | } 55 | 56 | func (r *TestCollector) Resolve(s string) ([]string, error) { 57 | switch s { 58 | case "foo": 59 | return []string{"xxx", "yyy"}, nil 60 | case "faz": 61 | return []string{"%foo%"}, nil 62 | case "environ_systemdrive": 63 | return []string{"C:"}, nil 64 | } 65 | 66 | return nil, errors.New("could not resolve") 67 | } 68 | -------------------------------------------------------------------------------- /artifacts/artifactdefinition.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | // Package artifacts provides functions for parsing and validating forensic 23 | // artifact definition files. 24 | package artifacts 25 | 26 | // A KeyValuePair represents Windows Registry key path and value name that can 27 | // potentially be collected. 28 | type KeyValuePair struct { 29 | Key string `yaml:"key,omitempty"` 30 | Value string `yaml:"value,omitempty"` 31 | } 32 | 33 | // Attributes are specific to the type of source definition. They contain 34 | // information. 35 | type Attributes struct { 36 | Names []string `yaml:"names,omitempty"` 37 | Paths []string `yaml:"paths,omitempty"` 38 | Separator string `yaml:"separator,omitempty"` 39 | Cmd string `yaml:"cmd,omitempty"` 40 | Args []string `yaml:"args,omitempty"` 41 | Keys []string `yaml:"keys,omitempty"` 42 | Query string `yaml:"query,omitempty"` 43 | BaseObject string `yaml:"base_object,omitempty"` 44 | KeyValuePairs []KeyValuePair `yaml:"key_value_pairs,omitempty"` 45 | } 46 | 47 | // Provide defines a knowledge base entry that can be created using this source. 48 | type Provide struct { 49 | Key string `yaml:"key,omitempty"` 50 | Regex string `yaml:"regex,omitempty"` 51 | WMIKey string `yaml:"wmi_key,omitempty"` 52 | } 53 | 54 | // The Source type objects define the source of the artifact data. Currently 55 | // the following source types are defined: 56 | // 57 | // - artifact; the source is one or more artifact definitions; 58 | // - file; the source is one or more files; 59 | // - path; the source is one or more paths; 60 | // - directory; the source is one or more directories; 61 | // - Windows Registry key; the source is one or more Windows Registry keys; 62 | // - Windows Registry value; the source is one or more Windows Registry values; 63 | // - WMI query; the source is a Windows Management Instrumentation query. 64 | // 65 | // The difference between the file and path source types are that file should 66 | // be used to define file entries that contain data and path, file entries that 67 | // define a location. E.g. on Windows %SystemRoot% could be considered a path 68 | // artifact definition, pointing to a location e.g. C:\\Windows. And where 69 | // C:\\Windows\\System32\\winevt\\Logs\\AppEvent.evt a file artifact definition, 70 | // pointing to the Application Event Log file. 71 | type Source struct { 72 | Parent string 73 | 74 | Type string `yaml:"type,omitempty"` 75 | Attributes Attributes `yaml:"attributes,omitempty"` 76 | Conditions []string `yaml:"conditions,omitempty"` 77 | SupportedOs []string `yaml:"supported_os,omitempty"` 78 | Provides []Provide `yaml:"provides,omitempty"` 79 | } 80 | 81 | // The ArtifactDefinition describes an object of digital archaeological interest. 82 | type ArtifactDefinition struct { 83 | Name string `yaml:"name,omitempty"` 84 | Doc string `yaml:"doc,omitempty"` 85 | Sources []Source `yaml:"sources,omitempty"` 86 | Conditions []string `yaml:"conditions,omitempty"` 87 | Provides []string `yaml:"provides,omitempty"` 88 | Labels []string `yaml:"labels,omitempty"` 89 | SupportedOs []string `yaml:"supported_os,omitempty"` 90 | Urls []string `yaml:"urls,omitempty"` 91 | } 92 | 93 | // SourceType is an enumeration of artifact definition source types. 94 | var SourceType = struct { 95 | ArtifactGroup string 96 | Command string 97 | Directory string 98 | File string 99 | Path string 100 | RegistryKey string 101 | RegistryValue string 102 | Wmi string 103 | }{ 104 | ArtifactGroup: "ARTIFACT_GROUP", 105 | Command: "COMMAND", 106 | Directory: "DIRECTORY", 107 | File: "FILE", 108 | Path: "PATH", 109 | RegistryKey: "REGISTRY_KEY", 110 | RegistryValue: "REGISTRY_VALUE", 111 | Wmi: "WMI", 112 | } 113 | -------------------------------------------------------------------------------- /artifacts/decoding.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package artifacts 23 | 24 | import ( 25 | "io" 26 | "os" 27 | 28 | "gopkg.in/yaml.v2" 29 | ) 30 | 31 | // DecodeFile takes a single artifact definition file to decode. 32 | func DecodeFile(filename string) ([]ArtifactDefinition, []string, error) { 33 | var artifactDefinitions []ArtifactDefinition 34 | 35 | var typeErrors []string 36 | 37 | // open file 38 | f, err := os.Open(filename) // #nosec 39 | if err != nil { 40 | return artifactDefinitions, typeErrors, err 41 | } 42 | defer f.Close() 43 | 44 | // decode file 45 | dec := NewDecoder(f) 46 | 47 | artifactDefinitions, err = dec.Decode() 48 | if err != nil { 49 | if typeerror, ok := err.(*yaml.TypeError); ok { 50 | typeErrors = append(typeErrors, typeerror.Errors...) 51 | } else { 52 | // bad error 53 | return artifactDefinitions, typeErrors, err 54 | } 55 | } 56 | 57 | return artifactDefinitions, typeErrors, nil 58 | } 59 | 60 | // DecodeFiles takes a list of artifact definition files. Those files are decoded, validated, filtered and expanded. 61 | func DecodeFiles(filenames []string) ([]ArtifactDefinition, error) { 62 | var artifactDefinitions []ArtifactDefinition 63 | 64 | // decode file 65 | for _, filename := range filenames { 66 | ads, _, err := DecodeFile(filename) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | artifactDefinitions = append(artifactDefinitions, ads...) 72 | } 73 | 74 | return artifactDefinitions, nil 75 | } 76 | 77 | // A Decoder reads and decodes YAML values from an input stream. 78 | type Decoder struct { 79 | yamldecoder *yaml.Decoder 80 | } 81 | 82 | // NewDecoder returns a new decoder that reads from r. 83 | // 84 | // The decoder introduces its own buffering and may read 85 | // data from r beyond the YAML values requested. 86 | func NewDecoder(r io.Reader) *Decoder { 87 | d := yaml.NewDecoder(r) 88 | d.SetStrict(true) 89 | 90 | return &Decoder{yamldecoder: d} 91 | } 92 | 93 | func (dec *Decoder) SetStrict(s bool) { 94 | dec.yamldecoder.SetStrict(s) 95 | } 96 | 97 | // Decode reads the next YAML-encoded value from its input and stores it in the 98 | // value pointed to by v. 99 | func (dec *Decoder) Decode() ([]ArtifactDefinition, error) { 100 | var artifactDefinitions []ArtifactDefinition 101 | 102 | for { 103 | artifactDefinition := ArtifactDefinition{} 104 | // load every document 105 | err := dec.yamldecoder.Decode(&artifactDefinition) 106 | if err != nil { 107 | if err == io.EOF { 108 | return artifactDefinitions, nil 109 | } 110 | 111 | return artifactDefinitions, err 112 | } 113 | 114 | // gather artifact 115 | artifactDefinitions = append(artifactDefinitions, artifactDefinition) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /artifacts/expansion_unix_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package artifacts 5 | 6 | import ( 7 | "reflect" 8 | "testing" 9 | ) 10 | 11 | func Test_toForensicPath(t *testing.T) { 12 | type args struct { 13 | name string 14 | prefixes []string 15 | } 16 | 17 | tests := []struct { 18 | name string 19 | args args 20 | want []string 21 | wantErr bool 22 | }{ 23 | {"simple", args{name: `/root`, prefixes: []string{"C", "D"}}, []string{"root"}, false}, 24 | {"prefixes", args{name: `/root`, prefixes: nil}, []string{"root"}, false}, 25 | } 26 | for _, tt := range tests { 27 | t.Run(tt.name, func(t *testing.T) { 28 | got, err := toForensicPath(tt.args.name, tt.args.prefixes) 29 | if (err != nil) != tt.wantErr { 30 | t.Errorf("toForensicPath() error = %v, wantErr %v", err, tt.wantErr) 31 | return 32 | } 33 | 34 | if !reflect.DeepEqual(got, tt.want) { 35 | t.Errorf("toForensicPath() got = %v, want %v", got, tt.want) 36 | } 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /artifacts/expansion_windows_test.go: -------------------------------------------------------------------------------- 1 | package artifacts 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func Test_toForensicPath(t *testing.T) { 9 | type args struct { 10 | name string 11 | prefixes []string 12 | } 13 | tests := []struct { 14 | name string 15 | args args 16 | want []string 17 | wantErr bool 18 | }{ 19 | {"simple", args{name: `C:\Windows`, prefixes: []string{"C", "D"}}, []string{"C/Windows"}, false}, 20 | {"no partition", args{name: `\Windows`, prefixes: []string{"C", "D"}}, []string{"C/Windows", "D/Windows"}, false}, 21 | {"no partition", args{name: `/Windows`, prefixes: []string{"C", "D"}}, []string{"C/Windows", "D/Windows"}, false}, 22 | } 23 | for _, tt := range tests { 24 | t.Run(tt.name, func(t *testing.T) { 25 | got, err := toForensicPath(tt.args.name, tt.args.prefixes) 26 | if (err != nil) != tt.wantErr { 27 | t.Errorf("toForensicPath() error = %v, wantErr %v", err, tt.wantErr) 28 | return 29 | } 30 | if !reflect.DeepEqual(got, tt.want) { 31 | t.Errorf("toForensicPath() got = %v, want %v", got, tt.want) 32 | } 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /artifacts/filter.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package artifacts 23 | 24 | import ( 25 | "runtime" 26 | "strings" 27 | ) 28 | 29 | // FilterOS returns a list of ArtifactDefinitions for the current operating 30 | // system. 31 | func FilterOS(artifactDefinitions []ArtifactDefinition) []ArtifactDefinition { 32 | var selected []ArtifactDefinition 33 | 34 | for _, artifactDefinition := range artifactDefinitions { 35 | if IsOSArtifactDefinition(runtime.GOOS, artifactDefinition.SupportedOs) { 36 | var sources []Source 37 | 38 | for _, source := range artifactDefinition.Sources { 39 | if IsOSArtifactDefinition(runtime.GOOS, source.SupportedOs) { 40 | sources = append(sources, source) 41 | } 42 | } 43 | 44 | artifactDefinition.Sources = sources 45 | selected = append(selected, artifactDefinition) 46 | } 47 | } 48 | 49 | return selected 50 | } 51 | 52 | // FilterName return a list of ArtifactDefinitions which match the provided 53 | // names. 54 | func FilterName(names []string, artifactDefinitions []ArtifactDefinition) []ArtifactDefinition { 55 | artifactDefinitionMap := map[string]ArtifactDefinition{} 56 | for _, artifactDefinition := range artifactDefinitions { 57 | artifactDefinitionMap[artifactDefinition.Name] = artifactDefinition 58 | } 59 | 60 | var artifactList []ArtifactDefinition 61 | for _, artifact := range expandArtifactGroup(names, artifactDefinitionMap) { 62 | artifactList = append(artifactList, artifact) 63 | } 64 | 65 | return artifactList 66 | } 67 | 68 | func IsOSArtifactDefinition(os string, supportedOs []string) bool { 69 | if len(supportedOs) == 0 { 70 | return true 71 | } 72 | 73 | for _, supportedos := range supportedOs { 74 | if strings.EqualFold(supportedos, os) { 75 | return true 76 | } 77 | } 78 | 79 | return false 80 | } 81 | -------------------------------------------------------------------------------- /artifacts/filter_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package artifacts 23 | 24 | import ( 25 | "reflect" 26 | "runtime" 27 | "testing" 28 | ) 29 | 30 | func Test_filterOS(t *testing.T) { 31 | type args struct { 32 | artifactDefinitions []ArtifactDefinition 33 | } 34 | 35 | tests := []struct { 36 | name string 37 | args args 38 | want []ArtifactDefinition 39 | }{ 40 | {"FilterOS true", args{[]ArtifactDefinition{{Name: "Test", SupportedOs: []string{runtime.GOOS}}}}, []ArtifactDefinition{{Name: "Test", Sources: nil, SupportedOs: []string{runtime.GOOS}}}}, 41 | {"FilterOS sources", args{[]ArtifactDefinition{{Name: "Test", Sources: []Source{{SupportedOs: []string{runtime.GOOS}}, {SupportedOs: []string{"xxx"}}}}}}, []ArtifactDefinition{{Name: "Test", Sources: []Source{{SupportedOs: []string{runtime.GOOS}}}}}}, 42 | {"FilterOS false", args{[]ArtifactDefinition{{Name: "Test", SupportedOs: []string{"xxx"}}}}, nil}, 43 | } 44 | for _, tt := range tests { 45 | t.Run(tt.name, func(t *testing.T) { 46 | if got := FilterOS(tt.args.artifactDefinitions); !reflect.DeepEqual(got, tt.want) { 47 | t.Errorf("FilterOS() = %#v, want %#v", got, tt.want) 48 | } 49 | }) 50 | } 51 | } 52 | 53 | func Test_isOSArtifactDefinition(t *testing.T) { 54 | type args struct { 55 | os string 56 | supportedOs []string 57 | } 58 | 59 | tests := []struct { 60 | name string 61 | args args 62 | want bool 63 | }{ 64 | {"Test Windows", args{"Windows", []string{"Windows"}}, true}, 65 | {"Test Windows", args{"Windows", []string{"Linux", "Darwin"}}, false}, 66 | {"Test Linux", args{"Linux", []string{"Linux"}}, true}, 67 | {"Test Darwin", args{"Darwin", []string{"Darwin"}}, true}, 68 | } 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | if got := IsOSArtifactDefinition(tt.args.os, tt.args.supportedOs); got != tt.want { 72 | t.Errorf("isOSArtifactDefinition() = %v, want %v", got, tt.want) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /assets/bin.generated.go: -------------------------------------------------------------------------------- 1 | // Code generated by github.com/forensicanalysis/go-resources. DO NOT EDIT. 2 | 3 | package assets 4 | 5 | var FS = map[string][]byte{} 6 | -------------------------------------------------------------------------------- /assets/config.generated.go: -------------------------------------------------------------------------------- 1 | package assets 2 | 3 | import "github.com/forensicanalysis/artifactcollector/collector" 4 | 5 | var Config = &collector.Configuration{Artifacts: []string{"DefaultCollection1"}, User: false, Case: "", OutputDir: ""} 6 | -------------------------------------------------------------------------------- /build/bin/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicanalysis/artifactcollector/45be3ed54d4bbb69fac87b22206242c0b63a801e/build/bin/.keep -------------------------------------------------------------------------------- /build/go/context/context.go: -------------------------------------------------------------------------------- 1 | package context 2 | 3 | type Context interface { 4 | Done() <-chan struct{} 5 | } 6 | 7 | type emptyCtx int 8 | 9 | func (*emptyCtx) Done() <-chan struct{} { 10 | return nil 11 | } 12 | 13 | func Background() Context { 14 | return new(emptyCtx) 15 | } 16 | -------------------------------------------------------------------------------- /build/go/fs/readdir.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-BSD file. 4 | 5 | package fs 6 | 7 | import ( 8 | "errors" 9 | "sort" 10 | ) 11 | 12 | // ReadDirFS is the interface implemented by a file system 13 | // that provides an optimized implementation of ReadDir. 14 | type ReadDirFS interface { 15 | FS 16 | 17 | // ReadDir reads the named directory 18 | // and returns a list of directory entries sorted by filename. 19 | ReadDir(name string) ([]DirEntry, error) 20 | } 21 | 22 | // ReadDir reads the named directory 23 | // and returns a list of directory entries sorted by filename. 24 | // 25 | // If fs implements ReadDirFS, ReadDir calls fs.ReadDir. 26 | // Otherwise ReadDir calls fs.Open and uses ReadDir and Close 27 | // on the returned file. 28 | func ReadDir(fsys FS, name string) ([]DirEntry, error) { 29 | if fsys, ok := fsys.(ReadDirFS); ok { 30 | return fsys.ReadDir(name) 31 | } 32 | 33 | file, err := fsys.Open(name) 34 | if err != nil { 35 | return nil, err 36 | } 37 | defer file.Close() 38 | 39 | dir, ok := file.(ReadDirFile) 40 | if !ok { 41 | return nil, &PathError{Op: "readdir", Path: name, Err: errors.New("not implemented")} 42 | } 43 | 44 | list, err := dir.ReadDir(-1) 45 | sort.Sort(SortedDir(list)) 46 | 47 | return list, err 48 | } 49 | 50 | // dirInfo is a DirEntry based on a FileInfo. 51 | type dirInfo struct { 52 | fileInfo FileInfo 53 | } 54 | 55 | func (di dirInfo) IsDir() bool { 56 | return di.fileInfo.IsDir() 57 | } 58 | 59 | func (di dirInfo) Type() FileMode { 60 | return di.fileInfo.Mode().Type() 61 | } 62 | 63 | func (di dirInfo) Info() (FileInfo, error) { 64 | return di.fileInfo, nil 65 | } 66 | 67 | func (di dirInfo) Name() string { 68 | return di.fileInfo.Name() 69 | } 70 | 71 | // FileInfoToDirEntry returns a DirEntry that returns information from info. 72 | // If info is nil, FileInfoToDirEntry returns nil. 73 | func FileInfoToDirEntry(info FileInfo) DirEntry { 74 | if info == nil { 75 | return nil 76 | } 77 | 78 | return dirInfo{fileInfo: info} 79 | } 80 | 81 | type SortedDir []DirEntry 82 | 83 | func (a SortedDir) Len() int { return len(a) } 84 | func (a SortedDir) Less(i, j int) bool { return a[i].Name() < a[j].Name() } 85 | func (a SortedDir) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 86 | -------------------------------------------------------------------------------- /build/go/fs/readfile.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-BSD file. 4 | 5 | package fs 6 | 7 | import "io" 8 | 9 | // ReadFileFS is the interface implemented by a file system 10 | // that provides an optimized implementation of ReadFile. 11 | type ReadFileFS interface { 12 | FS 13 | 14 | // ReadFile reads the named file and returns its contents. 15 | // A successful call returns a nil error, not io.EOF. 16 | // (Because ReadFile reads the whole file, the expected EOF 17 | // from the final Read is not treated as an error to be reported.) 18 | // 19 | // The caller is permitted to modify the returned byte slice. 20 | // This method should return a copy of the underlying data. 21 | ReadFile(name string) ([]byte, error) 22 | } 23 | 24 | // ReadFile reads the named file from the file system fs and returns its contents. 25 | // A successful call returns a nil error, not io.EOF. 26 | // (Because ReadFile reads the whole file, the expected EOF 27 | // from the final Read is not treated as an error to be reported.) 28 | // 29 | // If fs implements ReadFileFS, ReadFile calls fs.ReadFile. 30 | // Otherwise ReadFile calls fs.Open and uses Read and Close 31 | // on the returned file. 32 | func ReadFile(fsys FS, name string) ([]byte, error) { 33 | if fsys, ok := fsys.(ReadFileFS); ok { 34 | return fsys.ReadFile(name) 35 | } 36 | 37 | file, err := fsys.Open(name) 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer file.Close() 42 | 43 | var size int 44 | 45 | if info, err := file.Stat(); err == nil { 46 | size64 := info.Size() 47 | if int64(int(size64)) == size64 { 48 | size = int(size64) 49 | } 50 | } 51 | 52 | data := make([]byte, 0, size+1) 53 | 54 | for { 55 | if len(data) >= cap(data) { 56 | d := append(data[:cap(data)], 0) 57 | data = d[:len(data)] 58 | } 59 | 60 | n, err := file.Read(data[len(data):cap(data)]) 61 | data = data[:len(data)+n] 62 | 63 | if err != nil { 64 | if err == io.EOF { 65 | err = nil 66 | } 67 | 68 | return data, err 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /build/go/fs/stat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-BSD file. 4 | 5 | package fs 6 | 7 | // A StatFS is a file system with a Stat method. 8 | type StatFS interface { 9 | FS 10 | 11 | // Stat returns a FileInfo describing the file. 12 | // If there is an error, it should be of type *PathError. 13 | Stat(name string) (FileInfo, error) 14 | } 15 | 16 | // Stat returns a FileInfo describing the named file from the file system. 17 | // 18 | // If fs implements StatFS, Stat calls fs.Stat. 19 | // Otherwise, Stat opens the file to stat it. 20 | func Stat(fsys FS, name string) (FileInfo, error) { 21 | if fsys, ok := fsys.(StatFS); ok { 22 | return fsys.Stat(name) 23 | } 24 | 25 | file, err := fsys.Open(name) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | defer file.Close() 31 | 32 | return file.Stat() 33 | } 34 | -------------------------------------------------------------------------------- /build/win/artifactcollector.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | The artifactcollector is a tool to collect forensic artifacts on a system. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /build/win/artifactcollector.exe.user.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | The artifactcollector is a tool to collect forensic artifacts on a system. 10 | 11 | -------------------------------------------------------------------------------- /build/win/artifactcollector.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicanalysis/artifactcollector/45be3ed54d4bbb69fac87b22206242c0b63a801e/build/win/artifactcollector.ico -------------------------------------------------------------------------------- /build/win/artifactcollector32.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | The artifactcollector is a tool to collect forensic artifacts on a system. 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /build/win/artifactcollector32.exe.user.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | The artifactcollector is a tool to collect forensic artifacts on a system. 10 | 11 | -------------------------------------------------------------------------------- /build/win2k/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 AS builder 2 | 3 | COPY . /repo 4 | 5 | WORKDIR /repo 6 | 7 | RUN make generate-win 8 | RUN mv build/win/artifactcollector32.syso artifactcollector.syso 9 | 10 | FROM golang:1.2.2-cross 11 | 12 | ENV GOPATH=/gopath 13 | ENV GOOS=windows 14 | ENV GOARCH=386 15 | ENV CGO_ENABLED=1 16 | ENV CC=gcc 17 | 18 | COPY --from=builder /repo /repo 19 | 20 | RUN mkdir -p /gopath 21 | RUN mv /repo/vendor /gopath/src 22 | 23 | COPY ./build/go/context/context.go /gopath/src/context/context.go 24 | COPY ./build/go/fs /gopath/src/io/fs 25 | 26 | RUN mv /repo /gopath/src/github.com/forensicanalysis/artifactcollector 27 | RUN cp -r /gopath/src/github.com/forensicanalysis/artifactcollector/build/go /gopath/src 28 | 29 | WORKDIR /gopath/src/github.com/forensicanalysis/artifactcollector 30 | 31 | ENV CC=i686-w64-mingw32-gcc 32 | 33 | CMD ["go", "build", "-o", "/build/artifactcollector2k.exe", "."] 34 | -------------------------------------------------------------------------------- /build/winxp/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.23 AS builder 2 | 3 | COPY . /repo 4 | 5 | WORKDIR /repo 6 | 7 | RUN make generate-win 8 | RUN mv build/win/artifactcollector32.syso artifactcollector.syso 9 | 10 | FROM golang:1.9.7 11 | 12 | RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list 13 | RUN apt-get update 14 | RUN apt-get install -y --force-yes mingw-w64 15 | 16 | ENV GOPATH=/gopath 17 | ENV GOOS=windows 18 | ENV GOARCH=386 19 | ENV CGO_ENABLED=1 20 | ENV CC=gcc 21 | 22 | COPY --from=builder /repo /repo 23 | 24 | RUN mkdir -p /gopath 25 | RUN mv /repo/vendor /gopath/src 26 | 27 | COPY ./build/go/context/context.go /gopath/src/context/context.go 28 | COPY ./build/go/fs /gopath/src/io/fs 29 | 30 | RUN mv /repo /gopath/src/github.com/forensicanalysis/artifactcollector 31 | RUN cp -r /gopath/src/github.com/forensicanalysis/artifactcollector/build/go /gopath/src 32 | 33 | WORKDIR /gopath/src/github.com/forensicanalysis/artifactcollector 34 | 35 | ENV CC=i686-w64-mingw32-gcc 36 | 37 | CMD ["go", "build", "-o", "/build/artifactcollectorxp.exe", "."] 38 | -------------------------------------------------------------------------------- /collect/collect.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019-2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collect 23 | 24 | import ( 25 | "fmt" 26 | "io/ioutil" 27 | "log" 28 | "os" 29 | "path/filepath" 30 | "regexp" 31 | "runtime" 32 | "runtime/debug" 33 | "time" 34 | 35 | "github.com/forensicanalysis/artifactcollector/artifacts" 36 | "github.com/forensicanalysis/artifactcollector/collector" 37 | "github.com/forensicanalysis/artifactcollector/store" 38 | ) 39 | 40 | var ( 41 | windowsZipTempDir = regexp.MustCompile(`(?i)C:\\Windows\\system32`) 42 | sevenZipTempDir = regexp.MustCompile(`(?i)C:\\Users\\.*\\AppData\\Local\\Temp\\.*`) 43 | ) 44 | 45 | func Collect(config *collector.Configuration, artifactDefinitions []artifacts.ArtifactDefinition, embedded map[string][]byte) (run *Run, err error) { //nolint:funlen 46 | defer func() { 47 | if r := recover(); r != nil { 48 | logPrint("A critical error occurred: ", r, string(debug.Stack())) 49 | } 50 | }() 51 | 52 | start := time.Now() 53 | 54 | run = NewRun(config) 55 | 56 | setupLogging(run.LogfilePath) 57 | 58 | defer closeLogging() 59 | 60 | logPrint("Start to collect forensic artifacts. This might take a while.") 61 | 62 | // unpack internal files 63 | tempDir, err := unpack(embedded) 64 | if err != nil { 65 | logPrint(err) 66 | 67 | return run, err 68 | } 69 | defer os.RemoveAll(tempDir) // clean up 70 | 71 | if err := enforceAdmin(!config.User); err != nil { 72 | logPrint(err) 73 | 74 | return run, err 75 | } 76 | 77 | filteredArtifactDefinitions, err := filterArtifacts(config, artifactDefinitions) 78 | if err != nil { 79 | logPrint(err) 80 | 81 | return run, err 82 | } 83 | 84 | // create store 85 | store, teardownStore, err := createStore(run.StorePath, config, filteredArtifactDefinitions) 86 | if err != nil { 87 | logPrint(err) 88 | 89 | return run, err 90 | } 91 | 92 | // also log to store 93 | addLogger(store) 94 | 95 | // collect artifacts 96 | if err := collectArtifacts(store, tempDir, artifactDefinitions, filteredArtifactDefinitions, start); err != nil { 97 | logPrint(err) 98 | 99 | return run, err 100 | } 101 | 102 | // remove store logger 103 | resetLogger() 104 | 105 | if err := teardownStore(); err != nil { 106 | logPrint(fmt.Errorf("Close Store failed: %s", err)) 107 | 108 | return run, fmt.Errorf("Close Store failed: %s", err) 109 | } 110 | 111 | logPrint("Collection done.") 112 | 113 | return run, nil 114 | } 115 | 116 | func filterArtifacts(config *collector.Configuration, definitions []artifacts.ArtifactDefinition) ([]artifacts.ArtifactDefinition, error) { 117 | filtered := definitions 118 | 119 | if config.Artifacts != nil { 120 | filtered = artifacts.FilterName(config.Artifacts, definitions) 121 | } 122 | 123 | if len(filtered) == 0 { 124 | return nil, fmt.Errorf("No artifacts selected in config") 125 | } 126 | 127 | return filtered, nil 128 | } 129 | 130 | func collectArtifacts(store *store.ZipStore, tempDir string, artifactDefinitions []artifacts.ArtifactDefinition, filteredArtifactDefinitions []artifacts.ArtifactDefinition, start time.Time) error { 131 | collector, err := collector.NewCollector(store, tempDir, artifactDefinitions) 132 | if err != nil { 133 | return fmt.Errorf("Collector creation failed: %w", err) 134 | } 135 | 136 | total := len(filteredArtifactDefinitions) 137 | 138 | // collect artifacts 139 | for i := 0; i < total; i++ { 140 | collectArtifact(collector, filteredArtifactDefinitions[i], i, total) 141 | } 142 | 143 | log.Printf("Collected artifacts in %.1f seconds\n", time.Since(start).Seconds()) 144 | 145 | return nil 146 | } 147 | 148 | func collectArtifact(collector *collector.Collector, artifactDefinition artifacts.ArtifactDefinition, i, total int) { 149 | defer func() { 150 | if r := recover(); r != nil { 151 | logPrint("A critical error occurred: ", r, string(debug.Stack())) 152 | } 153 | }() 154 | 155 | startArtifact := time.Now() 156 | 157 | logPrint(fmt.Sprintf("Collecting %s (%d/%d)", artifactDefinition.Name, i+1, total)) 158 | 159 | for _, source := range artifactDefinition.Sources { 160 | collector.Collect(artifactDefinition.Name, source) 161 | } 162 | 163 | log.Printf("Collected %s in %.1f seconds\n", artifactDefinition.Name, time.Since(startArtifact).Seconds()) 164 | } 165 | 166 | func unpack(embedded map[string][]byte) (tempDir string, err error) { 167 | tempDir, err = ioutil.TempDir("", "ac") 168 | if err != nil { 169 | return tempDir, err 170 | } 171 | 172 | for path, content := range embedded { 173 | if err := os.MkdirAll(filepath.Join(tempDir, filepath.Dir(path)), 0700); err != nil { 174 | return tempDir, err 175 | } 176 | 177 | if err := ioutil.WriteFile(filepath.Join(tempDir, path), content, 0644); err != nil { 178 | return tempDir, err 179 | } 180 | 181 | log.Printf("Unpacking %s", path) 182 | } 183 | 184 | return tempDir, nil 185 | } 186 | 187 | func enforceAdmin(forceAdmin bool) error { 188 | switch { 189 | case !forceAdmin: 190 | return nil 191 | case runtime.GOOS == "windows": 192 | _, err := os.Open("\\\\.\\PHYSICALDRIVE0") 193 | if err != nil { 194 | logPrint("Need to be windows admin") 195 | 196 | return os.ErrPermission 197 | } 198 | 199 | return nil 200 | case os.Getgid() != 0: 201 | logPrint("need to be root") 202 | 203 | return os.ErrPermission 204 | default: 205 | return nil 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /collect/collect_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collect 23 | 24 | import ( 25 | "os" 26 | "strings" 27 | "testing" 28 | 29 | "github.com/forensicanalysis/artifactcollector/artifacts" 30 | "github.com/forensicanalysis/artifactcollector/collector" 31 | ) 32 | 33 | func TestCollect(t *testing.T) { 34 | config := collector.Configuration{Artifacts: []string{"Test"}, User: true} 35 | definitions := []artifacts.ArtifactDefinition{{ 36 | Name: "Test", 37 | Sources: []artifacts.Source{ 38 | {Type: "FILE", Attributes: artifacts.Attributes{Paths: []string{`C:\Windows\explorer.exe`}}}, 39 | {Type: "PATH", Attributes: artifacts.Attributes{Paths: []string{`\Program Files`}}}, 40 | {Type: "DIRECTORY", Attributes: artifacts.Attributes{Paths: []string{`\`}}}, 41 | {Type: "COMMAND", Attributes: artifacts.Attributes{Cmd: "hostname"}}, 42 | {Type: "REGISTRY_KEY", Attributes: artifacts.Attributes{Keys: []string{`HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zones\*`}}}, 43 | {Type: "REGISTRY_VALUE", Attributes: artifacts.Attributes{KeyValuePairs: []artifacts.KeyValuePair{{Key: `HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Nls\CodePage`, Value: "ACP"}}}}, 44 | {Type: "WMI", Attributes: artifacts.Attributes{Query: "SELECT LastBootUpTime FROM Win32_OperatingSystem"}}, 45 | }, 46 | }} 47 | 48 | hostname, err := os.Hostname() 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | 53 | type args struct { 54 | config *collector.Configuration 55 | artifactDefinitions []artifacts.ArtifactDefinition 56 | embedded map[string][]byte 57 | } 58 | 59 | tests := []struct { 60 | name string 61 | args args 62 | wantHost string 63 | }{ 64 | {"Collect artifactcollector", args{&config, definitions, nil}, hostname}, 65 | } 66 | for _, tt := range tests { 67 | t.Run(tt.name, func(t *testing.T) { 68 | got, err := Collect(tt.args.config, tt.args.artifactDefinitions, tt.args.embedded) 69 | if err != nil { 70 | t.Fatal(err) 71 | } 72 | 73 | if !strings.HasPrefix(got.Name, tt.wantHost) { 74 | t.Errorf("Collect().Name = %v, does not start with %v", got, tt.wantHost) 75 | } 76 | 77 | if !strings.HasPrefix(got.StorePath, tt.wantHost) { 78 | t.Errorf("Collect().StorePath = %v, does not start with %v", got, tt.wantHost) 79 | } 80 | 81 | if _, err := os.Stat(got.StorePath); os.IsNotExist(err) { 82 | t.Errorf("Store path %s does not exist", got.StorePath) 83 | } 84 | 85 | if _, err := os.Stat(got.LogfilePath); os.IsNotExist(err) { 86 | t.Errorf("Log file path %s does not exist", got.LogfilePath) 87 | } 88 | }) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /collect/createstore.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/forensicanalysis/artifactcollector/artifacts" 8 | "github.com/forensicanalysis/artifactcollector/collector" 9 | "github.com/forensicanalysis/artifactcollector/store" 10 | ) 11 | 12 | func createStore(storePath string, config *collector.Configuration, definitions []artifacts.ArtifactDefinition) (*store.ZipStore, func() error, error) { 13 | f, err := os.Create(storePath) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | store := store.NewSimpleStore(f) 19 | teardown := func() error { 20 | if err := store.Close(); err != nil { 21 | return err 22 | } 23 | 24 | return f.Close() 25 | } 26 | 27 | if err := store.InsertStruct("_config", "definitions", definitions); err != nil { 28 | return nil, nil, err 29 | } 30 | 31 | if err := store.InsertStruct("_config", "artifacts", config.Artifacts); err != nil { 32 | return nil, nil, err 33 | } 34 | 35 | return store, teardown, nil 36 | } 37 | -------------------------------------------------------------------------------- /collect/log.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "log" 8 | "os" 9 | ) 10 | 11 | var logfile *os.File 12 | 13 | func setupLogging(logfilePath string) { 14 | log.SetFlags(log.LstdFlags | log.Lshortfile) 15 | 16 | var err error 17 | 18 | logfile, err = os.OpenFile(logfilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 19 | if err != nil { 20 | log.Printf("Could not open logfile %s\n", err) 21 | } else { 22 | log.SetOutput(logfile) 23 | } 24 | } 25 | 26 | func addLogger(w io.Writer) { 27 | if logfile != nil { 28 | log.SetOutput(io.MultiWriter(logfile, w)) 29 | } else { 30 | log.SetOutput(w) 31 | } 32 | } 33 | 34 | func resetLogger() { 35 | if logfile != nil { 36 | log.SetOutput(logfile) 37 | } else { 38 | log.SetOutput(ioutil.Discard) 39 | } 40 | } 41 | 42 | func closeLogging() error { 43 | if logfile != nil { 44 | log.SetOutput(ioutil.Discard) 45 | 46 | return logfile.Close() 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func logPrint(a ...interface{}) { 53 | log.Println(a...) 54 | fmt.Println(a...) 55 | } 56 | -------------------------------------------------------------------------------- /collect/run.go: -------------------------------------------------------------------------------- 1 | package collect 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "time" 10 | 11 | "github.com/forensicanalysis/artifactcollector/collector" 12 | ) 13 | 14 | // Run is the output of a run that can be used to further process the output 15 | // (e.g. send the output to a SFTP server). 16 | type Run struct { 17 | Name string 18 | StorePath string 19 | LogfilePath string 20 | } 21 | 22 | func NewRun(config *collector.Configuration) *Run { 23 | var outputDirFlag string 24 | 25 | flag.StringVar(&outputDirFlag, "o", "", "Output directory for forensicstore and log file") 26 | flag.Parse() 27 | 28 | cwd, _ := os.Getwd() 29 | 30 | outputDir := "" // current directory 31 | 32 | // output dir order: 33 | // 1. -o flag given 34 | // 2. implemented in config 35 | // 3.1. running from zip -> Desktop 36 | // 3.2. otherwise -> current directory 37 | switch { 38 | case outputDirFlag != "": 39 | outputDir = outputDirFlag 40 | case config.OutputDir != "": 41 | outputDir = config.OutputDir 42 | case windowsZipTempDir.MatchString(cwd) || sevenZipTempDir.MatchString(cwd): 43 | fmt.Println("Running from zip, results will be available on Desktop") 44 | 45 | outputDir = filepath.Join(homeDir(), "Desktop") 46 | } 47 | 48 | if outputDir != "" { 49 | _ = os.MkdirAll(outputDir, 0700) 50 | } 51 | 52 | hostname, err := os.Hostname() 53 | if err != nil { 54 | hostname = "artifactcollector" 55 | } 56 | 57 | if config.Case != "" { 58 | hostname = config.Case + "-" + hostname 59 | } 60 | 61 | t := time.Now().UTC().Format("2006-01-02T15-04-05") 62 | collectionName := fmt.Sprintf("%s_%s", hostname, t) 63 | 64 | return &Run{ 65 | Name: collectionName, 66 | LogfilePath: filepath.Join(outputDir, collectionName+".log"), 67 | StorePath: filepath.Join(outputDir, collectionName+".zip"), 68 | } 69 | } 70 | 71 | func homeDir() string { 72 | if runtime.GOOS == "windows" { 73 | os.Getenv("USERPROFILE") 74 | } 75 | 76 | return os.Getenv("HOME") 77 | } 78 | -------------------------------------------------------------------------------- /collector/configuration.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | // Configuration defines the parameters of an artifactcollector. 25 | type Configuration struct { 26 | Artifacts []string `yaml:"artifacts"` 27 | User bool `yaml:"user"` 28 | Case string `yaml:"case"` 29 | OutputDir string `yaml:"output_dir"` 30 | } 31 | -------------------------------------------------------------------------------- /collector/file.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "crypto/md5" // #nosec 26 | "crypto/sha1" // #nosec 27 | "crypto/sha256" 28 | "fmt" 29 | "io" 30 | "io/fs" 31 | "log" 32 | "os" 33 | "path" 34 | "path/filepath" 35 | "strings" 36 | "syscall" 37 | 38 | "github.com/forensicanalysis/fslib/systemfs" 39 | ) 40 | 41 | func getString(m map[string]interface{}, key string) string { 42 | if value, ok := m[key]; ok { 43 | if valueString, ok := value.(string); ok { 44 | return valueString 45 | } 46 | } 47 | 48 | return "" 49 | } 50 | 51 | func (c *Collector) createFile(definitionName string, collectContents bool, srcpath, _ string) (f *File) { //nolint:funlen,gocognit,cyclop 52 | file := NewFile() 53 | file.Artifact = definitionName 54 | file.Name = path.Base(srcpath) 55 | file.Origin = map[string]interface{}{"path": srcpath} 56 | 57 | if !strings.Contains(srcpath, "*") && !strings.Contains(srcpath, "%%") { //nolint: nestif 58 | // exists 59 | srcInfo, err := fs.Stat(c.SourceFS, srcpath) 60 | if err != nil { 61 | if os.IsNotExist(err) || strings.Contains(strings.ToLower(err.Error()), "not found") { 62 | return nil 63 | } 64 | 65 | return file.AddError(err.Error()) 66 | } 67 | 68 | // do not return dirs 69 | if srcInfo.IsDir() && collectContents { 70 | return nil 71 | } 72 | 73 | file.Size = float64(srcInfo.Size()) 74 | attr := srcInfo.Sys() 75 | 76 | if attributes, ok := attr.(map[string]interface{}); ok { 77 | file.Ctime = getString(attributes, "created") 78 | file.Mtime = getString(attributes, "modified") 79 | file.Atime = getString(attributes, "accessed") 80 | delete(attributes, "created") 81 | delete(attributes, "modified") 82 | delete(attributes, "accessed") 83 | file.Attributes = attributes 84 | file.Attributes["stat_size"] = srcInfo.Size() 85 | } else { 86 | file.Attributes = map[string]interface{}{"stat_size": srcInfo.Size()} 87 | } 88 | 89 | // copy file 90 | if collectContents && file.Size > 0 { 91 | hostname, err := os.Hostname() 92 | if err != nil { 93 | hostname = "" 94 | } 95 | 96 | dstpath, storeFile, err := c.Store.StoreFile(filepath.Join("files", hostname, strings.TrimLeft(srcpath, ""))) 97 | if err != nil { 98 | return file.AddError(fmt.Errorf("error storing file: %w", err).Error()) 99 | } 100 | 101 | srcFile, err := c.SourceFS.Open(srcpath) 102 | if err != nil { 103 | return file.AddError(fmt.Errorf("error opening file: %w", err).Error()) 104 | } 105 | 106 | size, hashes, err := hashCopy(storeFile, srcFile) 107 | 108 | if cerr := srcFile.Close(); cerr != nil { 109 | log.Println(cerr) 110 | } 111 | 112 | if err != nil { 113 | // Copy failed, try NTFS copy 114 | // is a lock violation 115 | errorLockViolation := 33 116 | if systemFS, ok := c.SourceFS.(*systemfs.FS); ok { 117 | if errno, ok := err.(syscall.Errno); ok && int(errno) == errorLockViolation { 118 | log.Println("copy error because of a lock violation, try low level copy") 119 | 120 | ntfsSrcFile, teardown, oerr := systemFS.NTFSOpen(srcpath) 121 | if oerr != nil { 122 | return file.AddError(fmt.Errorf("error opening NTFS file: %w", oerr).Error()) 123 | } 124 | 125 | defer func() { 126 | if terr := teardown(); terr != nil { 127 | log.Println(terr) 128 | } 129 | }() 130 | 131 | // reset file or open a new store file 132 | if !resetFile(storeFile) { 133 | dstpath, storeFile, err = c.Store.StoreFile(filepath.Join("files", hostname, strings.TrimLeft(srcpath, ""))) 134 | if err != nil { 135 | return file.AddError(fmt.Errorf("error storing file: %w", err).Error()) 136 | } 137 | } 138 | 139 | size, hashes, err = hashCopy(storeFile, ntfsSrcFile) 140 | } 141 | } 142 | 143 | if err != nil { 144 | return file.AddError(fmt.Errorf("copy error %T %s -> store %s: %w", c.SourceFS, srcpath, dstpath, err).Error()) 145 | } 146 | } 147 | 148 | if size != srcInfo.Size() { 149 | file.AddError(fmt.Sprintf("filesize parsed is %d, copied %d bytes", srcInfo.Size(), size)) 150 | } 151 | 152 | file.Size = float64(size) 153 | file.ExportPath = filepath.ToSlash(dstpath) 154 | file.Hashes = hashes 155 | } 156 | 157 | return file 158 | } 159 | 160 | return file.AddError("path contains unknown expanders") 161 | } 162 | 163 | type Resetter interface { 164 | Reset() 165 | } 166 | 167 | func resetFile(storeFile io.Writer) bool { 168 | reset := false 169 | 170 | if seeker, ok := storeFile.(io.Seeker); ok { 171 | _, err := seeker.Seek(0, os.SEEK_CUR) 172 | if err != nil { 173 | reset = true 174 | } 175 | } 176 | 177 | if resetter, ok := storeFile.(Resetter); ok { 178 | resetter.Reset() 179 | 180 | reset = true 181 | } 182 | 183 | return reset 184 | } 185 | 186 | func hashCopy(dst io.Writer, src io.Reader) (int64, map[string]interface{}, error) { 187 | md5hash, sha1hash, sha256hash := md5.New(), sha1.New(), sha256.New() // #nosec 188 | 189 | size, err := io.Copy(io.MultiWriter(dst, sha1hash, md5hash, sha256hash), src) 190 | if err != nil { 191 | return 0, nil, err 192 | } 193 | 194 | return size, map[string]interface{}{ 195 | "MD5": fmt.Sprintf("%x", md5hash.Sum(nil)), 196 | "SHA-1": fmt.Sprintf("%x", sha1hash.Sum(nil)), 197 | "SHA-256": fmt.Sprintf("%x", sha256hash.Sum(nil)), 198 | }, nil 199 | } 200 | -------------------------------------------------------------------------------- /collector/file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "bytes" 26 | "io" 27 | "reflect" 28 | "testing" 29 | ) 30 | 31 | type storeResetter struct{} 32 | 33 | func (s *storeResetter) Write(_ []byte) (int, error) { 34 | return 0, nil 35 | } 36 | 37 | func (s *storeResetter) Close() error { 38 | return nil 39 | } 40 | 41 | func (s *storeResetter) Reset() {} 42 | 43 | type storeSeeker struct{} 44 | 45 | func (s *storeSeeker) Write(_ []byte) (int, error) { 46 | return 0, nil 47 | } 48 | 49 | func (s *storeSeeker) Close() error { 50 | return nil 51 | } 52 | 53 | // Seek(offset int64, whence int) (int64, error) 54 | func (s *storeSeeker) Seek(offset int64, whence int) (int64, error) { 55 | return 0, nil 56 | } 57 | 58 | func Test_getString(t *testing.T) { 59 | testMap := map[string]interface{}{ 60 | "test": "I'm a string", 61 | "not a string": 1, 62 | } 63 | 64 | type args struct { 65 | m map[string]interface{} 66 | key string 67 | } 68 | 69 | tests := []struct { 70 | name string 71 | args args 72 | want string 73 | }{ 74 | { 75 | name: "key exists, value is a string", 76 | args: args{ 77 | m: testMap, 78 | key: "test", 79 | }, 80 | want: "I'm a string", 81 | }, 82 | { 83 | name: "key exists, not a string", 84 | args: args{ 85 | m: testMap, 86 | key: "not a string", 87 | }, 88 | want: "", 89 | }, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | if got := getString(tt.args.m, tt.args.key); got != tt.want { 94 | t.Errorf("getString() = %v, want %v", got, tt.want) 95 | } 96 | }) 97 | } 98 | } 99 | 100 | func Test_resetFile_seeker(t *testing.T) { 101 | type args struct { 102 | storeFile io.WriteCloser 103 | } 104 | 105 | tests := []struct { 106 | name string 107 | args args 108 | want bool 109 | }{ 110 | { 111 | name: "io.Seeker", 112 | args: args{ 113 | storeFile: &storeSeeker{}, 114 | }, 115 | want: false, 116 | }, 117 | { 118 | name: "Resetter", 119 | args: args{ 120 | storeFile: &storeResetter{}, 121 | }, 122 | want: true, 123 | }, 124 | } 125 | for _, tt := range tests { 126 | t.Run(tt.name, func(t *testing.T) { 127 | if got := resetFile(tt.args.storeFile); got != tt.want { 128 | t.Errorf("resetFile() = %v, want %v", got, tt.want) 129 | } 130 | }) 131 | } 132 | } 133 | 134 | func Test_hashCopy(t *testing.T) { 135 | type args struct { 136 | src io.Reader 137 | } 138 | 139 | tests := []struct { 140 | name string 141 | args args 142 | want int64 143 | want1 map[string]interface{} 144 | wantDst string 145 | wantErr bool 146 | }{ 147 | { 148 | name: "empty string", 149 | args: args{ 150 | src: bytes.NewBuffer([]byte("")), 151 | }, 152 | want: 0, 153 | want1: map[string]interface{}{ 154 | "MD5": "d41d8cd98f00b204e9800998ecf8427e", 155 | "SHA-1": "da39a3ee5e6b4b0d3255bfef95601890afd80709", 156 | "SHA-256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 157 | }, 158 | wantDst: "", 159 | wantErr: false, 160 | }, 161 | } 162 | for _, tt := range tests { 163 | t.Run(tt.name, func(t *testing.T) { 164 | dst := &bytes.Buffer{} 165 | 166 | got, got1, err := hashCopy(dst, tt.args.src) 167 | if (err != nil) != tt.wantErr { 168 | t.Errorf("hashCopy() error = %v, wantErr %v", err, tt.wantErr) 169 | return 170 | } 171 | 172 | if got != tt.want { 173 | t.Errorf("hashCopy() got = %v, want %v", got, tt.want) 174 | } 175 | 176 | if !reflect.DeepEqual(got1, tt.want1) { 177 | t.Errorf("hashCopy() got1 = %v, want %v", got1, tt.want1) 178 | } 179 | 180 | if gotDst := dst.String(); gotDst != tt.wantDst { 181 | t.Errorf("hashCopy() gotDst = %v, want %v", gotDst, tt.wantDst) 182 | } 183 | }) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /collector/process.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "bytes" 26 | "fmt" 27 | "io" 28 | "os" 29 | "os/exec" 30 | "path" 31 | "path/filepath" 32 | "time" 33 | ) 34 | 35 | func (c *Collector) createProcess(definitionName, cmd string, args []string) *Process { 36 | process := NewProcess() 37 | process.Artifact = definitionName 38 | process.CommandLine = cmd 39 | process.Name = cmd 40 | 41 | for _, arg := range args { 42 | process.CommandLine += " " + arg 43 | } 44 | 45 | // setup output destinations 46 | stdoutPath, stdoutFile, err := c.Store.StoreFile(path.Join("process", definitionName, "stdout")) 47 | if err != nil { 48 | return process.AddError(err.Error()) 49 | } 50 | 51 | process.StdoutPath = filepath.ToSlash(stdoutPath) 52 | stderrBuf := &bytes.Buffer{} 53 | 54 | // run command 55 | execution := exec.Command(filepath.Join(c.TempDir, "pack", "bin", cmd), args...) // #nosec 56 | 57 | if _, err := os.Stat(filepath.Join(c.TempDir, "pack", "bin", cmd)); os.IsNotExist(err) { 58 | process.AddError(fmt.Sprintf("%s is not bundled into artifactcollector, try execution from path", cmd)) 59 | execution = exec.Command(cmd, args...) // #nosec 60 | } 61 | 62 | execution.Stdout = stdoutFile 63 | execution.Stderr = stderrBuf 64 | process.CreatedTime = time.Now().UTC().Format(time.RFC3339Nano) 65 | 66 | if err = execution.Run(); err != nil { 67 | process.AddError(err.Error()) 68 | } 69 | 70 | // write to stderr 71 | stderrPath, stderrFile, err := c.Store.StoreFile(path.Join("process", definitionName, "stderr")) 72 | if err != nil { 73 | return process.AddError(err.Error()) 74 | } 75 | 76 | if _, err := io.Copy(stderrFile, stderrBuf); err != nil { 77 | process.AddError(err.Error()) 78 | } 79 | 80 | process.StderrPath = filepath.ToSlash(stderrPath) 81 | 82 | return process 83 | } 84 | -------------------------------------------------------------------------------- /collector/process_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "os" 26 | "reflect" 27 | "testing" 28 | 29 | "github.com/forensicanalysis/artifactcollector/store" 30 | ) 31 | 32 | func TestLiveCollector_createProcess(t *testing.T) { 33 | f, err := os.CreateTemp("", "test.zip") 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | defer os.Remove(f.Name()) 38 | 39 | store := store.NewSimpleStore(f) 40 | 41 | type args struct { 42 | definitionName string 43 | cmd string 44 | args []string 45 | } 46 | 47 | tests := []struct { 48 | name string 49 | args args 50 | want *Process 51 | }{ 52 | { 53 | "hostname", 54 | args{"test", "hostname", nil}, 55 | &Process{ 56 | Name: "hostname", 57 | Artifact: "test", 58 | Type: "process", 59 | StdoutPath: "process/test/stdout", 60 | StderrPath: "process/test/stderr", 61 | CommandLine: "hostname", 62 | ReturnCode: 0, 63 | Errors: []interface{}{"hostname is not bundled into artifactcollector, try execution from path"}, 64 | }, 65 | }, 66 | } 67 | for _, tt := range tests { 68 | t.Run(tt.name, func(t *testing.T) { 69 | c := &Collector{Store: store} 70 | got := c.createProcess(tt.args.definitionName, tt.args.cmd, tt.args.args) 71 | got.ID = "" // unset ID 72 | got.CreatedTime = "" // unset created 73 | 74 | if !reflect.DeepEqual(got, tt.want) { 75 | t.Errorf("createProcess() = %#v, want %#v", got, tt.want) 76 | } 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /collector/registry_windows.go: -------------------------------------------------------------------------------- 1 | //go:build go1.8 2 | // +build go1.8 3 | 4 | // Copyright (c) 2019 Siemens AG 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | // this software and associated documentation files (the "Software"), to deal in 8 | // the Software without restriction, including without limitation the rights to 9 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 10 | // the Software, and to permit persons to whom the Software is furnished to do so, 11 | // subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in all 14 | // copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 18 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | // 23 | // Author(s): Jonas Plum 24 | 25 | package collector 26 | 27 | import ( 28 | "errors" 29 | "fmt" 30 | "strings" 31 | "time" 32 | 33 | "golang.org/x/sys/windows/registry" 34 | ) 35 | 36 | func (c *Collector) createRegistryKey(definitionName, key string) *RegistryKey { 37 | k, rk := c.createEmptyRegistryKey(definitionName, key) 38 | defer k.Close() 39 | 40 | values, errs := c.getValues(k) 41 | for _, err := range errs { 42 | rk.AddError(err.Error()) 43 | } 44 | rk.Values = values 45 | return rk 46 | } 47 | 48 | func (c *Collector) createRegistryValue(definitionName, key, valueName string) *RegistryKey { 49 | k, rk := c.createEmptyRegistryKey(definitionName, key) 50 | defer k.Close() 51 | 52 | value, err := c.getValue(k, valueName) 53 | if err != nil { 54 | rk.AddError(err.Error()) 55 | } else { 56 | rk.Values = []RegistryValue{value} 57 | } 58 | 59 | return rk 60 | } 61 | 62 | func getRegistryKey(key string) (string, *registry.Key, error) { 63 | registryMap := map[string]registry.Key{ 64 | "HKEY_CLASSES_ROOT": registry.CLASSES_ROOT, 65 | "HKEY_CURRENT_USER": registry.CURRENT_USER, 66 | "HKEY_LOCAL_MACHINE": registry.LOCAL_MACHINE, 67 | "HKEY_USERS": registry.USERS, 68 | "HKEY_CURRENT_CONFIG": registry.CURRENT_CONFIG, 69 | "HKEY_PERFORMANCE_DATA": registry.PERFORMANCE_DATA, 70 | } 71 | key = strings.Trim(key, "/") 72 | key = strings.Replace(key, "/", `\`, -1) 73 | keyparts := strings.SplitN(key, `\`, 2) 74 | if len(keyparts) != 2 { //nolint:gomnd 75 | return key, nil, fmt.Errorf("wrong number of keyparts %s", keyparts) 76 | } 77 | k, err := registry.OpenKey(registryMap[keyparts[0]], keyparts[1], registry.READ|registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS) 78 | if err != nil { 79 | return key, &k, fmt.Errorf("could not open key: %w", err) 80 | } 81 | return key, &k, nil 82 | } 83 | 84 | func (c *Collector) createEmptyRegistryKey(name string, fskey string) (*registry.Key, *RegistryKey) { 85 | rk := NewRegistryKey() 86 | rk.Artifact = name 87 | rk.Values = []RegistryValue{} 88 | // get registry key 89 | cleankey, k, err := getRegistryKey(fskey) 90 | rk.Key = cleankey 91 | if err != nil { 92 | rk.Errors = append(rk.Errors, err.Error()) 93 | return k, rk 94 | } 95 | 96 | info, err := k.Stat() 97 | if err != nil { 98 | rk.Errors = append(rk.Errors, err.Error()) 99 | } else { 100 | rk.ModifiedTime = info.ModTime().UTC().Format(time.RFC3339Nano) 101 | } 102 | return k, rk 103 | } 104 | 105 | func (c *Collector) getValues(k *registry.Key) (values []RegistryValue, valueErrors []error) { 106 | // get registry key stats 107 | ki, err := k.Stat() 108 | if err != nil { 109 | return nil, []error{fmt.Errorf("could not stat: %w", err)} 110 | } 111 | 112 | if ki.ValueCount > 0 { 113 | valuenames, err := k.ReadValueNames(int(ki.ValueCount)) 114 | if err != nil { 115 | return nil, []error{fmt.Errorf("could not read value names: %w", err)} 116 | } 117 | 118 | for _, valuename := range valuenames { 119 | value, err := c.getValue(k, valuename) 120 | if err != nil { 121 | valueErrors = append(valueErrors, err) 122 | } else { 123 | values = append(values, value) 124 | } 125 | } 126 | } 127 | 128 | return values, valueErrors 129 | } 130 | 131 | func (c *Collector) getValue(k *registry.Key, valueName string) (value RegistryValue, err error) { 132 | dataType, _, _, stringData, err := valueData(k, valueName) 133 | if err != nil { 134 | return value, fmt.Errorf("could not parse registry data: %w", err) 135 | } 136 | if valueName == "" { 137 | valueName = "(Default)" 138 | } 139 | return RegistryValue{Name: valueName, Data: stringData, DataType: dataType}, nil 140 | } 141 | 142 | func valueData(rk *registry.Key, name string) (dataType string, bytesData []byte, data interface{}, stringData string, err error) { 143 | size, valtype, err := rk.GetValue(name, nil) 144 | if err != nil { 145 | return "", nil, nil, "", err 146 | } 147 | 148 | bytesData = make([]byte, size) 149 | _, _, err = rk.GetValue(name, bytesData) 150 | if err != nil { 151 | return "", nil, nil, "", err 152 | } 153 | 154 | dataType, err = getValueTypeString(valtype) 155 | if err != nil { 156 | return "", nil, nil, "", err 157 | } 158 | 159 | switch valtype { 160 | case registry.SZ, registry.EXPAND_SZ: 161 | stringData, _, err = rk.GetStringValue(name) 162 | data = stringData 163 | case registry.NONE, registry.BINARY: 164 | data, _, err = rk.GetBinaryValue(name) 165 | stringData = fmt.Sprintf("% x", data) 166 | case registry.QWORD, registry.DWORD: 167 | data, _, err = rk.GetIntegerValue(name) 168 | stringData = fmt.Sprintf("%d", data) 169 | case registry.MULTI_SZ: 170 | data, _, err = rk.GetStringsValue(name) 171 | stringData = strings.Join(data.([]string), " ") 172 | case registry.DWORD_BIG_ENDIAN, registry.LINK, registry.RESOURCE_LIST, registry.FULL_RESOURCE_DESCRIPTOR, 173 | registry.RESOURCE_REQUIREMENTS_LIST: 174 | fallthrough 175 | default: 176 | data = bytesData 177 | stringData = fmt.Sprintf("% x", bytesData) 178 | } 179 | return dataType, bytesData, data, stringData, err 180 | } 181 | 182 | func getValueTypeString(valtype uint32) (string, error) { 183 | types := map[uint32]string{ 184 | registry.NONE: "REG_NONE", 185 | registry.SZ: "REG_SZ", 186 | registry.EXPAND_SZ: "REG_EXPAND_SZ", 187 | registry.BINARY: "REG_BINARY", 188 | registry.DWORD: "REG_DWORD", 189 | registry.DWORD_BIG_ENDIAN: "REG_DWORD_BIG_ENDIAN", 190 | registry.LINK: "REG_LINK", 191 | registry.MULTI_SZ: "REG_MULTI_SZ", 192 | registry.RESOURCE_LIST: "REG_RESOURCE_LIST", 193 | registry.FULL_RESOURCE_DESCRIPTOR: "REG_FULL_RESOURCE_DESCRIPTOR", 194 | registry.RESOURCE_REQUIREMENTS_LIST: "REG_RESOURCE_REQUIREMENTS_LIST", 195 | registry.QWORD: "REG_QWORD", 196 | } 197 | if s, ok := types[valtype]; ok { 198 | return s, nil 199 | } 200 | return "", errors.New("value type not found") 201 | } 202 | -------------------------------------------------------------------------------- /collector/registrydummy_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | //go:build !windows || !go1.8 23 | // +build !windows !go1.8 24 | 25 | package collector 26 | 27 | func (c *Collector) createRegistryValue(definitionName, _, _ string) *RegistryKey { 28 | return &RegistryKey{Artifact: definitionName, Type: "empty"} 29 | } 30 | 31 | func (c *Collector) createRegistryKey(definitionName, _ string) *RegistryKey { 32 | return &RegistryKey{Artifact: definitionName, Type: "empty"} 33 | } 34 | -------------------------------------------------------------------------------- /collector/resolve_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "os" 26 | "path/filepath" 27 | "runtime" 28 | "sort" 29 | "strings" 30 | "testing" 31 | 32 | "github.com/forensicanalysis/artifactcollector/artifacts" 33 | "github.com/forensicanalysis/artifactcollector/store" 34 | ) 35 | 36 | func Test_collectorResolver_Resolve(t *testing.T) { 37 | windowsEnvironmentVariableSystemRoot := artifacts.ArtifactDefinition{ 38 | Name: "WindowsEnvironmentVariableSystemRoot", 39 | Doc: `The system root directory path, defined by %SystemRoot%, typically "C:\Windows".`, 40 | Sources: []artifacts.Source{{ 41 | Type: "PATH", 42 | Attributes: artifacts.Attributes{ 43 | Paths: []string{`\Windows`, `\WinNT`, `\WINNT35`, `\WTSRV`}, 44 | Separator: `\`, 45 | }, 46 | Provides: []artifacts.Provide{ 47 | {Key: "environ_systemroot"}, 48 | {Key: "environ_windir"}, 49 | {Key: "environ_systemdrive", Regex: `^(..)`}, 50 | }, 51 | }, { 52 | Type: "REGISTRY_VALUE", 53 | Attributes: artifacts.Attributes{ 54 | KeyValuePairs: []artifacts.KeyValuePair{{ 55 | Key: `HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion`, 56 | Value: `SystemRoot`, 57 | }}, 58 | }, 59 | Provides: []artifacts.Provide{ 60 | {Key: "environ_systemroot"}, 61 | {Key: "environ_windir"}, 62 | {Key: "environ_systemdrive", Regex: `^(..)`}, 63 | }, 64 | }}, 65 | SupportedOs: []string{"Windows"}, 66 | Urls: []string{"http://environmentvariables.org/SystemRoot"}, 67 | } 68 | 69 | windowsSystemEventLogEvtx := artifacts.ArtifactDefinition{ 70 | Name: "WindowsSystemEventLogEvtxFile", 71 | Doc: "Windows System Event log for Vista or later systems.", 72 | Sources: []artifacts.Source{{ 73 | Type: "FILE", 74 | Attributes: artifacts.Attributes{ 75 | Paths: []string{`%%environ_systemroot%%\System32\winevt\Logs\System.evtx`}, 76 | Separator: `\`, 77 | }, 78 | }}, 79 | Conditions: []string{"os_major_version >= 6"}, 80 | Labels: []string{"Logs"}, 81 | SupportedOs: []string{"Windows"}, 82 | Urls: []string{"http://www.forensicswiki.org/wiki/Windows_XML_Event_Log_(EVTX)"}, 83 | } 84 | 85 | type args struct { 86 | parameter string 87 | } 88 | 89 | tests := []struct { 90 | name string 91 | args args 92 | wantResolves []string 93 | wantErr bool 94 | os string 95 | }{ 96 | {"Resolve test", args{"environ_systemroot"}, []string{`C/Windows`, `C:\windows`}, false, "windows"}, 97 | } 98 | for _, tt := range tests { 99 | t.Run(tt.name, func(t *testing.T) { 100 | if tt.os == runtime.GOOS { 101 | testDir := setup(t) 102 | defer teardown(t) 103 | 104 | err := os.MkdirAll(filepath.Join(testDir, "extract"), 0755) 105 | if err != nil { 106 | t.Errorf("Could not make dir %s", err) 107 | return 108 | } 109 | 110 | f, err := os.CreateTemp("", "test.zip") 111 | if err != nil { 112 | t.Fatal(err) 113 | } 114 | defer os.Remove(f.Name()) 115 | 116 | store := store.NewSimpleStore(f) 117 | 118 | collector, err := NewCollector(store, "", []artifacts.ArtifactDefinition{windowsSystemEventLogEvtx, windowsEnvironmentVariableSystemRoot}) 119 | if err != nil { 120 | t.Errorf("NewCollector() error = %v", err) 121 | return 122 | } 123 | 124 | gotResolves, err := collector.Resolve(tt.args.parameter) 125 | if (err != nil) != tt.wantErr { 126 | t.Errorf("Resolve() error = %v, wantErr %v", err, tt.wantErr) 127 | return 128 | } 129 | 130 | sort.Strings(gotResolves) 131 | sort.Strings(tt.wantResolves) 132 | 133 | if len(gotResolves) != len(tt.wantResolves) { 134 | t.Errorf("Resolve() gotResolves = %v, want %v", gotResolves, tt.wantResolves) 135 | } 136 | 137 | for i := range gotResolves { 138 | if !strings.EqualFold(gotResolves[i], tt.wantResolves[i]) { 139 | t.Errorf("Resolve() gotResolves = %v, want %v", gotResolves, tt.wantResolves) 140 | } 141 | } 142 | } 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /collector/types.go: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | // File implements a STIX 2.1 File Object. 10 | type File struct { 11 | ID string `json:"id"` 12 | Artifact string `json:"artifact,omitempty"` 13 | Type string `json:"type"` 14 | Hashes map[string]interface{} `json:"hashes,omitempty"` 15 | Size float64 `json:"size,omitempty"` 16 | Name string `json:"name"` 17 | Ctime string `json:"ctime,omitempty"` 18 | Mtime string `json:"mtime,omitempty"` 19 | Atime string `json:"atime,omitempty"` 20 | Origin map[string]interface{} `json:"origin,omitempty"` 21 | ExportPath string `json:"export_path,omitempty"` 22 | Errors []interface{} `json:"errors,omitempty"` 23 | Attributes map[string]interface{} `json:"attributes,omitempty"` 24 | } 25 | 26 | // NewFile creates a new STIX 2.1 File Object. 27 | func NewFile() *File { 28 | return &File{ID: "file--" + uuid.New().String(), Type: "file"} 29 | } 30 | 31 | // AddError adds an error string to a File and returns this File. 32 | func (i *File) AddError(err string) *File { 33 | log.Print(err) 34 | i.Errors = append(i.Errors, err) 35 | 36 | return i 37 | } 38 | 39 | // Directory implements a STIX 2.1 Directory Object. 40 | type Directory struct { 41 | ID string `json:"id"` 42 | Artifact string `json:"artifact,omitempty"` 43 | Type string `json:"type"` 44 | Path string `json:"path"` 45 | Ctime string `json:"ctime,omitempty"` 46 | Mtime string `json:"mtime,omitempty"` 47 | Atime string `json:"atime,omitempty"` 48 | Errors []interface{} `json:"errors,omitempty"` 49 | } 50 | 51 | // NewDirectory creates a new STIX 2.1 Directory Object. 52 | func NewDirectory() *Directory { 53 | return &Directory{ID: "directory--" + uuid.New().String(), Type: "directory"} 54 | } 55 | 56 | // AddError adds an error string to a Directory and returns this Directory. 57 | func (i *Directory) AddError(err string) *Directory { 58 | log.Print(err) 59 | i.Errors = append(i.Errors, err) 60 | 61 | return i 62 | } 63 | 64 | // RegistryValue implements a STIX 2.1 Windows™ Registry Value Type. 65 | type RegistryValue struct { 66 | Name string `json:"name"` 67 | Data string `json:"data,omitempty"` 68 | DataType string `json:"data_type,omitempty"` 69 | Errors []interface{} `json:"errors,omitempty"` 70 | } 71 | 72 | // NewRegistryValue creates a new STIX 2.1 Windows™ Registry Value Type. 73 | func NewRegistryValue() *RegistryValue { 74 | return &RegistryValue{} 75 | } 76 | 77 | // AddError adds an error string to a RegistryValue and returns this RegistryValue. 78 | func (i *RegistryValue) AddError(err string) *RegistryValue { 79 | log.Print(err) 80 | i.Errors = append(i.Errors, err) 81 | 82 | return i 83 | } 84 | 85 | // RegistryKey implements a STIX 2.1 Windows™ Registry Key Object. 86 | type RegistryKey struct { 87 | ID string `json:"id"` 88 | Artifact string `json:"artifact,omitempty"` 89 | Type string `json:"type"` 90 | Key string `json:"key"` 91 | Values []RegistryValue `json:"values,omitempty"` 92 | ModifiedTime string `json:"modified_time,omitempty"` 93 | Errors []interface{} `json:"errors,omitempty"` 94 | } 95 | 96 | // NewRegistryKey creates a new STIX 2.1 Windows™ Registry Key Object. 97 | func NewRegistryKey() *RegistryKey { 98 | return &RegistryKey{ID: "windows-registry-key--" + uuid.New().String(), Type: "windows-registry-key"} 99 | } 100 | 101 | // AddError adds an error string to a RegistryKey and returns this RegistryKey. 102 | func (i *RegistryKey) AddError(err string) *RegistryKey { 103 | log.Print(err) 104 | i.Errors = append(i.Errors, err) 105 | 106 | return i 107 | } 108 | 109 | // Process implements a STIX 2.1 Process Object. 110 | type Process struct { 111 | ID string `json:"id"` 112 | Artifact string `json:"artifact,omitempty"` 113 | Type string `json:"type"` 114 | Name string `json:"name,omitempty"` 115 | CreatedTime string `json:"created_time,omitempty"` 116 | Cwd string `json:"cwd,omitempty"` 117 | CommandLine string `json:"command_line,omitempty"` 118 | StdoutPath string `json:"stdout_path,omitempty"` 119 | StderrPath string `json:"stderr_path,omitempty"` 120 | WMI []interface{} `json:"wmi,omitempty"` 121 | ReturnCode float64 `json:"return_code,omitempty"` 122 | Errors []interface{} `json:"errors,omitempty"` 123 | } 124 | 125 | // NewProcess creates a new STIX 2.1 Process Object. 126 | func NewProcess() *Process { 127 | return &Process{ID: "process--" + uuid.New().String(), Type: "process"} 128 | } 129 | 130 | // AddError adds an error string to a Process and returns this Process. 131 | func (i *Process) AddError(err string) *Process { 132 | log.Print(err) 133 | i.Errors = append(i.Errors, err) 134 | 135 | return i 136 | } 137 | -------------------------------------------------------------------------------- /collector/wmi.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "time" 26 | ) 27 | 28 | func (c *Collector) createWMI(definitonName, query string) *Process { 29 | process := NewProcess() 30 | process.Artifact = definitonName 31 | process.CommandLine = query 32 | process.Name = "WMI" 33 | process.CreatedTime = time.Now().UTC().Format(time.RFC3339Nano) 34 | 35 | results, err := WMIQuery(query) 36 | if err != nil { 37 | return process.AddError(err.Error()) 38 | } 39 | 40 | for _, result := range results { 41 | process.WMI = append(process.WMI, result) 42 | } 43 | 44 | return process 45 | } 46 | -------------------------------------------------------------------------------- /collector/wmi_unix.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | //go:build !windows 23 | // +build !windows 24 | 25 | package collector 26 | 27 | import "errors" 28 | 29 | // WMIQuery is a dummy function for non windows systems. 30 | func WMIQuery(_ string) (wmiResult []map[string]interface{}, err error) { 31 | return nil, errors.New("WMI calls are not supported") 32 | } 33 | -------------------------------------------------------------------------------- /collector/wmi_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Stack Exchange 2 | // Copyright (c) 2019 Siemens AG 3 | // Copyright (c) 2021 Jonas Plum 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | // 22 | // This code was adapted from 23 | // https://github.com/StackExchange/wmi/blob/master/swbemservices.go 24 | 25 | package collector 26 | 27 | import ( 28 | "fmt" 29 | "time" 30 | 31 | "github.com/go-ole/go-ole" 32 | "github.com/go-ole/go-ole/oleutil" 33 | ) 34 | 35 | // WMIQuery runs a WMI query and returns the result as a map. 36 | func WMIQuery(q string) ([]map[string]interface{}, error) { 37 | resultsChan := make(chan []map[string]interface{}, 1) 38 | errChan := make(chan error, 1) 39 | go wmiRun(resultsChan, errChan, q) 40 | 41 | select { 42 | case result := <-resultsChan: 43 | return result, nil 44 | case err := <-errChan: 45 | return nil, err 46 | case <-time.After(10 * time.Second): //nolint:gomnd 47 | return nil, fmt.Errorf("timeout") 48 | } 49 | } 50 | 51 | func wmiRun(resultsChan chan []map[string]interface{}, errChan chan error, q string) { //nolint:funlen 52 | // init COM, oh yeah 53 | err := ole.CoInitialize(0) 54 | if err != nil { 55 | errChan <- err 56 | return 57 | } 58 | defer ole.CoUninitialize() 59 | 60 | unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") 61 | if err != nil { 62 | errChan <- err 63 | return 64 | } 65 | defer unknown.Release() 66 | 67 | wmi, err := unknown.QueryInterface(ole.IID_IDispatch) 68 | if err != nil { 69 | errChan <- err 70 | return 71 | } 72 | defer wmi.Release() 73 | 74 | // service is a SWbemServices 75 | serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer") 76 | if err != nil { 77 | errChan <- err 78 | return 79 | } 80 | service := serviceRaw.ToIDispatch() 81 | defer service.Release() 82 | 83 | // result is a SWBemObjectSet 84 | resultRaw, err := oleutil.CallMethod(service, "ExecQuery", q) 85 | if err != nil { 86 | errChan <- err 87 | return 88 | } 89 | result := resultRaw.ToIDispatch() 90 | defer result.Release() 91 | 92 | // list of results 93 | enumProperty, err := oleutil.GetProperty(result, "_NewEnum") 94 | if err != nil { 95 | errChan <- err 96 | return 97 | } 98 | defer enumProperty.Clear() //nolint:errcheck 99 | 100 | enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) 101 | if err != nil { 102 | errChan <- err 103 | return 104 | } 105 | if enum == nil { 106 | errChan <- fmt.Errorf("can't get IEnumVARIANT, enum is nil") 107 | return 108 | } 109 | defer enum.Release() 110 | 111 | i := 0 112 | // iterate results 113 | var wmiResults []map[string]interface{} 114 | for elementRaw, length, err := enum.Next(1); length > 0; elementRaw, length, err = enum.Next(1) { 115 | if err != nil { 116 | errChan <- err 117 | return 118 | } 119 | wmiResult, err := parseElement(elementRaw) 120 | if err != nil { 121 | errChan <- err 122 | return 123 | } 124 | wmiResults = append(wmiResults, wmiResult) 125 | 126 | i++ 127 | } 128 | resultsChan <- wmiResults 129 | } 130 | 131 | func parseElement(elementRaw ole.VARIANT) (map[string]interface{}, error) { 132 | wmiResult := map[string]interface{}{} 133 | 134 | // element is a SWbemObject, but really a Win32_Process 135 | element := elementRaw.ToIDispatch() 136 | defer element.Release() 137 | 138 | // get properties of result 139 | rawProperties, err := oleutil.GetProperty(element, "Properties_") 140 | if err != nil { 141 | return nil, err 142 | } 143 | properties := rawProperties.ToIDispatch() 144 | defer properties.Release() 145 | 146 | err = oleutil.ForEach(properties, func(v *ole.VARIANT) error { 147 | propertyName, err := oleutil.GetProperty(v.ToIDispatch(), "Name") 148 | if err != nil { 149 | return err 150 | } 151 | property, err := oleutil.GetProperty(element, propertyName.ToString()) 152 | if err != nil { 153 | return err 154 | } 155 | value := "" 156 | if property.Value() != nil { 157 | value = fmt.Sprint(property.Value()) 158 | } 159 | wmiResult[propertyName.ToString()] = value 160 | return nil 161 | }) 162 | return wmiResult, err 163 | } 164 | -------------------------------------------------------------------------------- /collector/wmi_windows_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package collector 23 | 24 | import ( 25 | "runtime" 26 | "strings" 27 | "testing" 28 | ) 29 | 30 | func TestWMIQuery(t *testing.T) { 31 | type args struct { 32 | q string 33 | } 34 | tests := []struct { 35 | name string 36 | args args 37 | want string 38 | wantErr bool 39 | }{ 40 | {"OS", args{"SELECT * from Win32_OperatingSystem"}, "C:\\WINDOWS", false}, 41 | } 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | if runtime.GOOS != "windows" { 45 | t.Skip() 46 | } 47 | 48 | got, err := WMIQuery(tt.args.q) 49 | if (err != nil) != tt.wantErr { 50 | t.Errorf("WMIQuery() error = %v, wantErr %v", err, tt.wantErr) 51 | return 52 | } 53 | if !strings.EqualFold(got[0]["WindowsDirectory"].(string), tt.want) { 54 | t.Errorf("WMIQuery() got = %v, want %v", got[0]["WindowsDirectory"], tt.want) 55 | } 56 | }) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /config/ac.yaml: -------------------------------------------------------------------------------- 1 | artifacts: ["DefaultCollection1"] # artifact definitions to collect 2 | user: false # optional, if true, do not request admin permissions 3 | case: "" # optional case name 4 | output_dir: "" # optional output directory -------------------------------------------------------------------------------- /config/artifacts/README.md: -------------------------------------------------------------------------------- 1 | ## Artifact Definitions 2 | 3 | The artifactcollector uses yaml files to define forensic artifacts it can collect. 4 | 5 | The yaml files are based on the [ForensicArtifacts/artifacts](https://github.com/ForensicArtifacts/artifacts) 6 | repository, but with the following major changes: 7 | 8 | - `provides` on source level are added to enable extraction of parameters 9 | - All source types are distinctly defined, including the `DIRECTORY` type. 10 | - Parameter expansion and globing is defined, including `**`. 11 | - Inconsistent trailing `\*` in REGISTRY_KEYs are removed. 12 | 13 | The [Style Guide](style_guide.md) describes the full specification of the artifact definitions 14 | how they are used in the artifactcollector. 15 | -------------------------------------------------------------------------------- /config/artifacts/collections.yaml: -------------------------------------------------------------------------------- 1 | # Predefined opinionated collections 2 | 3 | name: DefaultCollection1 4 | doc: Predefined opinionated collections 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: 9 | - 'FOR500' 10 | - 'WindowsComputerName' 11 | - 'WindowsEventLogs' 12 | - 'WindowsHotfixes' 13 | - 'WindowsNetworkInterfaceInformation' 14 | - 'WindowsPersistence' 15 | - 'WindowsRunKeys' 16 | - 'WindowsServices' 17 | - 'WindowsUninstallKeys' 18 | - 'WindowsUSBInformation' 19 | supported_os: [ Windows ] 20 | - type: ARTIFACT_GROUP 21 | attributes: 22 | names: 23 | # - 'BrowserCache' 24 | - 'BrowserHistory' 25 | - 'LinuxIPTablesRulesCommand' 26 | - 'LinuxAtJobsFiles' 27 | - 'LinuxAuditLogFiles' 28 | - 'LinuxCronTabFiles' 29 | - 'LinuxHostnameFile' 30 | supported_os: [ Linux ] 31 | - type: ARTIFACT_GROUP 32 | attributes: 33 | names: 34 | # - 'BrowserCache' 35 | - 'BrowserHistory' 36 | - 'MacOSAtJobsFile' 37 | - 'MacOSAuditLogFiles' 38 | - 'MacOSBashHistoryFile' 39 | - 'MacOSCronTabFile' 40 | - 'MacOSHostsFile' 41 | - 'MacOSLastlogFile' 42 | - 'MacOSMiscLogFiles' 43 | - 'MacOSRecentItemsFiles' 44 | - 'MacOSSystemLogFiles' 45 | - 'MacOSUserTrashFiles' 46 | supported_os: [ Darwin ] 47 | supported_os: [ Darwin,Linux,Windows ] 48 | --- 49 | # Artifacts from the SANS FOR500 course 50 | 51 | name: FOR500 52 | doc: Windows Forensic Analysis 53 | sources: 54 | - type: ARTIFACT_GROUP 55 | attributes: 56 | names: 57 | - WindowsBrowserArtifacts 58 | - WindowsProgramExecution 59 | - WindowsDeletedFiles 60 | - WindowsNetworkActivity 61 | # - WindowsFileOpening 62 | - AccountUsage 63 | - ExternalDevice 64 | supported_os: [ Windows ] 65 | --- 66 | name: WindowsBrowserArtifacts 67 | doc: WindowsBrowserArtifacts 68 | sources: 69 | - type: ARTIFACT_GROUP 70 | attributes: 71 | names: 72 | - WindowsOpenSaveMRU 73 | - WindowsOpenSavePidlMRU 74 | # EmailAttachments 75 | # SkypeMainDirectory is only for macos 76 | # BrowserCache not collected by default 77 | - BrowserHistory 78 | # AdsZoneIdentifier 79 | supported_os: [ Windows ] 80 | --- 81 | name: WindowsProgramExecution 82 | doc: Program Execution 83 | sources: 84 | - type: ARTIFACT_GROUP 85 | attributes: 86 | names: 87 | # UserAssist 88 | - WindowsActivitiesCacheDatabase 89 | - WindowsMostRecentApplication 90 | - WindowsAppCompatCache # Shimcache 91 | # JumpLists 92 | - WindowsAMCacheHveFile 93 | - WindowsSystemResourceUsageMonitorDatabaseFile 94 | # BAM/DAM 95 | # LastVisitedMRU 96 | - WindowsPrefetchFiles 97 | supported_os: [ Windows ] 98 | --- 99 | name: WindowsDeletedFiles 100 | doc: Deleted Files 101 | sources: 102 | - type: ARTIFACT_GROUP 103 | attributes: 104 | names: 105 | # ACMRU 106 | # Thumbcache 107 | # Thumbs.db 108 | # IEEdgeFile -> WindowsBrowserArtifacts 109 | # WordWheelQuery 110 | - WindowsRecycleBin 111 | # LastVisitedMRU -> WindowsProgramExecution 112 | supported_os: [ Windows ] 113 | --- 114 | name: WindowsNetworkActivity 115 | doc: Network Activity 116 | sources: 117 | - type: ARTIFACT_GROUP 118 | attributes: 119 | names: 120 | - WindowsTimezone 121 | - InternetExplorerCookiesFile 122 | # NetworkHistory 123 | # WLANEventLog 124 | # BrowserSearchTerms -> WindowsBrowserArtifacts 125 | # WindowsSystemResourceUsageMonitorDatabaseFile -> WindowsProgramExecution 126 | supported_os: [ Windows ] 127 | # --- 128 | # name: WindowsFileOpening 129 | # doc: File Opening 130 | # sources: 131 | # - type: ARTIFACT_GROUP 132 | # attributes: 133 | # names: 134 | # # WindowsOpenSaveMRU -> WindowsBrowserArtifacts 135 | # # RecentFiles 136 | # # JumpLists -> WindowsProgramExecution 137 | # # ShellBags 138 | # # LNKFiles 139 | # # WindowsPrefetchFiles -> WindowsProgramExecution 140 | # # LastVisitedMRU -> WindowsProgramExecution 141 | # # IEEdgeFile -> WindowsBrowserArtifacts 142 | # # OfficeRecentFiles 143 | # supported_os: [Windows] 144 | --- 145 | name: AccountUsage 146 | doc: Account Usage 147 | sources: 148 | - type: ARTIFACT_GROUP 149 | attributes: 150 | names: 151 | - WindowsSystemRegistryFiles 152 | - WindowsXMLEventLogSecurityFile 153 | supported_os: [ Windows ] 154 | --- 155 | name: ExternalDevice 156 | doc: External Device 157 | sources: 158 | - type: ARTIFACT_GROUP 159 | attributes: 160 | names: 161 | # KeyIdentification 162 | - WindowsSetupApiLogs 163 | # User 164 | # PnPEvents 165 | # VolumeSerialNumber 166 | # DriverLetter 167 | # LNKFiles -> WindowsFileOpening 168 | supported_os: [ Windows ] 169 | -------------------------------------------------------------------------------- /config/artifacts/linux.yaml: -------------------------------------------------------------------------------- 1 | # Linux specific artifacts. 2 | 3 | name: LinuxIPTablesRulesCommand 4 | doc: List IPTables rules. 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | args: [ "-L", "-n", "-v" ] 9 | cmd: /sbin/iptables 10 | supported_os: [ Linux ] 11 | --- 12 | name: LinuxAtJobsFiles 13 | doc: Linux at jobs. 14 | sources: 15 | - type: FILE 16 | attributes: { paths: [ '/var/spool/at/*' ] } 17 | supported_os: [ Linux ] 18 | --- 19 | name: LinuxAuditLogFiles 20 | doc: Linux audit log files. 21 | sources: 22 | - type: FILE 23 | attributes: { paths: [ '/var/log/audit/*' ] } 24 | supported_os: [ Linux ] 25 | --- 26 | name: LinuxCronTabFiles 27 | doc: Crontab files. 28 | sources: 29 | - type: FILE 30 | attributes: 31 | paths: 32 | - '/etc/crontab' 33 | - '/etc/cron.d/*' 34 | - '/var/spool/cron/**' 35 | supported_os: [ Linux ] 36 | --- 37 | name: LinuxHostnameFile 38 | doc: Linux hostname file. 39 | sources: 40 | - type: FILE 41 | attributes: { paths: [ '/etc/hostname' ] } 42 | supported_os: [ Linux ] 43 | --- 44 | name: LinuxPasswdFile 45 | doc: | 46 | Linux passwd file. 47 | 48 | A passwd file consist of colon separated values in the format: 49 | username:password:uid:gid:full name:home directory:shell 50 | sources: 51 | - type: FILE 52 | attributes: { paths: [ '/etc/passwd' ] } 53 | provides: 54 | - key: users.homedir 55 | regex: '.*:(.*?):.*' 56 | supported_os: [ Linux ] 57 | --- 58 | name: LinuxHomePath 59 | doc: Users directories in /home 60 | sources: 61 | - type: PATH 62 | attributes: { paths: [ '/home/*' ] } 63 | provides: 64 | - key: users.homedir 65 | supported_os: [ Linux ] 66 | -------------------------------------------------------------------------------- /config/artifacts/macos.yaml: -------------------------------------------------------------------------------- 1 | # MacOS (Darwin) specific artifacts. 2 | 3 | name: MacOSAtJobsFile 4 | doc: MacOS at jobs 5 | sources: 6 | - type: FILE 7 | attributes: { paths: [ '/usr/lib/cron/jobs/*' ] } 8 | supported_os: [ Darwin ] 9 | --- 10 | name: MacOSAuditLogFiles 11 | doc: Audit log files 12 | sources: 13 | - type: FILE 14 | attributes: 15 | paths: 16 | - '/private/var/audit/*' 17 | - '/var/audit/*' 18 | supported_os: [ Darwin ] 19 | --- 20 | name: MacOSBashHistoryFile 21 | doc: Terminal Commands History 22 | sources: 23 | - type: FILE 24 | attributes: { paths: [ '%%users.homedir%%/.bash_history' ] } 25 | supported_os: [ Darwin ] 26 | --- 27 | name: MacOSCronTabFile 28 | doc: Cron tabs 29 | sources: 30 | - type: FILE 31 | attributes: 32 | paths: 33 | - '/etc/crontab' 34 | - '/private/etc/crontab' 35 | - '/usr/lib/cron/tabs/*' 36 | supported_os: [ Darwin ] 37 | --- 38 | name: MacOSHostsFile 39 | doc: Hosts file 40 | sources: 41 | - type: FILE 42 | attributes: 43 | paths: 44 | - '/etc/hosts' 45 | - '/private/etc/hosts' 46 | supported_os: [ Darwin ] 47 | --- 48 | name: MacOSLastlogFile 49 | doc: Mac OS X lastlog file. 50 | sources: 51 | - type: FILE 52 | attributes: 53 | paths: 54 | - '/private/var/log/lastlog' 55 | - '/var/log/lastlog' 56 | supported_os: [ Darwin ] 57 | --- 58 | name: MacOSMiscLogFiles 59 | doc: Misc. Logs 60 | sources: 61 | - type: FILE 62 | attributes: { paths: [ '/Library/Logs/*' ] } 63 | supported_os: [ Darwin ] 64 | --- 65 | name: MacOSRecentItemsFiles 66 | doc: Recent Items 67 | sources: 68 | - type: FILE 69 | attributes: { paths: [ '%%users.homedir%%/Library/Preferences/com.apple.recentitems.plist' ] } 70 | supported_os: [ Darwin ] 71 | --- 72 | name: MacOSSystemLogFiles 73 | doc: System log files 74 | sources: 75 | - type: FILE 76 | attributes: 77 | paths: 78 | - '/private/var/log/*' 79 | - '/var/log/*' 80 | supported_os: [ Darwin ] 81 | --- 82 | name: MacOSUsersPath 83 | doc: Users directories in /Users 84 | sources: 85 | - type: PATH 86 | attributes: { paths: [ '/Users/*' ] } 87 | provides: 88 | - key: users.homedir 89 | supported_os: [ Darwin ] 90 | --- 91 | name: MacOSUserTrashFiles 92 | doc: User Trash Folder 93 | sources: 94 | - type: FILE 95 | attributes: { paths: [ '%%users.homedir%%/.Trash/*' ] } 96 | supported_os: [ Darwin ] -------------------------------------------------------------------------------- /config/artifacts/windows_logs.yaml: -------------------------------------------------------------------------------- 1 | # Windows event logs. 2 | 3 | name: WindowsEventLogs 4 | doc: Windows Event logs. 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: 9 | - 'WindowsEventLogApplicationFile' 10 | - 'WindowsEventLogSecurityFile' 11 | - 'WindowsEventLogSystemFile' 12 | - 'WindowsXMLEventLogApplicationFile' 13 | - 'WindowsXMLEventLogSecurityFile' 14 | - 'WindowsXMLEventLogSysmonFile' 15 | - 'WindowsXMLEventLogSystemFile' 16 | - 'WindowsXMLEventLogTerminalServicesFile' 17 | supported_os: [ Windows ] 18 | --- 19 | name: WindowsEventLogPath 20 | doc: Windows Event log locations. 21 | sources: 22 | - type: PATH 23 | attributes: 24 | paths: 25 | - '%%environ_systemroot%%\System32\config' 26 | - '%%environ_systemroot%%\System32\winevt\Logs' 27 | separator: '\' 28 | provides: [ { key: windows_event_logs } ] 29 | supported_os: [ Windows ] 30 | --- 31 | name: WindowsEventLogApplicationFile 32 | doc: Application Windows Event Log. 33 | sources: 34 | - type: FILE 35 | attributes: 36 | paths: [ '%%windows_event_logs%%\AppEvent.evt' ] 37 | separator: '\' 38 | supported_os: [ Windows ] 39 | --- 40 | name: WindowsEventLogSecurityFile 41 | doc: Security Windows Event Log. 42 | sources: 43 | - type: FILE 44 | attributes: 45 | paths: [ '%%windows_event_logs%%\SecEvent.evt' ] 46 | separator: '\' 47 | supported_os: [ Windows ] 48 | --- 49 | name: WindowsEventLogSystemFile 50 | doc: System Windows Event Log. 51 | sources: 52 | - type: FILE 53 | attributes: 54 | paths: [ '%%windows_event_logs%%\SysEvent.evt' ] 55 | separator: '\' 56 | supported_os: [ Windows ] 57 | --- 58 | name: WindowsXMLEventLogApplicationFile 59 | doc: Application Windows XML Event Log. 60 | sources: 61 | - type: FILE 62 | attributes: 63 | paths: [ '%%windows_event_logs%%\Application.evtx' ] 64 | separator: '\' 65 | supported_os: [ Windows ] 66 | --- 67 | name: WindowsXMLEventLogSecurityFile 68 | doc: Security Windows XML Event Log. 69 | sources: 70 | - type: FILE 71 | attributes: 72 | paths: [ '%%windows_event_logs%%\Security.evtx' ] 73 | separator: '\' 74 | supported_os: [ Windows ] 75 | --- 76 | name: WindowsXMLEventLogSysmonFile 77 | doc: Sysmon Windows XML Event Log. 78 | sources: 79 | - type: FILE 80 | attributes: 81 | paths: [ '%%windows_event_logs%%\Microsoft-Windows-Sysmon%4Operational.evtx' ] 82 | separator: '\' 83 | supported_os: [ Windows ] 84 | --- 85 | name: WindowsXMLEventLogSystemFile 86 | doc: System Windows XML Event Log. 87 | sources: 88 | - type: FILE 89 | attributes: 90 | paths: [ '%%windows_event_logs%%\System.evtx' ] 91 | separator: '\' 92 | supported_os: [ Windows ] 93 | --- 94 | name: WindowsXMLEventLogTerminalServicesFile 95 | doc: TerminalServices Windows XML Event Log. 96 | sources: 97 | - type: FILE 98 | attributes: 99 | paths: [ '%%windows_event_logs%%\Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx' ] 100 | separator: '\' 101 | supported_os: [ Windows ] 102 | -------------------------------------------------------------------------------- /config/artifacts/windows_usb.yaml: -------------------------------------------------------------------------------- 1 | # USB Artifacts 2 | 3 | name: WindowsUSBInformation 4 | doc: Windows Event logs. 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: 9 | - 'WindowsUSBDeviceInformations' 10 | - 'WindowsUSBVolumeAndDriveMapping' 11 | - 'WindowsUSBUserMountedDevices' 12 | - 'WindowsDeviceSetupFile' 13 | supported_os: [ Windows ] 14 | --- 15 | name: WindowsUSBDeviceInformations 16 | doc: | 17 | Windows USB Device Informations. 18 | 19 | USBSTOR subkey only exists when there ever was an USB device mounted. 20 | sources: 21 | - type: REGISTRY_KEY 22 | attributes: 23 | keys: 24 | - 'HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR\**' 25 | supported_os: [ Windows ] 26 | --- 27 | name: WindowsUSBVolumeAndDriveMapping 28 | doc: | 29 | Windows USB volume and drive mapping. 30 | 31 | Displays the mapping of USB devices to drives and volumes. 32 | sources: 33 | - type: REGISTRY_KEY 34 | attributes: 35 | keys: 36 | - 'HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices' 37 | supported_os: [ Windows ] 38 | --- 39 | name: WindowsUSBUserMountedDevices 40 | doc: | 41 | Windows USB user mounted devices. 42 | 43 | Shows the GUIDs of all devices the user has ever mounted. 44 | sources: 45 | - type: REGISTRY_KEY 46 | attributes: 47 | keys: 48 | - 'HKEY_USERS\%%users.sid%%\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2\**' 49 | supported_os: [ Windows ] 50 | --- 51 | name: WindowsDeviceSetupFile 52 | doc: Logfiles for Windows PNP driver installation 53 | sources: 54 | - type: FILE 55 | attributes: 56 | paths: 57 | - '%%environ_systemroot%%\inf\setupapi*.log' 58 | separator: '\' 59 | supported_os: [ Windows ] 60 | -------------------------------------------------------------------------------- /docs/ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicanalysis/artifactcollector/45be3ed54d4bbb69fac87b22206242c0b63a801e/docs/ac.png -------------------------------------------------------------------------------- /doublestar/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Bob Matcuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doublestar/README.md: -------------------------------------------------------------------------------- 1 | Recursive directory globbing via `**` for Go's [io/fs](https://golang.org/pkg/io/fs). 2 | 3 | ## Example 4 | 5 | ``` golang 6 | func main() { 7 | // get file system for this repository 8 | wd, _ := os.Getwd() 9 | fsys := os.DirFS(wd) 10 | 11 | // get all yml files 12 | matches, _ := fsdoublestar.Glob(fsys, "**/*.yml") 13 | 14 | // print matches 15 | fmt.Println(matches) 16 | // Output: [.github/workflows/ci.yml .github/.golangci.yml] 17 | } 18 | ``` 19 | 20 | ## Acknowledgement 21 | 22 | This repository is based on [Bob Matcuk's](https://github.com/bmatcuk) great [doublestar](https://github.com/bmatcuk/doublestar) package. 23 | -------------------------------------------------------------------------------- /doublestar/example_test.go: -------------------------------------------------------------------------------- 1 | package doublestar_test 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/forensicanalysis/artifactcollector/doublestar" 8 | ) 9 | 10 | func Example() { 11 | // get file system for this repository 12 | wd, _ := os.Getwd() 13 | fsys := os.DirFS(wd) 14 | 15 | // get all yml files 16 | matches, _ := doublestar.Glob(fsys, "**/*.md") 17 | 18 | // print matches 19 | fmt.Println(matches) 20 | // Output: [README.md] 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/forensicanalysis/artifactcollector 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/forensicanalysis/fslib v0.15.2 7 | github.com/go-ole/go-ole v1.2.4 8 | github.com/google/uuid v1.3.0 9 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 10 | gopkg.in/yaml.v2 v2.4.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= 2 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 3 | github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= 4 | github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 5 | github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c h1:MVVbswUlqicyj8P/JljoocA7AyCo62gzD0O7jfvrhtE= 6 | github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= 13 | github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= 14 | github.com/forensicanalysis/fslib v0.15.2 h1:c09Pnm31vtSZgj6EiHGauMCcFuNiP/d3D5Qy4ZPfhKA= 15 | github.com/forensicanalysis/fslib v0.15.2/go.mod h1:7xuslRTRu/B0apdl4u1QV/qlbyN8PY93mtcUk8w4s88= 16 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 17 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 18 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 19 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 20 | github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 21 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 22 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 23 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 24 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 25 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 26 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 27 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 28 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 29 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 30 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 31 | github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 32 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 33 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 34 | github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561/go.mod h1:lvjGftC8oe7XPtyrOidaMi0rp5B9+XY/ZRUynGnuaxQ= 35 | github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77 h1:Msb6XRY62jQOueNNlB5LGin1rljK7c49NLniGwJG2bg= 36 | github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 37 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 38 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 39 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 40 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 41 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 42 | github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho= 43 | github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 44 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 45 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= 46 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 48 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 49 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 50 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 51 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 52 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 53 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 54 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 55 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 56 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 57 | www.velocidex.com/golang/go-ntfs v0.1.1 h1:///bh0pjoKMHC8x/Q1yaIm2Wa2UNOYwmVti9PsR3i9A= 58 | www.velocidex.com/golang/go-ntfs v0.1.1/go.mod h1:1sqoU8u2Jchwiqsbz4yMSq061wEAOcyXhTCfm6Gz3Lk= 59 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | // Package artifactcollector provides a software that collects forensic artifacts 23 | // on systems. These artifacts can be used in forensic investigations to understand 24 | // attacker behavior on compromised computers. 25 | // 26 | // # Features 27 | // 28 | // The artifactcollector offers the following features 29 | // - ️🖥️ Runs on 🖼️ Windows, 🐧 Linux and 🍏 macOS 30 | // - 🛍️ Can extract files, directories, registry entries, command and WMI output 31 | // - ⭐ Uses the configurable and extensible [Forensics Artifacts](https://github.com/forensicanalysis/artifacts) 32 | // - 💾 Creates a forensicstore as [structured output](https://github.com/forensicanalysis/forensicstore) 33 | // - 🕊️ Open source 34 | package main 35 | 36 | import ( 37 | "os" 38 | 39 | "github.com/forensicanalysis/artifactcollector/assets" 40 | "github.com/forensicanalysis/artifactcollector/collect" 41 | ) 42 | 43 | func main() { 44 | if _, err := collect.Collect(assets.Config, assets.Artifacts, assets.FS); err != nil { 45 | os.Exit(1) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /store/aczip/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-BSD file. 4 | 5 | package aczip_test 6 | 7 | import ( 8 | "archive/zip" 9 | "fmt" 10 | "io" 11 | "log" 12 | "os" 13 | 14 | "github.com/forensicanalysis/artifactcollector/store/aczip" 15 | ) 16 | 17 | func ExampleWriter() { 18 | // Create a buffer to write our archive to. 19 | f, err := os.CreateTemp("", "test.zip") 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | // Create a new zip archive. 25 | w := aczip.NewWriter(f) 26 | 27 | // Add some files to the archive. 28 | files := []struct { 29 | Name, Body string 30 | }{ 31 | {"readme.txt", "This archive contains some text files."}, 32 | {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"}, 33 | {"todo.txt", "Get animal handling licence.\nWrite more examples."}, 34 | } 35 | for _, file := range files { 36 | f, err := w.Create(file.Name) 37 | if err != nil { 38 | log.Fatal(err) 39 | } 40 | 41 | _, err = f.Write([]byte(file.Body)) 42 | if err != nil { 43 | log.Fatal(err) 44 | } 45 | } 46 | 47 | // Make sure to check the error on Close. 48 | if err := w.Close(); err != nil { 49 | log.Fatal(err) 50 | } 51 | } 52 | 53 | func ExampleReader() { 54 | // Open a zip archive for reading. 55 | r, err := zip.OpenReader("testdata/readme.zip") 56 | if err != nil { 57 | log.Fatal(err) 58 | } 59 | defer r.Close() 60 | 61 | // Iterate through the files in the archive, 62 | // printing some of their contents. 63 | for _, f := range r.File { 64 | fmt.Printf("Contents of %s:\n", f.Name) 65 | 66 | rc, err := f.Open() 67 | if err != nil { 68 | log.Fatal(err) 69 | } 70 | 71 | _, err = io.CopyN(os.Stdout, rc, 68) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | rc.Close() 77 | fmt.Println() 78 | } 79 | // Output: 80 | // Contents of README: 81 | // This is the source code repository for the Go programming language. 82 | } 83 | -------------------------------------------------------------------------------- /store/aczip/extra.go: -------------------------------------------------------------------------------- 1 | package aczip 2 | 3 | import ( 4 | "archive/zip" 5 | "compress/flate" 6 | "encoding/binary" 7 | "errors" 8 | "io" 9 | "io/ioutil" 10 | ) 11 | 12 | func isZip64(h *zip.FileHeader) bool { 13 | return h.CompressedSize64 >= uint32max || h.UncompressedSize64 >= uint32max 14 | } 15 | 16 | func (w *Writer) Exists(path string) (bool, error) { 17 | if w.closed { 18 | return false, errors.New("zip: exists from closed archive") 19 | } 20 | 21 | if w.last != nil && !w.last.closed { 22 | if err := w.last.close(); err != nil { 23 | return false, err 24 | } 25 | } 26 | 27 | for _, h := range w.dir { 28 | if h.Name == path { 29 | return true, nil 30 | } 31 | } 32 | 33 | return false, nil 34 | } 35 | 36 | func (w *Writer) Read(name string) ([]byte, error) { 37 | if w.closed { 38 | return nil, errors.New("zip: read from closed archive") 39 | } 40 | 41 | if w.last != nil && !w.last.closed { 42 | if err := w.last.close(); err != nil { 43 | return nil, err 44 | } 45 | } 46 | 47 | for _, h := range w.dir { 48 | if h.Name == name { 49 | return w.readFile(h) 50 | } 51 | } 52 | 53 | return nil, errors.New("zip: file not found") 54 | } 55 | 56 | func (w *Writer) readFile(h *header) ([]byte, error) { 57 | offset := w.cw.count 58 | 59 | bodyOffset, err := w.findBodyOffset(h) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | if _, err := w.cw.w.Seek(bodyOffset+int64(h.offset), 0); err != nil { 65 | return nil, err 66 | } 67 | 68 | // Create a limited reader to read only the compressed data 69 | limitedReader := io.LimitReader(w.cw.w, int64(h.CompressedSize64)) 70 | 71 | // Create a flate reader to uncompress the data directly from the limited reader 72 | flateReader := flate.NewReader(limitedReader) 73 | defer flateReader.Close() 74 | 75 | b, err := ioutil.ReadAll(flateReader) 76 | if err != nil && err != io.EOF { 77 | return nil, err 78 | } 79 | 80 | if _, err := w.cw.w.Seek(offset, 0); err != nil { 81 | return nil, err 82 | } 83 | 84 | return b, nil 85 | } 86 | 87 | func (w *Writer) findBodyOffset(h *header) (int64, error) { 88 | var buf [fileHeaderLen]byte 89 | if _, err := w.cw.w.ReadAt(buf[:], int64(h.offset)); err != nil { 90 | return 0, err 91 | } 92 | 93 | b := readBuf(buf[:]) 94 | if sig := b.uint32(); sig != fileHeaderSignature { 95 | return 0, zip.ErrFormat 96 | } 97 | 98 | b = b[22:] // skip over most of the header 99 | filenameLen := int(b.uint16()) 100 | extraLen := int(b.uint16()) 101 | 102 | return int64(fileHeaderLen + filenameLen + extraLen), nil 103 | } 104 | 105 | type readBuf []byte 106 | 107 | func (b *readBuf) uint8() uint8 { 108 | v := (*b)[0] 109 | *b = (*b)[1:] 110 | 111 | return v 112 | } 113 | 114 | func (b *readBuf) uint16() uint16 { 115 | v := binary.LittleEndian.Uint16(*b) 116 | *b = (*b)[2:] 117 | 118 | return v 119 | } 120 | 121 | func (b *readBuf) uint32() uint32 { 122 | v := binary.LittleEndian.Uint32(*b) 123 | *b = (*b)[4:] 124 | 125 | return v 126 | } 127 | 128 | func (b *readBuf) uint64() uint64 { 129 | v := binary.LittleEndian.Uint64(*b) 130 | *b = (*b)[8:] 131 | 132 | return v 133 | } 134 | 135 | func (b *readBuf) sub(n int) readBuf { 136 | b2 := (*b)[:n] 137 | *b = (*b)[n:] 138 | 139 | return b2 140 | } 141 | -------------------------------------------------------------------------------- /store/aczip/extra_test.go: -------------------------------------------------------------------------------- 1 | package aczip_test 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "testing" 7 | 8 | "github.com/forensicanalysis/artifactcollector/store/aczip" 9 | ) 10 | 11 | func TestRead(t *testing.T) { 12 | f, err := os.CreateTemp("", "test.zip") 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | defer os.Remove(f.Name()) 17 | 18 | w := aczip.NewWriter(f) 19 | 20 | _, err = w.Create("test.txt") 21 | if err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | zf, err := w.Create("test2.txt") 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | 30 | _, err = zf.Write([]byte("test")) 31 | if err != nil { 32 | t.Fatal(err) 33 | } 34 | 35 | read, err := w.Read("test.txt") 36 | if err != nil { 37 | t.Fatal(err) 38 | } 39 | 40 | if string(read) != "" { 41 | t.Fatalf("read is not empty: %s", read) 42 | } 43 | 44 | read, err = w.Read("test2.txt") 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | log.Println(f.Name()) 50 | 51 | if string(read) != "test" { 52 | t.Fatalf("read is not test: %s", read) 53 | } 54 | 55 | err = w.Close() 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /store/aczip/struct.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-BSD file. 4 | 5 | /* 6 | Package zip provides support for reading and writing ZIP archives. 7 | 8 | See: http://www.pkware.com/documents/casestudies/APPNOTE.TXT 9 | 10 | This package does not support disk spanning. 11 | 12 | A note about ZIP64: 13 | 14 | To be backwards compatible the FileHeader has both 32 and 64 bit Size 15 | fields. The 64 bit fields will always contain the correct value and 16 | for normal archives both fields will be the same. For files requiring 17 | the ZIP64 format the 32 bit fields will be 0xffffffff and the 64 bit 18 | fields must be used instead. 19 | */ 20 | package aczip 21 | 22 | const Deflate uint16 = 8 23 | 24 | const ( 25 | fileHeaderSignature = 0x04034b50 26 | directoryHeaderSignature = 0x02014b50 27 | directoryEndSignature = 0x06054b50 28 | directory64LocSignature = 0x07064b50 29 | directory64EndSignature = 0x06064b50 30 | dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder 31 | fileHeaderLen = 30 // + filename + extra 32 | directoryHeaderLen = 46 // + filename + extra + comment 33 | directoryEndLen = 22 // + comment 34 | dataDescriptorLen = 16 // four uint32: descriptor signature, crc32, compressed size, size 35 | dataDescriptor64Len = 24 // descriptor with 8 byte sizes 36 | directory64LocLen = 20 // 37 | directory64EndLen = 56 // + extra 38 | 39 | // version numbers 40 | zipVersion20 = 20 // 2.0 41 | zipVersion45 = 45 // 4.5 (reads and writes zip64 archives) 42 | 43 | // limits for non zip64 files 44 | uint16max = (1 << 16) - 1 45 | uint32max = (1 << 32) - 1 46 | 47 | // extra header id's 48 | zip64ExtraID = 0x0001 // zip64 Extended Information Extra Field 49 | ) 50 | -------------------------------------------------------------------------------- /store/aczip/testdata/readme.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicanalysis/artifactcollector/45be3ed54d4bbb69fac87b22206242c0b63a801e/store/aczip/testdata/readme.zip -------------------------------------------------------------------------------- /store/aczip/writer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE-BSD file. 4 | 5 | package aczip 6 | 7 | import ( 8 | "archive/zip" 9 | "bytes" 10 | "io/ioutil" 11 | "math/rand" 12 | "os" 13 | "testing" 14 | ) 15 | 16 | type WriteTest struct { 17 | Name string 18 | Data []byte 19 | Mode os.FileMode 20 | } 21 | 22 | var writeTests = []WriteTest{ 23 | { 24 | Name: "foo", 25 | Data: []byte("Rabbits, guinea pigs, gophers, marsupial rats, and quolls."), 26 | Mode: 0666, 27 | }, 28 | { 29 | Name: "bar", 30 | Data: nil, // large data set in the test 31 | Mode: 0644, 32 | }, 33 | { 34 | Name: "setuid", 35 | Data: []byte("setuid file"), 36 | Mode: 0755 | os.ModeSetuid, 37 | }, 38 | { 39 | Name: "setgid", 40 | Data: []byte("setgid file"), 41 | Mode: 0755 | os.ModeSetgid, 42 | }, 43 | { 44 | Name: "symlink", 45 | Data: []byte("../link/target"), 46 | Mode: 0755 | os.ModeSymlink, 47 | }, 48 | } 49 | 50 | func TestWriter(t *testing.T) { 51 | largeData := make([]byte, 1<<17) 52 | for i := range largeData { 53 | largeData[i] = byte(rand.Int()) 54 | } 55 | 56 | writeTests[1].Data = largeData 57 | 58 | defer func() { 59 | writeTests[1].Data = nil 60 | }() 61 | 62 | // write a zip file 63 | f, err := os.CreateTemp("", "test.zip") 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | // defer os.Remove(f.Name()) 68 | w := NewWriter(f) 69 | 70 | for _, wt := range writeTests { 71 | testCreate(t, w, &wt) 72 | } 73 | 74 | if err := w.Close(); err != nil { 75 | t.Fatal(err) 76 | } 77 | 78 | t.Log(f.Name()) 79 | 80 | if err := f.Sync(); err != nil { 81 | t.Fatal(err) 82 | } 83 | 84 | if _, err := f.Seek(0, 0); err != nil { 85 | t.Fatal(err) 86 | } 87 | 88 | b, err := ioutil.ReadAll(f) 89 | if err != nil { 90 | t.Fatal(err) 91 | } 92 | 93 | // read it back 94 | r, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 95 | if err != nil { 96 | t.Fatal(err) 97 | } 98 | 99 | for i, wt := range writeTests { 100 | testReadFile(t, r.File[i], &wt) 101 | } 102 | } 103 | 104 | func testCreate(t *testing.T, w *Writer, wt *WriteTest) { 105 | header := &zip.FileHeader{ 106 | Name: wt.Name, 107 | Method: Deflate, 108 | } 109 | if wt.Mode != 0 { 110 | header.SetMode(wt.Mode) 111 | } 112 | 113 | f, err := w.CreateHeader(header) 114 | if err != nil { 115 | t.Fatal(err) 116 | } 117 | 118 | _, err = f.Write(wt.Data) 119 | if err != nil { 120 | t.Fatal(err) 121 | } 122 | } 123 | 124 | func testReadFile(t *testing.T, f *zip.File, wt *WriteTest) { 125 | if f.Name != wt.Name { 126 | t.Fatalf("File name: got %q, want %q", f.Name, wt.Name) 127 | } 128 | 129 | testFileMode(t, wt.Name, f, wt.Mode) 130 | 131 | rc, err := f.Open() 132 | if err != nil { 133 | t.Fatal("opening:", err) 134 | } 135 | 136 | b, err := ioutil.ReadAll(rc) 137 | if err != nil { 138 | t.Fatal("reading:", err) 139 | } 140 | 141 | err = rc.Close() 142 | if err != nil { 143 | t.Fatal("closing:", err) 144 | } 145 | 146 | if !bytes.Equal(b, wt.Data) { 147 | t.Errorf("File contents %q, want %q", b, wt.Data) 148 | } 149 | } 150 | 151 | func testFileMode(t *testing.T, zipName string, f *zip.File, want os.FileMode) { 152 | mode := f.Mode() 153 | if want == 0 { 154 | t.Errorf("%s: %s mode: got %v, want none", zipName, f.Name, mode) 155 | } else if mode != want { 156 | t.Errorf("%s: %s mode: want %v, got %v", zipName, f.Name, want, mode) 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /store/zipstore.go: -------------------------------------------------------------------------------- 1 | package store 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | "strconv" 11 | "time" 12 | 13 | "github.com/forensicanalysis/artifactcollector/store/aczip" 14 | ) 15 | 16 | type ZipStore struct { 17 | w *aczip.Writer 18 | } 19 | 20 | func NewSimpleStore(f *os.File) *ZipStore { 21 | return &ZipStore{ 22 | w: aczip.NewWriter(f), 23 | } 24 | } 25 | 26 | func (k *ZipStore) InsertStruct(artifact, id string, element interface{}) error { 27 | b, err := json.Marshal(element) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | _, err = k.w.WriteFile(fmt.Sprintf("artifacts/%s/%s.json", artifact, id), b) 33 | 34 | return err 35 | } 36 | 37 | func (k *ZipStore) StoreFile(filePath string) (storePath string, file io.Writer, err error) { 38 | i := 0 39 | ext := filepath.Ext(filePath) 40 | remoteStoreFilePath := filePath 41 | base := remoteStoreFilePath[:len(remoteStoreFilePath)-len(ext)] 42 | 43 | exists, err := k.w.Exists(remoteStoreFilePath) 44 | if err != nil { 45 | return "", nil, err 46 | } 47 | 48 | for exists { 49 | remoteStoreFilePath = fmt.Sprintf("%s_%d%s", base, i, ext) 50 | i++ 51 | 52 | exists, err = k.w.Exists(remoteStoreFilePath) 53 | if err != nil { 54 | return "", nil, err 55 | } 56 | } 57 | 58 | file, err = k.w.Create(remoteStoreFilePath) 59 | 60 | return remoteStoreFilePath, file, err 61 | } 62 | 63 | func (k *ZipStore) LoadFile(filePath string) (file io.Reader, err error) { 64 | b, err := k.w.Read(filePath) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return bytes.NewReader(b), nil 70 | } 71 | 72 | func (k *ZipStore) Write(b []byte) (int, error) { 73 | name := strconv.FormatInt(time.Now().UTC().UnixNano(), 10) 74 | 75 | return k.w.WriteFile("logs/"+name+".log", b) 76 | } 77 | 78 | func (k *ZipStore) Close() error { 79 | return k.w.Close() 80 | } 81 | -------------------------------------------------------------------------------- /test/artifacts/collect_1.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: Test1 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: FILE 7 | attributes: {paths: ['/foo.txt']} -------------------------------------------------------------------------------- /test/artifacts/collect_2.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: Test2 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: {keys: ['/foo']} -------------------------------------------------------------------------------- /test/artifacts/collect_3.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: Test3 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: {cmd: 'go', args: ['version']} -------------------------------------------------------------------------------- /test/artifacts/collect_4.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: Test4 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: {paths: ['/dir']} -------------------------------------------------------------------------------- /test/artifacts/collect_5.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: Test5 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: REGISTRY_VALUE 7 | attributes: {key_value_pairs: [{'key': '/foo', 'value':'bar'}]} -------------------------------------------------------------------------------- /test/artifacts/collect_6.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: Test6 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: FILE 7 | attributes: {paths: ['/dir/**2/foo.txt']} 8 | supported_os: [Linux,Darwin] 9 | - type: FILE 10 | attributes: {paths: ['\dir\**2\foo.txt'], separator: '\'} 11 | supported_os: [Windows] 12 | supported_os: [Linux,Darwin,Windows] -------------------------------------------------------------------------------- /test/artifacts/invalid/artifact_os.yaml: -------------------------------------------------------------------------------- 1 | # Unknown supported_os 2 | 3 | name: UnknownTestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Unknown] -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_1.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_10.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_11.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_12.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: FILE 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_13.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: PATH 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_14.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: REGISTRY_KEY 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_15.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: REGISTRY_VALUE 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_16.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: WMI 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_2.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_3.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_4.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: FILE 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_5.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: PATH 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_6.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_7.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: REGISTRY_VALUE 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_8.yaml: -------------------------------------------------------------------------------- 1 | # Test unnessesarry attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: WMI 7 | attributes: 8 | names: [Member] 9 | paths: ['/etc'] 10 | separator: '/' 11 | cmd: hostname 12 | args: ['-f'] 13 | keys: ['key'] 14 | query: query 15 | base_object: '\baseobject' 16 | key_value_pairs: [{key: 'key', value: 'value'}] 17 | -------------------------------------------------------------------------------- /test/artifacts/invalid/attributes_9.yaml: -------------------------------------------------------------------------------- 1 | # Test missing attributes 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/custom.yaml: -------------------------------------------------------------------------------- 1 | name: CustomArtifact 2 | custom: Custom field 3 | supported_os: [Unknown] -------------------------------------------------------------------------------- /test/artifacts/invalid/deprecated_vars.yaml: -------------------------------------------------------------------------------- 1 | # Deprecated variables 2 | 3 | name: TestDirectory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | paths: 9 | - "%%users.userprofile%%\\AppData\\Local" 10 | # - "%%users.userprofile%%\\AppData\\Roaming" 11 | # - "%%users.userprofile%%\\Application Data" 12 | # - "%%users.userprofile%%\\Local Settings\\Application Data" 13 | separator: '\' 14 | supported_os: [Windows] 15 | -------------------------------------------------------------------------------- /test/artifacts/invalid/doc_long.yaml: -------------------------------------------------------------------------------- 1 | # No empty line in docstring 2 | 3 | name: TestCommand 4 | doc: | 5 | Minimal dummy artifact definition for tests 6 | Without an empty line in the doc string 7 | sources: 8 | - type: COMMAND 9 | attributes: 10 | cmd: hostname -------------------------------------------------------------------------------- /test/artifacts/invalid/ending.yml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: TestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: test 9 | args: [] -------------------------------------------------------------------------------- /test/artifacts/invalid/file_1.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: GoVersionCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: docker 9 | args: [version] -------------------------------------------------------------------------------- /test/artifacts/invalid/file_2.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | noname: GoVersionCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: docker 9 | args: [version] -------------------------------------------------------------------------------- /test/artifacts/invalid/file_3.yaml: -------------------------------------------------------------------------------- 1 | name: GoVersionCommand 2 | doc: Minimal dummy artifact definition for tests 3 | sources: 4 | - type: COMMAND 5 | attributes: 6 | cmd: docker 7 | args: [version] -------------------------------------------------------------------------------- /test/artifacts/invalid/group_member_exist.yaml: -------------------------------------------------------------------------------- 1 | # Unknown name in artifact group 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: 9 | - Unknown -------------------------------------------------------------------------------- /test/artifacts/invalid/linux_name_prefix_1.yaml: -------------------------------------------------------------------------------- 1 | # Validate name prefix 2 | 3 | name: TestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Linux] -------------------------------------------------------------------------------- /test/artifacts/invalid/mac_os_double_path_1.yaml: -------------------------------------------------------------------------------- 1 | # Require duplicate paths for certain folders in macOS 2 | 3 | name: TestDirectory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | paths: ['/etc'] # , '/tmp', '/var'] 9 | supported_os: ['Darwin'] -------------------------------------------------------------------------------- /test/artifacts/invalid/mac_os_double_path_2.yaml: -------------------------------------------------------------------------------- 1 | # Require duplicate paths for certain folders in macOS 2 | 3 | name: TestDirectory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | paths: ['/private/etc'] # , '/tmp', '/var'] 9 | supported_os: ['Darwin'] -------------------------------------------------------------------------------- /test/artifacts/invalid/macos_name_prefix_2.yaml: -------------------------------------------------------------------------------- 1 | # Validate name prefix 2 | 3 | name: TestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Darwin] -------------------------------------------------------------------------------- /test/artifacts/invalid/name_case_1.yaml: -------------------------------------------------------------------------------- 1 | # Validate name case 2 | 3 | name: testcommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname -------------------------------------------------------------------------------- /test/artifacts/invalid/name_case_2.yaml: -------------------------------------------------------------------------------- 1 | # Validate name case 2 | 3 | name: Test Command 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname -------------------------------------------------------------------------------- /test/artifacts/invalid/name_type_suffix_1.yaml: -------------------------------------------------------------------------------- 1 | # Validate name suffix 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | -------------------------------------------------------------------------------- /test/artifacts/invalid/name_type_suffix_2.yaml: -------------------------------------------------------------------------------- 1 | # Validate name suffix 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname -------------------------------------------------------------------------------- /test/artifacts/invalid/name_unique.yaml: -------------------------------------------------------------------------------- 1 | # Duplicate name 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | --- 10 | name: Test 11 | doc: Minimal dummy artifact definition for tests 12 | sources: 13 | - type: COMMAND 14 | attributes: 15 | cmd: hostname -------------------------------------------------------------------------------- /test/artifacts/invalid/no_cycles_1.yaml: -------------------------------------------------------------------------------- 1 | # No cyclic artifact groups 2 | 3 | name: TestA 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: 9 | - TestB 10 | --- 11 | name: TestB 12 | doc: Minimal dummy artifact definition for tests 13 | sources: 14 | - type: ARTIFACT_GROUP 15 | attributes: 16 | names: 17 | - TestA -------------------------------------------------------------------------------- /test/artifacts/invalid/no_cycles_2.yaml: -------------------------------------------------------------------------------- 1 | # No cyclic artifact groups 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: ARTIFACT_GROUP 7 | attributes: 8 | names: 9 | - Test 10 | -------------------------------------------------------------------------------- /test/artifacts/invalid/no_windows_homedir.yaml: -------------------------------------------------------------------------------- 1 | # No homedir in windows 2 | 3 | name: WindowsTestDirectory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: {paths: ['%%users.homedir%%'], separator: '\'} 8 | supported_os: [Windows] -------------------------------------------------------------------------------- /test/artifacts/invalid/not_provided_1.yaml: -------------------------------------------------------------------------------- 1 | # Not provided 2 | 3 | name: TestProvided 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: {keys: ['%%CURRENT_CONTROL_SET%%\foo']} 8 | -------------------------------------------------------------------------------- /test/artifacts/invalid/not_provided_2.yaml: -------------------------------------------------------------------------------- 1 | # Not provided 2 | 3 | name: TestProvided2 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: {keys: ['%%CURRENT_CONTROL_SET%%\foo']} 8 | - type: REGISTRY_KEY 9 | attributes: 10 | keys: ['foo'] 11 | provides: 12 | - key: "CURRENT_CONTROL_SET" 13 | supported_os: [Linux, Darwin] 14 | -------------------------------------------------------------------------------- /test/artifacts/invalid/registry_current_control_set_1.yaml: -------------------------------------------------------------------------------- 1 | # No CURRENT_CONTROL_SET 2 | 3 | name: Test 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: {keys: ['%%CURRENT_CONTROL_SET%%\foo']} 8 | -------------------------------------------------------------------------------- /test/artifacts/invalid/registry_current_control_set_2.yaml: -------------------------------------------------------------------------------- 1 | # No CURRENT_CONTROL_SET 2 | 3 | name: Test 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_VALUE 7 | attributes: {key_value_pairs: [{key: '%%CURRENT_CONTROL_SET%%\foo', value: 'foo'}]} 8 | -------------------------------------------------------------------------------- /test/artifacts/invalid/registry_hkey_current_user_1.yaml: -------------------------------------------------------------------------------- 1 | # No HKEY_CURRENT_USER 2 | 3 | name: Test 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: {keys: ['HKEY_CURRENT_USER\\foo']} 8 | -------------------------------------------------------------------------------- /test/artifacts/invalid/registry_hkey_current_user_2.yaml: -------------------------------------------------------------------------------- 1 | # No HKEY_CURRENT_USER 2 | 3 | name: Test 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_VALUE 7 | attributes: {key_value_pairs: [{key: 'HKEY_CURRENT_USER\\foo', value: 'foo'}]} 8 | -------------------------------------------------------------------------------- /test/artifacts/invalid/registry_key_unique.yaml: -------------------------------------------------------------------------------- 1 | # Duplicate registry key 2 | 3 | name: TestA 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_KEY 7 | attributes: {keys: ['foo']} 8 | --- 9 | name: Test 10 | doc: Dummy artifact definition for testing. 11 | sources: 12 | - type: REGISTRY_KEY 13 | attributes: {keys: ['foo']} -------------------------------------------------------------------------------- /test/artifacts/invalid/registry_value_unique.yaml: -------------------------------------------------------------------------------- 1 | # Duplicate registry value 2 | 3 | name: TestA 4 | doc: Dummy artifact definition for testing. 5 | sources: 6 | - type: REGISTRY_VALUE 7 | attributes: {key_value_pairs: [{key: 'foo', value: 'bar'}]} 8 | --- 9 | name: Test 10 | doc: Dummy artifact definition for testing. 11 | sources: 12 | - type: REGISTRY_VALUE 13 | attributes: {key_value_pairs: [{key: 'foo', value: 'bar'}]} -------------------------------------------------------------------------------- /test/artifacts/invalid/source_os.yaml: -------------------------------------------------------------------------------- 1 | # Unknown supported_os in source 2 | 3 | name: UnknownTestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Unknown] -------------------------------------------------------------------------------- /test/artifacts/invalid/source_type.yaml: -------------------------------------------------------------------------------- 1 | # Source type unknown 2 | 3 | name: TestUnknown 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: UNKNOWN 7 | -------------------------------------------------------------------------------- /test/artifacts/invalid/windows_name_prefix_3.yaml: -------------------------------------------------------------------------------- 1 | # Validate name prefix 2 | 3 | name: TestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Windows] -------------------------------------------------------------------------------- /test/artifacts/invalid/windows_os_specific_1.yaml: -------------------------------------------------------------------------------- 1 | # Validate name prefix 2 | 3 | name: TestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Linux] -------------------------------------------------------------------------------- /test/artifacts/invalid/windows_os_specific_2.yaml: -------------------------------------------------------------------------------- 1 | # Validate name prefix 2 | 3 | name: TestCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | supported_os: [Linux] -------------------------------------------------------------------------------- /test/artifacts/linux_test.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: LinuxMountCmd 3 | doc: Linux output of mount 4 | sources: 5 | - type: COMMAND 6 | attributes: 7 | args: [] 8 | cmd: /bin/mount 9 | labels: [System] 10 | supported_os: [Linux] 11 | --- 12 | name: ThumbnailCacheFolder 13 | doc: Thumbnail cache folder. 14 | sources: 15 | - type: FILE 16 | attributes: {paths: ['%%users.homedir%%/.thumbnails/**3']} 17 | labels: [Users] 18 | supported_os: [Linux] 19 | --- 20 | name: SSHHostPubKeys 21 | doc: SSH host public keys 22 | sources: 23 | - type: FILE 24 | attributes: 25 | paths: 26 | - '/etc/ssh/ssh_host_*_key.pub' 27 | labels: [Authentication, Configuration Files] 28 | supported_os: [Linux] -------------------------------------------------------------------------------- /test/artifacts/macos_test.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: MacOSTest 3 | sources: 4 | - type: ARTIFACT_GROUP 5 | attributes: 6 | names: 7 | - MacOSAuditLogFiles 8 | - MacOSBashHistory 9 | - MacOSBashSessions 10 | - MacOSLocalTime 11 | - MacOSLoadedKexts 12 | - MacOSUserDownloadsDirectory 13 | supported_os: [Darwin] 14 | --- 15 | name: MacOSAuditLogFiles 16 | doc: Audit log files 17 | sources: 18 | - type: FILE 19 | attributes: {paths: ['/var/audit/*']} 20 | labels: [System, Logs] 21 | supported_os: [Darwin] 22 | urls: 23 | - 'http://forensicswiki.org/wiki/Mac_OS_X' 24 | - 'http://forensicswiki.org/wiki/Mac_OS_X_10.9_-_Artifacts_Location#System_Logs' 25 | --- 26 | name: MacOSBashHistory 27 | doc: Terminal Commands History 28 | sources: 29 | - type: FILE 30 | attributes: {paths: ['%%users.homedir%%/.bash_history']} 31 | labels: [Users, Logs] 32 | supported_os: [Darwin] 33 | urls: 34 | - 'http://forensicswiki.org/wiki/Mac_OS_X' 35 | - 'http://forensicswiki.org/wiki/Mac_OS_X_10.9_-_Artifacts_Location#Logs' 36 | --- 37 | name: MacOSBashSessions 38 | doc: Terminal Commands Sessions 39 | sources: 40 | - type: FILE 41 | attributes: {paths: ['%%users.homedir%%/.bash_sessions/*']} 42 | labels: [Users, Logs] 43 | supported_os: [Darwin] 44 | urls: ['https://www.swiftforensics.com/2018/05/bash-sessions-in-macos.html'] 45 | --- 46 | name: MacOSLocalTime 47 | doc: Local time zone configuation 48 | sources: 49 | - type: FILE 50 | attributes: 51 | paths: 52 | - '/etc/localtime' 53 | - '/private/etc/localtime' 54 | labels: [System] 55 | supported_os: [Darwin] 56 | urls: 57 | - 'http://forensicswiki.org/wiki/Mac_OS_X' 58 | - 'http://forensicswiki.org/wiki/Mac_OS_X_10.9_-_Artifacts_Location#System_Info_Misc.' 59 | --- 60 | name: MacOSLoadedKexts 61 | doc: MacOS Loaded Kernel Extensions. 62 | sources: 63 | - type: COMMAND 64 | attributes: 65 | args: [] 66 | cmd: /usr/sbin/kextstat 67 | labels: [System] 68 | supported_os: [Darwin] 69 | --- 70 | name: MacOSUserDownloadsDirectory 71 | doc: User downloads directory 72 | sources: 73 | - type: DIRECTORY 74 | attributes: {paths: ['%%users.homedir%%/Downloads/**']} 75 | labels: [Users] 76 | supported_os: [Darwin] 77 | urls: 78 | - 'http://forensicswiki.org/wiki/Mac_OS_X' 79 | - 'http://forensicswiki.org/wiki/Mac_OS_X_10.9_-_Artifacts_Location#User_Directories' 80 | -------------------------------------------------------------------------------- /test/artifacts/valid/double_star.yaml: -------------------------------------------------------------------------------- 1 | # Double star in path 2 | 3 | name: TestFile 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: FILE 7 | attributes: 8 | paths: 9 | - "/**/" 10 | -------------------------------------------------------------------------------- /test/artifacts/valid/mac_os_double_path_3.yaml: -------------------------------------------------------------------------------- 1 | # Require duplicate paths for certain folders in macOS 2 | 3 | name: Test1Directory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | paths: ['/etc', '/private/etc'] # , '/tmp', '/var'] 9 | supported_os: ['Darwin'] 10 | -------------------------------------------------------------------------------- /test/artifacts/valid/mac_os_double_path_4.yaml: -------------------------------------------------------------------------------- 1 | # Require duplicate paths for certain folders in macOS 2 | 3 | name: Test2Directory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | paths: ['/etc'] # , '/tmp', '/var'] 9 | supported_os: ['Darwin'] 10 | - type: DIRECTORY 11 | attributes: 12 | paths: ['/private/etc'] # , '/tmp', '/var'] 13 | supported_os: ['Darwin'] -------------------------------------------------------------------------------- /test/artifacts/valid/name_type_suffix_3.yaml: -------------------------------------------------------------------------------- 1 | # Validate name suffix 2 | 3 | name: Test 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: hostname 9 | - type: FILE 10 | attributes: 11 | paths: ["/dev"] -------------------------------------------------------------------------------- /test/artifacts/valid/processing.yaml: -------------------------------------------------------------------------------- 1 | # Require duplicate paths for certain folders in macOS 2 | 3 | name: Test3Directory 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: DIRECTORY 7 | attributes: 8 | paths: ['/dev'] 9 | supported_os: ['Darwin', "Linux"] 10 | -------------------------------------------------------------------------------- /test/artifacts/valid/valid.yaml: -------------------------------------------------------------------------------- 1 | # Artifact definitions for testing 2 | 3 | name: GoVersionCommand 4 | doc: Minimal dummy artifact definition for tests 5 | sources: 6 | - type: COMMAND 7 | attributes: 8 | cmd: docker 9 | args: [version] 10 | --- 11 | name: GoInfoCommand 12 | doc: Full Dummy artifact definition for tests 13 | sources: 14 | - type: COMMAND 15 | attributes: 16 | cmd: docker 17 | args: [info] 18 | conditions: [time_zone != Pacific/Galapagos] 19 | supported_os: [Windows,Linux,Darwin] 20 | # conditions: [time_zone != Pacific/Galapagos] 21 | labels: [Docker] 22 | # provides: [environ_systemroot] 23 | supported_os: [Windows,Linux,Darwin] 24 | urls: ['https://docs.docker.com/engine/reference/commandline/info/'] 25 | --- 26 | name: MysteriousCommand 27 | doc: Mysterious command 28 | sources: 29 | - type: COMMAND 30 | attributes: 31 | cmd: env 32 | args: [] 33 | -------------------------------------------------------------------------------- /test/artifacts/windows_test.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: WindowsAvailableTimeZones 3 | doc: Timezones available on a Windows system. 4 | sources: 5 | - type: REGISTRY_KEY 6 | attributes: {keys: ['HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Time Zones\*\*']} 7 | supported_os: [Windows] 8 | urls: ['https://github.com/libyal/winreg-kb/wiki/Time-zone-keys'] 9 | --- 10 | name: WindowsCodePage 11 | doc: The code page of the system. 12 | sources: 13 | - type: REGISTRY_VALUE 14 | attributes: 15 | key_value_pairs: 16 | - {key: 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Nls\CodePage', value: 'ACP'} 17 | provides: [code_page] 18 | supported_os: [Windows] 19 | urls: ['http://en.wikipedia.org/wiki/Windows_code_page'] 20 | --- 21 | name: WindowsComputerName 22 | doc: The name of the system. 23 | sources: 24 | - type: REGISTRY_VALUE 25 | attributes: 26 | key_value_pairs: 27 | - {key: 'HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ComputerName\ComputerName', value: 'ComputerName'} 28 | supported_os: [Windows] 29 | --- 30 | name: WindowsConfigSys 31 | doc: Windows config.sys file 32 | sources: 33 | - type: FILE 34 | attributes: 35 | paths: 36 | - '%%environ_systemdrive%%\config.sys' 37 | - '%%environ_windir%%\config.nt' 38 | separator: '\' 39 | supported_os: [Windows] 40 | --- 41 | name: WindowsCredentialProviderFilters 42 | doc: Windows Credential Provider Filters 43 | sources: 44 | - type: REGISTRY_KEY 45 | attributes: 46 | keys: 47 | - 'HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Authentication\Credential Provider Filters\*' 48 | - 'HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Authentication\Credential Provider Filters\*' 49 | supported_os: [Windows] 50 | urls: ['http://blog.leetsys.com/2012/01/02/capturing-windows-7-credentials-at-logon-using-custom-credential-provider/'] 51 | --- 52 | name: WindowsFirewallRules 53 | doc: Command to list the configured Windows Firewall rules. 54 | sources: 55 | - type: COMMAND 56 | attributes: 57 | args: ["advfirewall", "firewall", "show", "rule", "name=all"] 58 | cmd: netsh.exe 59 | labels: [System] 60 | supported_os: [Windows] 61 | --- 62 | name: WMIAccountUsersDomain 63 | doc: | 64 | Fill out user AD domain information based on username. 65 | 66 | We expect this artifact to be collected with WindowsRegistryProfiles 67 | to supply the rest of the user information. This artifact optimizes retrieval 68 | of user information by limiting the WMI query to users for which we have 69 | a username for. Specifically this solves the issue that in a domain setting, 70 | querying for all users via WMI will give you the list of all local and domain 71 | accounts which means a large data transfer from an Active Directory server. 72 | This artifact relies on having the users.username field populated in the knowledge 73 | base. Unfortunately even limiting by username this query can be slow, and 74 | this artifact runs it for each user present on the system. 75 | sources: 76 | - type: WMI 77 | attributes: {query: SELECT * FROM Win32_UserAccount WHERE name='%%users.username%%'} 78 | labels: [Users] 79 | provides: [users.userdomain] 80 | supported_os: [Windows] 81 | urls: ['http://msdn.microsoft.com/en-us/library/windows/desktop/aa394507(v=vs.85).aspx'] 82 | --- 83 | name: WindowsRecycleBin 84 | doc: Windows Recycle Bin (Recyler, $Recycle.Bin) files. 85 | sources: 86 | - type: FILE 87 | attributes: 88 | paths: 89 | - '\$Recycle.Bin\**' 90 | - '\Recycler\**' 91 | separator: '\' 92 | labels: [Users] 93 | supported_os: [Windows] 94 | urls: ['http://www.forensicswiki.org/wiki/Windows#Recycle_Bin'] 95 | --- 96 | name: WindowsScheduledTasks 97 | doc: Windows Scheduled Tasks. 98 | sources: 99 | - type: FILE 100 | attributes: 101 | paths: 102 | - '%%environ_systemroot%%\Tasks\**10' 103 | - '%%environ_systemroot%%\System32\Tasks\**10' 104 | - '%%environ_systemroot%%\SysWow64\Tasks\**10' 105 | separator: '\' 106 | supported_os: [Windows] 107 | urls: ['http://forensicswiki.org/wiki/Windows#Scheduled_Tasks'] -------------------------------------------------------------------------------- /tools/artifactvalidator/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | // Package artifactvalidator implements the artifactvalidator command line tool 23 | // that can validate artifact definition files and search for errors, possible 24 | // inconsistencies and other flaws. 25 | // 26 | // # Usage 27 | // 28 | // To run just provide the location of the forensic artifact definition files: 29 | // 30 | // artifactvalidator -v -s artifacts/data/*.yaml 31 | // 32 | // The output is a list of potential issues in those files. 33 | package main 34 | 35 | import ( 36 | "context" 37 | "flag" 38 | "fmt" 39 | "log/slog" 40 | "os" 41 | "path/filepath" 42 | "runtime" 43 | "sort" 44 | "strings" 45 | 46 | "github.com/olekukonko/tablewriter" 47 | 48 | "github.com/forensicanalysis/artifactcollector/artifacts" 49 | ) 50 | 51 | func main() { // nolint:gocyclo,gocognit,funlen 52 | ctx := context.Background() 53 | 54 | exitcode := 0 55 | // parse flags 56 | var entrypoints string 57 | var verbose, summary, quite, nofail bool 58 | flag.BoolVar(&verbose, "verbose", false, "show common flaws as well") 59 | flag.BoolVar(&verbose, "v", false, "show common flaws as well"+" (shorthand)") 60 | flag.BoolVar(&quite, "quite", false, "hide informational flaws") 61 | flag.BoolVar(&quite, "q", false, "hide informational flaws"+" (shorthand)") 62 | flag.BoolVar(&summary, "summary", false, "show summary") 63 | flag.BoolVar(&summary, "s", false, "show summary"+" (shorthand)") 64 | flag.BoolVar(&nofail, "no-fail", false, "do not fail on flaws") 65 | flag.StringVar(&entrypoints, "entrypoints", "", "entrypoint for the artifact collection which are not marked as unused, e.g. 'DefaultCollection1', can be a comma separated list") 66 | flag.Parse() 67 | 68 | // setup logging 69 | switch { 70 | case verbose: 71 | slog.SetLogLoggerLevel(slog.LevelDebug) 72 | case quite: 73 | slog.SetLogLoggerLevel(slog.LevelWarn) 74 | default: 75 | slog.SetLogLoggerLevel(slog.LevelInfo) 76 | } 77 | 78 | args := flag.Args() 79 | 80 | // windows does not expand * 81 | if runtime.GOOS == "windows" { 82 | var files []string 83 | for _, arg := range args { 84 | paths, err := filepath.Glob(arg) 85 | if err != nil { 86 | slog.ErrorContext(ctx, err.Error()) 87 | } 88 | files = append(files, paths...) 89 | } 90 | args = files 91 | } 92 | 93 | // parse artifacts 94 | flaws, err := ValidateFiles(args, strings.Split(entrypoints, ",")) 95 | if err != nil { 96 | slog.ErrorContext(ctx, err.Error()) 97 | 98 | os.Exit(1) 99 | } 100 | 101 | var filteredFlaws []Flaw 102 | if verbose { 103 | filteredFlaws = flaws 104 | } else { 105 | for _, flaw := range flaws { 106 | if flaw.Severity >= Warning || (!quite && flaw.Severity == Info) { 107 | filteredFlaws = append(filteredFlaws, flaw) 108 | } 109 | } 110 | } 111 | 112 | if len(filteredFlaws) > 0 { 113 | exitcode = 1 114 | printFlaws(ctx, filteredFlaws) 115 | if nofail { 116 | exitcode = 0 117 | } 118 | } 119 | 120 | if summary { 121 | // decode file 122 | var artifactDefinitions []artifacts.ArtifactDefinition 123 | for _, filename := range args { 124 | ads, _, err := artifacts.DecodeFile(filename) 125 | if err != nil { 126 | slog.ErrorContext(ctx, err.Error()) 127 | 128 | os.Exit(1) 129 | } 130 | artifactDefinitions = append(artifactDefinitions, ads...) 131 | } 132 | 133 | fmt.Printf("\nFound %d artifacts\n", len(artifactDefinitions)) 134 | 135 | if len(artifactDefinitions) > 0 { 136 | sourcetypes, oss := map[string]int{}, map[string]int{} 137 | for _, artifactDefinition := range artifactDefinitions { 138 | for _, source := range artifactDefinition.Sources { 139 | inc(sourcetypes, source.Type) 140 | } 141 | for _, supportedOS := range artifactDefinition.SupportedOs { 142 | inc(oss, supportedOS) 143 | } 144 | // for _, label := range artifactDefinition.Labels { 145 | // inc(labels, label) 146 | // } 147 | } 148 | printTable("Artifact definition by type", sourcetypes) 149 | printTable("Artifact definition by OS", oss) 150 | // printTable("Artifact definition by label", labels) 151 | } 152 | } 153 | os.Exit(exitcode) 154 | } 155 | 156 | func inc(m map[string]int, key string) { 157 | if _, ok := m[key]; !ok { 158 | m[key] = 0 159 | } 160 | m[key]++ 161 | } 162 | 163 | func printTable(caption string, m map[string]int) { 164 | fmt.Println("\n" + caption) 165 | keys, values := sortedMap(m) 166 | table := tablewriter.NewWriter(os.Stdout) 167 | table.SetHeader(keys) 168 | table.SetCenterSeparator("|") 169 | table.AppendBulk([][]string{values}) 170 | table.Render() 171 | } 172 | 173 | func sortedMap(m map[string]int) ([]string, []string) { 174 | var values []string 175 | var keys []string 176 | for k := range m { 177 | keys = append(keys, k) 178 | } 179 | sort.Strings(keys) 180 | for _, k := range keys { 181 | values = append(values, fmt.Sprint(m[k])) 182 | } 183 | return keys, values 184 | } 185 | 186 | func printFlaws(ctx context.Context, flaws []Flaw) { 187 | for _, flaw := range flaws { 188 | switch flaw.Severity { 189 | case Common: 190 | slog.DebugContext(ctx, fmt.Sprintf("%-60s %-30s %s", flaw.File, flaw.ArtifactDefinition, flaw.Message)) 191 | case Info: 192 | slog.InfoContext(ctx, fmt.Sprintf("%-60s %-30s %s", flaw.File, flaw.ArtifactDefinition, flaw.Message)) 193 | case Warning: 194 | slog.WarnContext(ctx, fmt.Sprintf("%-60s %-30s %s", flaw.File, flaw.ArtifactDefinition, flaw.Message)) 195 | case Error: 196 | slog.ErrorContext(ctx, fmt.Sprintf("%-60s %-30s %s", flaw.File, flaw.ArtifactDefinition, flaw.Message)) 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /tools/artifactvalidator/main_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2019 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package main 23 | 24 | import ( 25 | "reflect" 26 | "testing" 27 | ) 28 | 29 | func Test_inc(t *testing.T) { 30 | type args struct { 31 | m map[string]int 32 | key string 33 | } 34 | tests := []struct { 35 | name string 36 | args args 37 | want map[string]int 38 | }{ 39 | {"Simple inc", args{map[string]int{"a": 2}, "a"}, map[string]int{"a": 3}}, 40 | {"Add key", args{map[string]int{}, "b"}, map[string]int{"b": 1}}, 41 | } 42 | for _, tt := range tests { 43 | t.Run(tt.name, func(t *testing.T) { 44 | inc(tt.args.m, tt.args.key) 45 | if !reflect.DeepEqual(tt.args.m, tt.want) { 46 | t.Errorf("inc() got = %v, want %v", tt.args.m, tt.want) 47 | } 48 | }) 49 | } 50 | } 51 | 52 | func Test_printTable(t *testing.T) { 53 | type args struct { 54 | caption string 55 | m map[string]int 56 | } 57 | tests := []struct { 58 | name string 59 | args args 60 | }{ 61 | {"Print Table", args{"Table1", map[string]int{"b": 1}}}, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | printTable(tt.args.caption, tt.args.m) 66 | }) 67 | } 68 | } 69 | 70 | func Test_sortedMap(t *testing.T) { 71 | type args struct { 72 | m map[string]int 73 | } 74 | tests := []struct { 75 | name string 76 | args args 77 | want []string 78 | want1 []string 79 | }{ 80 | {"Sort map", args{map[string]int{"a": 2, "b": 1}}, []string{"a", "b"}, []string{"2", "1"}}, 81 | } 82 | for _, tt := range tests { 83 | t.Run(tt.name, func(t *testing.T) { 84 | got, got1 := sortedMap(tt.args.m) 85 | if !reflect.DeepEqual(got, tt.want) { 86 | t.Errorf("sortedMap() got = %v, want %v", got, tt.want) 87 | } 88 | if !reflect.DeepEqual(got1, tt.want1) { 89 | t.Errorf("sortedMap() got1 = %v, want %v", got1, tt.want1) 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tools/go.mod: -------------------------------------------------------------------------------- 1 | module tools 2 | 3 | go 1.23.0 4 | 5 | replace github.com/forensicanalysis/artifactcollector => ./.. 6 | 7 | require ( 8 | github.com/forensicanalysis/artifactcollector v0.0.0-00010101000000-000000000000 9 | github.com/looplab/tarjan v0.1.0 10 | github.com/olekukonko/tablewriter v0.0.5 11 | gopkg.in/yaml.v2 v2.4.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/djherbis/times v1.5.0 // indirect 17 | github.com/forensicanalysis/fslib v0.15.2 // indirect 18 | github.com/go-ole/go-ole v1.2.4 // indirect 19 | github.com/google/uuid v1.3.0 // indirect 20 | github.com/hashicorp/golang-lru v0.5.4 // indirect 21 | github.com/mattn/go-runewidth v0.0.10 // indirect 22 | github.com/rivo/uniseg v0.2.0 // indirect 23 | github.com/stretchr/testify v1.6.1 // indirect 24 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 // indirect 25 | www.velocidex.com/golang/go-ntfs v0.1.1 // indirect 26 | ) 27 | -------------------------------------------------------------------------------- /tools/go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= 2 | github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= 3 | github.com/alecthomas/colour v0.1.0 h1:nOE9rJm6dsZ66RGWYSFrXw461ZIt9A6+nHgL7FRrDUk= 4 | github.com/alecthomas/colour v0.1.0/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= 5 | github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c h1:MVVbswUlqicyj8P/JljoocA7AyCo62gzD0O7jfvrhtE= 6 | github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= 7 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/djherbis/times v1.5.0 h1:79myA211VwPhFTqUk8xehWrsEO+zcIZj0zT8mXPVARU= 13 | github.com/djherbis/times v1.5.0/go.mod h1:5q7FDLvbNg1L/KaBmPcWlVR9NmoKo3+ucqUA3ijQhA0= 14 | github.com/forensicanalysis/fslib v0.15.2 h1:c09Pnm31vtSZgj6EiHGauMCcFuNiP/d3D5Qy4ZPfhKA= 15 | github.com/forensicanalysis/fslib v0.15.2/go.mod h1:7xuslRTRu/B0apdl4u1QV/qlbyN8PY93mtcUk8w4s88= 16 | github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI= 17 | github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= 18 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 19 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 20 | github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 21 | github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= 22 | github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= 23 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 24 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 25 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 26 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 27 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 28 | github.com/looplab/tarjan v0.1.0 h1:LycVSLuqFNb6S/RKau7inixXD0K45fTlZ0Zy9WTtwzw= 29 | github.com/looplab/tarjan v0.1.0/go.mod h1:3CvPAapzS1ESsE1ql7MXbad+ENQS21SsfatXQpXFqEo= 30 | github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= 31 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 32 | github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 33 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 34 | github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= 35 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 36 | github.com/olekukonko/tablewriter v0.0.0-20180912035003-be2c049b30cc/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= 37 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 38 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 42 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 43 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 44 | github.com/sebdah/goldie v0.0.0-20180424091453-8784dd1ab561/go.mod h1:lvjGftC8oe7XPtyrOidaMi0rp5B9+XY/ZRUynGnuaxQ= 45 | github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77 h1:Msb6XRY62jQOueNNlB5LGin1rljK7c49NLniGwJG2bg= 46 | github.com/sebdah/goldie v0.0.0-20190531093107-d313ffb52c77/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= 47 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 48 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 50 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 51 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 52 | github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 53 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 54 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 55 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 56 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150 h1:xHms4gcpe1YE7A3yIllJXP16CMAGuqwO2lX1mTyyRRc= 57 | golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 58 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 59 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 60 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 61 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 62 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 63 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 64 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 65 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 66 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 67 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 68 | www.velocidex.com/golang/go-ntfs v0.1.1 h1:///bh0pjoKMHC8x/Q1yaIm2Wa2UNOYwmVti9PsR3i9A= 69 | www.velocidex.com/golang/go-ntfs v0.1.1/go.mod h1:1sqoU8u2Jchwiqsbz4yMSq061wEAOcyXhTCfm6Gz3Lk= 70 | -------------------------------------------------------------------------------- /tools/resources/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 omeid 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tools/resources/README.md: -------------------------------------------------------------------------------- 1 | # go-resources 2 | 3 | > [!IMPORTANT] 4 | > You should use the `embed` package instead of this tool. 5 | > See https://pkg.go.dev/embed for more information. 6 | > 7 | > Only use this tool if you need to support older (< 1.16) versions of Go. 8 | 9 | go-resources is a tool to embed files into Go source code. 10 | 11 | ## Installation 12 | 13 | ```sh 14 | go install github.com/forensicanalysis/go-resources@latest 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```sh 20 | resources -h 21 | Usage resources: 22 | -output filename 23 | filename to write the output to 24 | -package name 25 | name of the package to generate (default "main") 26 | -tag tag 27 | tag to use for the generated package (default no tag) 28 | -trim prefix 29 | path prefix to remove from the resulting file path in the virtual filesystem 30 | -var name 31 | name of the variable to assign the virtual filesystem to (default "FS") 32 | ``` 33 | 34 | ## Optimization 35 | 36 | Generating resources result in a very high number of lines of code, 1MB 37 | of resources result about 5MB of code at over 87,000 lines of code. This 38 | is caused by the chosen representation of the file contents within the 39 | generated file. 40 | 41 | Instead of a (binary) string, `resources` transforms each file into an 42 | actual byte slice. For example, a file with content `Hello, world!` will 43 | be represented as follows: 44 | 45 | ``` go 46 | var FS = map[string][]byte{ 47 | "/hello.txt": []byte{ 48 | 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 49 | 0x21, 50 | }, 51 | } 52 | ``` 53 | 54 | While this seems wasteful, the compiled binary is not really affected. 55 | _If you add 1MB of resources, your binary will increase 1MB as well_. 56 | 57 | However, compiling this many lines of code takes time and slows down the 58 | compiler. To avoid recompiling the resources every time and leverage the 59 | compiler cache, generate your resources into a standalone package and 60 | then import it, this will allow for faster iteration as you don't have 61 | to wait for the resources to be compiled with every change. 62 | 63 | ``` sh 64 | mkdir -p assets 65 | resources -var=FS -package=assets -output=assets/assets.go your/files/here 66 | ``` 67 | 68 | ``` go 69 | package main 70 | 71 | import "importpath/to/assets" 72 | 73 | func main() { 74 | data, ok := assets.FS["your/files/here"] 75 | // ... 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /tools/resources/cmd.go: -------------------------------------------------------------------------------- 1 | // Unfancy resources embedding with Go. 2 | 3 | package main 4 | 5 | import ( 6 | "flag" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | var ( 15 | pkgName = "main" 16 | varName = "FS" 17 | tag = "" 18 | out = "" 19 | trimPath = "" 20 | ) 21 | 22 | type nope struct{} 23 | 24 | func main() { 25 | t0 := time.Now() 26 | 27 | flag.StringVar(&pkgName, "package", pkgName, "`name` of the package to generate") 28 | flag.StringVar(&varName, "var", varName, "`name` of the variable to assign the virtual filesystem to") 29 | flag.StringVar(&tag, "tag", tag, "`tag` to use for the generated package (default no tag)") 30 | flag.StringVar(&out, "output", out, "`filename` to write the output to") 31 | flag.StringVar(&trimPath, "trim", trimPath, "path `prefix` to remove from the resulting file path in the virtual filesystem") 32 | flag.Parse() 33 | 34 | if out == "" { 35 | flag.PrintDefaults() 36 | log.Fatal("-output is required.") 37 | } 38 | 39 | config := Config{ 40 | Pkg: pkgName, 41 | Var: varName, 42 | Tag: tag, 43 | } 44 | 45 | res := New() 46 | res.Config = config 47 | 48 | files := make(map[string]nope) 49 | 50 | for _, g := range flag.Args() { 51 | matches, err := filepath.Glob(g) 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | 56 | for _, m := range matches { 57 | info, err := os.Stat(m) 58 | 59 | if !os.IsNotExist(err) && !info.IsDir() { 60 | files[m] = nope{} 61 | } 62 | } 63 | } 64 | 65 | for path := range files { 66 | name := filepath.ToSlash(path) 67 | name = strings.TrimPrefix(name, trimPath) 68 | 69 | err := res.AddFile(name, path) 70 | if err != nil { 71 | log.Fatal(err) 72 | } 73 | } 74 | 75 | if err := res.Write(out); err != nil { 76 | log.Fatal(err) 77 | } 78 | 79 | log.Printf("Finished in %v. Wrote %d resources to %s", time.Since(t0), len(files), out) 80 | } 81 | -------------------------------------------------------------------------------- /tools/resources/resources.go: -------------------------------------------------------------------------------- 1 | // Package resources provides unfancy resources embedding with Go. 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | "text/template" 13 | ) 14 | 15 | // File mimicks the os.File and http.File interface. 16 | type File interface { 17 | io.Reader 18 | Stat() (os.FileInfo, error) 19 | } 20 | 21 | // New creates a new Package. 22 | func New() *Package { 23 | return &Package{ 24 | Config: Config{ 25 | Pkg: "resources", 26 | Var: "FS", 27 | }, 28 | Files: make(map[string]File), 29 | } 30 | } 31 | 32 | // Config defines some details about the output file. 33 | type Config struct { 34 | Pkg string // Pkg holds the package name 35 | Var string // Var holds the variable name for the virtual filesystem 36 | Tag string // Tag may hold an optional build tag, unless empty 37 | } 38 | 39 | // A Package describes a collection of files and how they should be transformed 40 | // to an output. 41 | type Package struct { 42 | Config 43 | Files map[string]File 44 | } 45 | 46 | // Add a file to the package at the give path. 47 | func (p *Package) Add(name string, file File) error { 48 | name = filepath.ToSlash(name) 49 | p.Files[name] = file 50 | 51 | return nil 52 | } 53 | 54 | // AddFile is a helper function that adds the files from the path into the 55 | // package under the path file. 56 | func (p *Package) AddFile(name, path string) error { 57 | f, err := os.Open(path) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | return p.Add(name, f) 63 | } 64 | 65 | // Build compiles the package and writes it into an io.Writer. 66 | func (p *Package) Build(out io.Writer) error { 67 | return pkg.Execute(out, p) 68 | } 69 | 70 | // Write builds the package (via Build) and writes the output the file 71 | // given by the path argument. 72 | func (p *Package) Write(path string) error { 73 | err := os.MkdirAll(filepath.Dir(path), 0700) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | f, err := os.Create(path) 79 | if err != nil { 80 | return err 81 | } 82 | 83 | defer func() { 84 | err := f.Close() 85 | if err != nil { 86 | log.Panicf("Failed to close file: %s", err) 87 | } 88 | }() 89 | 90 | return p.Build(f) 91 | } 92 | 93 | var ( 94 | // Template. 95 | pkg *template.Template 96 | 97 | // BlockWidth allows to adjust the number of bytes per line in the generated file. 98 | BlockWidth = 12 99 | ) 100 | 101 | func reader(input io.Reader, indent int) (string, error) { 102 | var ( 103 | buff bytes.Buffer 104 | strbuf strings.Builder 105 | isString bool 106 | err error 107 | curblock = 0 108 | linebreak = "\n" + strings.Repeat("\t", indent) 109 | ) 110 | 111 | b := make([]byte, BlockWidth) 112 | isString = true 113 | 114 | for n, e := input.Read(b); e == nil; n, e = input.Read(b) { 115 | for i := range n { 116 | if isString { 117 | if isGoASCII(rune(b[i])) { 118 | strbuf.WriteByte(b[i]) 119 | } else { 120 | isString = false 121 | } 122 | } 123 | 124 | _, e = fmt.Fprintf(&buff, "0x%02x,", b[i]) 125 | if e != nil { 126 | err = e 127 | 128 | break 129 | } 130 | 131 | curblock++ 132 | if curblock < BlockWidth { 133 | buff.WriteRune(' ') 134 | 135 | continue 136 | } 137 | 138 | buff.WriteString(linebreak) 139 | 140 | curblock = 0 141 | } 142 | } 143 | 144 | if isString { 145 | return "[]byte(`" + strbuf.String() + "`),", err 146 | } 147 | 148 | return "{" + linebreak + buff.String() + "\n" + strings.Repeat("\t", indent-1) + "},", err 149 | } 150 | 151 | func isGoASCII(b rune) bool { 152 | if ((' ' <= b && b <= '~') || b == '\n' || b == '\t' || b == '\r') && b != '`' { 153 | return true 154 | } 155 | 156 | return false 157 | } 158 | 159 | func init() { 160 | pkg = template.Must(template.New("file").Funcs(template.FuncMap{"reader": reader}).Parse(fileTemplate)) 161 | pkg = template.Must(pkg.New("pkg").Parse(pkgTemplate)) 162 | } 163 | 164 | const fileTemplate = `{{ reader . 4 }}` 165 | 166 | const pkgTemplate = `// Code generated by github.com/forensicanalysis/go-resources. DO NOT EDIT. 167 | 168 | {{ if .Tag }}// +build {{ .Tag }} 169 | 170 | {{ end }} 171 | package {{ .Pkg }} 172 | 173 | var {{ .Var }} = map[string][]byte{ {{range $path, $file := .Files }} 174 | "/{{ $path }}": {{ template "file" $file }}{{ end }} 175 | } 176 | ` 177 | -------------------------------------------------------------------------------- /tools/resources/resources_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/forensicanalysis/go-resources/testdata/generated" 10 | ) 11 | 12 | //go:generate go build -o testdata/resources . 13 | //go:generate testdata/resources -package generated -output testdata/generated/store_prod.go testdata/*.txt testdata/*.sql testdata/*.bin 14 | 15 | func TestGenerated(t *testing.T) { 16 | t.Parallel() 17 | 18 | for _, tt := range []struct { 19 | name string 20 | }{ 21 | {name: "test.txt"}, 22 | {name: "patrick.txt"}, 23 | {name: "query.sql"}, 24 | {name: "123.bin"}, 25 | {name: "12.bin"}, 26 | } { 27 | t.Run(tt.name, func(t *testing.T) { 28 | t.Parallel() 29 | 30 | content, ok := generated.FS["/testdata/"+tt.name] 31 | 32 | if !ok { 33 | t.Fatalf("expected no error opening file") 34 | } 35 | 36 | data, err := os.ReadFile(filepath.Join("testdata", tt.name)) 37 | if err != nil { 38 | t.Fatal(err) 39 | } 40 | 41 | if !bytes.Equal(content, data) { 42 | t.Errorf("expected to find snippet '%x', got: '%x'", data, content) 43 | } 44 | }) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tools/resources/testdata/12.bin: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /tools/resources/testdata/123.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/forensicanalysis/artifactcollector/45be3ed54d4bbb69fac87b22206242c0b63a801e/tools/resources/testdata/123.bin -------------------------------------------------------------------------------- /tools/resources/testdata/patrick.txt: -------------------------------------------------------------------------------- 1 | is this is test.txt? 2 | no, this is patrick! 3 | -------------------------------------------------------------------------------- /tools/resources/testdata/query.sql: -------------------------------------------------------------------------------- 1 | create table if not exist "files"; 2 | drop table "files"; 3 | -------------------------------------------------------------------------------- /tools/resources/testdata/test.txt: -------------------------------------------------------------------------------- 1 | this is test.txt 2 | -------------------------------------------------------------------------------- /tools/yaml2go/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Siemens AG 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | // this software and associated documentation files (the "Software"), to deal in 5 | // the Software without restriction, including without limitation the rights to 6 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | // the Software, and to permit persons to whom the Software is furnished to do so, 8 | // subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | // 20 | // Author(s): Jonas Plum 21 | 22 | package main 23 | 24 | import ( 25 | "fmt" 26 | "io" 27 | "log" 28 | "os" 29 | "os/exec" 30 | "path/filepath" 31 | "strings" 32 | 33 | "gopkg.in/yaml.v2" 34 | 35 | "github.com/forensicanalysis/artifactcollector/artifacts" 36 | "github.com/forensicanalysis/artifactcollector/collector" 37 | ) 38 | 39 | func artifacts2go(artifactDefinitionFiles []string) ([]artifacts.ArtifactDefinition, error) { 40 | var artifactDefinitions []artifacts.ArtifactDefinition 41 | 42 | for _, artifactDefinitionFile := range artifactDefinitionFiles { 43 | // parse artifact definition yaml 44 | data, err := os.Open(artifactDefinitionFile) // #nosec 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | decoder := yaml.NewDecoder(data) 50 | 51 | for { 52 | artifactDefinition := artifacts.ArtifactDefinition{} 53 | 54 | err := decoder.Decode(&artifactDefinition) 55 | if err == io.EOF { 56 | break 57 | } 58 | 59 | if err != nil { 60 | return nil, fmt.Errorf("decode of %s failed: %w", artifactDefinitionFile, err) 61 | } 62 | 63 | for i := range artifactDefinition.Sources { 64 | artifactDefinition.Sources[i].Parent = artifactDefinition.Name 65 | } 66 | 67 | artifactDefinitions = append(artifactDefinitions, artifactDefinition) 68 | } 69 | } 70 | 71 | return artifactDefinitions, nil 72 | } 73 | 74 | func createGoFile(pkg, name string, objects interface{}) error { 75 | // write go code to assets go 76 | err := os.MkdirAll(pkg, 0750) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | f, err := os.Create(filepath.Join(pkg, name+".generated.go")) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | _, err = fmt.Fprintf(f, "package %s \nvar %s = %#v", pkg, strings.Title(name), objects) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | // add imports 92 | cmd := exec.Command("goimports", "-w", filepath.Join(pkg, name+".generated.go")) // #nosec 93 | 94 | return cmd.Run() 95 | } 96 | 97 | func main() { 98 | configFile := os.Args[1] 99 | 100 | configYaml, err := os.ReadFile(configFile) // #nosec 101 | if err != nil { 102 | log.Fatal(err) 103 | } 104 | 105 | config := &collector.Configuration{} 106 | 107 | err = yaml.Unmarshal(configYaml, config) 108 | if err != nil { 109 | log.Fatal(err) 110 | } 111 | 112 | err = createGoFile("assets", "config", config) 113 | if err != nil { 114 | log.Fatal(err) 115 | } 116 | 117 | var artifactDefinitionFiles []string 118 | 119 | for _, adarg := range os.Args[2:] { 120 | out, err := filepath.Glob(adarg) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | 125 | artifactDefinitionFiles = append(artifactDefinitionFiles, out...) 126 | } 127 | 128 | artifactDefinitions, err := artifacts2go(artifactDefinitionFiles) 129 | if err != nil { 130 | log.Fatal(err) 131 | } 132 | 133 | // decode file 134 | for _, filename := range artifactDefinitionFiles { 135 | if _, _, err := artifacts.DecodeFile(filename); err != nil { 136 | log.Fatal(err) 137 | } 138 | } 139 | 140 | err = createGoFile("assets", "artifacts", artifactDefinitions) 141 | if err != nil { 142 | log.Fatal(err) 143 | } 144 | } 145 | --------------------------------------------------------------------------------