├── .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 |
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 | 
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 |
--------------------------------------------------------------------------------