diff --git a/.gitignore b/.gitignore index e69de29..5a34ca5 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,2 @@ +velociraptor* +sqlitehunter_compiler* \ No newline at end of file diff --git a/Makefile b/Makefile index 4fc8879..5e7f9a7 100644 --- a/Makefile +++ b/Makefile @@ -1,2 +1,13 @@ -all: - go run ./bin > ./output/SQLiteHunter.yaml +all: build windows + +build: + go build -o sqlitehunter_compiler ./bin/*.go + +windows: + GOOS=windows GOARCH=amd64 \ + go build -o sqlitehunter_compiler.exe ./bin/*.go + +compile: FORCE + ./sqlitehunter_compiler -config ./config.yaml -definition_directory ./definitions > output/SQLiteHunter.yaml + +FORCE: diff --git a/README.md b/README.md index e4e8983..81076cc 100644 --- a/README.md +++ b/README.md @@ -68,9 +68,11 @@ application based on the file, and finally analysis of the file: ## How is this repository organized? -The main logic is stored in YAML definitions stored in the `definitions` directory: +The main logic is stored in YAML definitions stored in the +`definitions` directory: -1. `Name`: This is the first part of the artifact source name that will be produced. +1. `Name`: This is the first part of the artifact source name that + will be produced. 2. `Author`,`Email`, `Reference`: Self explanatory. @@ -109,3 +111,264 @@ The main logic is stored in YAML definitions stored in the `definitions` directo within the source they will override the definition. This allows for different sources to be written for different versions of the SQLite tables. + +## Example Development Walk Through + +In the following section I will describe how to add new definitions to +the SQLiteHunter artifact with this repository. Because SQLiteHunter +is a combined artifact that operates on all targets we need to compile +the artifact each time we want to use it. + +The general process for development is: + +1. Obtain a test file (e.g. a SQLite file from a target system or + similar). Store this file in the test_files directory somewhere. +2. Write a definitions file in the definitions directory (more on that later). +3. Compile the artifact using `make compile` or just from the top level + +``` +./sqlitehunter_compiler > output/SQLiteHunter.yaml +``` + +4. Now simply collect the new target using Velociraptor directly. + +Lets work through an example. For this example I will write the `Edge +Browser Navigation History` target from the SQLECmd project. + +### Step 1: Get a sample file. + +This targets the file `C:\Users\User\AppData\Local\Microsoft\Edge\User Data\Default\WebAssistDatabase` so I copy this file into the +test_files directory. It is highly recommended to share a sample file +in your PR in order for automated tests to be built. + +### Writing the definition file. + +I will start off creating a new definition file in the definitions +directory: `EdgeBrowser_NavigationHistory.yaml` + +The file starts off with the common fields: +```yaml +Name: Edge Browser Navigation History +Author: Suyash Tripathi +Email: suyash.tripathi@cybercx.com.au +Reference: https://github.com/EricZimmerman/SQLECmd +``` + +Next I will add the `SQLiteIdentifyQuery` that Velociraptor will run +to determine if this is in fact a `WebAssistDatabase`. A good check +(which is used in the original SQLECmd map is to check if the file +contains a `navigation_history` table. + +```yaml +SQLiteIdentifyQuery: | + SELECT count(*) AS `Check` + FROM sqlite_master + WHERE type='table' + AND name='navigation_history'; +SQLiteIdentifyValue: 1 +``` + +The query is expected to return 1 row. + +Next I will add a new category for this definition. I will give it the +Test category for now so I can isolate just this definition during +development. Normally SQLiteHunter is designed to operate on many +targets automatically which makes it a bit harder to use in +development. This way we can just run a single target using the +`--args All=N --args Test=Y` args. + +```yaml +Categories: + - Edge + - Test + - Browser +``` + +Next we set the file matching filters. These allow Velociraptor to +identify potential files by filename which is a lot faster than having +to read and test every file. Usually the filename is expected to be +`WebAssistDatabase` and it lives in the Edge profile directories. + +The Edge browser is also available on MacOS so we need to add globs +for that. + +```yaml +FilenameRegex: "WebAssistDatabase" +Globs: + - "{{WindowsChromeProfiles}}/*/WebAssistDatabase" + - "{{MacOSChromeProfiles}}/*/WebAssistDatabase" +``` + +Now come the interesting part - we need to add the actual Source for +extracting the data. The SQLiteHunter artifact is structured in a two +pass form - first the SQL is run on the sqlite file, then the +resulting rows are passed through a VQL query which is able to +enrich/post process the data. + +For the moment we just want to add an SQL query that will run on the +SQLite file and simply pass the VQL through unchanged. + +A good start is the SQL from the SQLECmd repository: + +```yaml +Sources: +- name: Navigation History + VQL: | + SELECT * FROM Rows + SQL: | + SELECT + navigation_history.id AS ID, + datetime(navigation_history.last_visited_time, 'unixepoch') AS 'Last Visited Time', + navigation_history.title AS Title, + navigation_history.url AS URL, + navigation_history.num_visits AS VisitCount + FROM + navigation_history + ORDER BY + navigation_history.last_visited_time ASC; +``` + +The VQL part is a simple passthrough query while the SQL part is take +directly from the SQLECmd project. + +### Testing the definition + +We are now ready to test the definition. First compile it with `make +compile`, next test with Velociraptor (from the top level directory): + +``` +make compile && ./velociraptor-v0.7.1-linux-amd64 --definitions ./output/ -v artifacts collect Generic.Forensic.SQLiteHunter --args CustomGlob=`pwd`/test_files/Edge/* --args All=N --args Test=Y +``` + +I you do not want to build the `sqlitehunter_compiler` you can just +download it from the Releases page of this repository and place it at +the top level of the repository - otherwise you can build it from +source using just `make` at the top level. + +This command: +1. Uses the Velociraptor binary appropriate for the platform you are + running on +2. Adds the `--definitions` to get Velociraptor to automatically load + the new artifact (overriding the built in version). +3. Uses the `-v` flag to have detailed logging - you should look for + helpful messages or errors during development. +4. Adds the `CustomGlob` parameter to force the `SQLiteHunter` + artifact to search the `test_files` directory instead of the + system. Leaving this out will force it to search the current system + which may be useful as well. +5. Finally we turn all `All` processing and focus on collecting only + the `Test` category. This can be omitted if the `CustomGlob` is + very specific so other targets are not triggered anyway. The + purpose of this is to just speed up the development cycle. + +Let's look at some of the output on my system: + +```text +[INFO] 2023-12-08T23:10:29Z Globs for category Test is /home/mic/projects/SQLiteHunter/test_files/Edge/* +[INFO] 2023-12-08T23:10:29Z Starting collection of Generic.Forensic.SQLiteHunter/AllFiles +... +[INFO] 2023-12-08T23:10:29Z sqlite: Will try to copy /home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase to temp file +[INFO] 2023-12-08T23:10:29Z sqlite: Using local copy /tmp/tmp210798471.sqlite +[INFO] 2023-12-08T23:10:29Z /home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase was identified as Edge Browser Navigation History_Navigation History +[INFO] 2023-12-08T23:10:29Z sqlite: removing tempfile /tmp/tmp210798471.sqlite +[INFO] 2023-12-08T23:10:29Z /home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase matched by filename WebAssistDatabase +[ + { + "OSPath": "/home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase" + }, + { + "ID": 0, + "Last Visited Time": "2023-08-21 02:13:07", + "Title": "Japanese language - Wikipedia", + "URL": "https://en.wikipedia.org/wiki/Japanese_language", + "VisitCount": 1, + "OSPath": "/home/mic/projects/SQLiteHunter/test_files/Edge/WebAssistDatabase" + }, +``` + +The first logged message shows that selecting the Test category +results in the `CustomGlob` being used (if CustomGlob is not specified +it will resolve to the globs given in the definition file. + +Next we see the sqlite file being copied to a temp file (this is done +to protect the integrity of the SQLite files from changes due to +journaling etc and to avoid locked sqlite files from genering an +error). + +Next we see the file is identified as an `Edge Browser Navigation +History` file based on the `SQLiteIdentifyQuery` query. + +Then we see the rows generated by the SQLite file. + +The output looks almost right but there is a problem - the `Last +Visited Time` timestamp is not formatted correctly as an ISO timestamp +(there is a missing timezone specifier). This is because formatting +times was done using the SQL query but this does not generate correct +timestamps. + +It is generally better to use VQL to format times correctly. Lets fix +this by moving the timestamp formatting code from SQL to VQL: + +```yaml +Sources: +- name: Navigation History + VQL: | + SELECT ID, + timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, + Title, URL, VisitCount + FROM Rows + + SQL: | + SELECT + navigation_history.id AS ID, + navigation_history.last_visited_time AS 'Last Visited Time', + navigation_history.title AS Title, + navigation_history.url AS URL, + navigation_history.num_visits AS VisitCount + FROM + navigation_history + ORDER BY + navigation_history.last_visited_time ASC; +``` + +Now the output is more correct and properly formatted: +``` +{ + "ID": 0, + "Last Visited Time": "2023-08-27T08:02:34Z", + "Title": "Microsoft Edge | What's New", + "URL": "https://www.microsoft.com/en-us/edge/update/116?form=MT00GR\u0026channel=stable\u0026version=116.0.1938.54", + "VisitCount": 1 +}, +``` + +### Time boxing and filtering + +While this works pretty well, we lack the ability to control the +output of the artifact based on filtering or time boxing. The user may +specify the following parameters: `DateAfter`, `DateBefore` and +`FilterRegex` to narrow output in the artifact. + +Each source interprets these contraints in the way that makes sense to +them. In this case we should implement time boxing based on the `Last +Visit Time` and allow the user to filter by Title and URL: + +```yaml +Sources: +- name: Navigation History + VQL: | + SELECT ID, + timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, + Title, URL, VisitCount + FROM Rows + WHERE `Last Visited Time` > DateAfter + AND `Last Visited Time` < DateBefore + AND (Title, URL) =~ FilterRegex +``` + +You can verify these filters work by specifying the parameters on the +command line: + +``` +make compile && ./velociraptor-v0.7.1-linux-amd64 --definitions ./output/ -v artifacts collect Generic.Forensic.SQLiteHunter --args CustomGlob=`pwd`/test_files/Edge/* --args All=N --args Test=Y --args FilterRegex=Audio +``` diff --git a/definitions/EdgeBrowser_NavigationHistory.yaml b/definitions/EdgeBrowser_NavigationHistory.yaml new file mode 100644 index 0000000..1d59d1a --- /dev/null +++ b/definitions/EdgeBrowser_NavigationHistory.yaml @@ -0,0 +1,40 @@ +Name: Edge Browser Navigation History +Author: Suyash Tripathi +Email: suyash.tripathi@cybercx.com.au +Reference: https://github.com/EricZimmerman/SQLECmd +SQLiteIdentifyQuery: | + SELECT count(*) AS `Check` + FROM sqlite_master + WHERE type='table' + AND name='navigation_history'; +SQLiteIdentifyValue: 1 +Categories: + - Edge + - Browser +FilenameRegex: "WebAssistDatabase" +Globs: + - "{{WindowsChromeProfiles}}/*/WebAssistDatabase" + - "{{MacOSChromeProfiles}}/*/WebAssistDatabase" + +Sources: +- name: Navigation History + VQL: | + SELECT ID, + timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, + Title, URL, VisitCount, OSPath + FROM Rows + WHERE `Last Visited Time` > DateAfter + AND `Last Visited Time` < DateBefore + AND (Title, URL) =~ FilterRegex + + SQL: | + SELECT + navigation_history.id AS ID, + navigation_history.last_visited_time AS 'Last Visited Time', + navigation_history.title AS Title, + navigation_history.url AS URL, + navigation_history.num_visits AS VisitCount + FROM + navigation_history + ORDER BY + navigation_history.last_visited_time ASC; diff --git a/output/SQLiteHunter.yaml b/output/SQLiteHunter.yaml index 0da61c2..156c1a0 100644 --- a/output/SQLiteHunter.yaml +++ b/output/SQLiteHunter.yaml @@ -26,7 +26,7 @@ column_types: type: preview_upload export: | - LET SPEC <= "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" + LET SPEC <= "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" LET Specs <= parse_json(data=gunzip(string=base64decode(string=SPEC))) LET CheckHeader(OSPath) = read_file(filename=OSPath, length=12) = "SQLite forma" LET Bool(Value) = if(condition=Value, then="Yes", else="No") @@ -72,7 +72,7 @@ export: | then=OSPath =~ get(item=Specs.sources, field=SourceName).filename) -- Build a regex for all enabled categories. - LET all_categories = SELECT _value FROM foreach(row=["All","MacOS","Chrome","Browser","Firefox","Edge","InternetExplorer","Windows"]) WHERE get(field=_value) + LET all_categories = SELECT _value FROM foreach(row=["All","MacOS","Chrome","Browser","Edge","Firefox","InternetExplorer","Windows"]) WHERE get(field=_value) LET category_regex <= join(sep="|", array=all_categories._value) LET AllGlobs <= filter(list=Specs.globs, condition="x=> x.tags =~ category_regex") LET _ <= log(message="Globs for category %v is %v", args=[category_regex, CustomGlob || AllGlobs.glob]) @@ -130,14 +130,14 @@ parameters: default: N -- name: Firefox - description: Select targets with category Firefox +- name: Edge + description: Select targets with category Edge type: bool default: N -- name: Edge - description: Select targets with category Edge +- name: Firefox + description: Select targets with category Firefox type: bool default: N @@ -445,6 +445,19 @@ sources: +- name: Edge Browser Navigation History_Navigation History + query: | + LET Rows = SELECT * FROM ApplyFile(SourceName="Edge Browser Navigation History_Navigation History") + SELECT ID, + timestamp(epoch=`Last Visited Time`) AS `Last Visited Time`, + Title, URL, VisitCount, OSPath + FROM Rows + WHERE `Last Visited Time` > DateAfter + AND `Last Visited Time` < DateBefore + AND (Title, URL) =~ FilterRegex + + + - name: Firefox Places query: | LET Rows = SELECT * FROM ApplyFile(SourceName="Firefox Places") diff --git a/test_files/Edge/WebAssistDatabase b/test_files/Edge/WebAssistDatabase new file mode 100755 index 0000000..2b0d758 Binary files /dev/null and b/test_files/Edge/WebAssistDatabase differ diff --git a/testing/edge.go b/testing/edge.go new file mode 100644 index 0000000..edfa55b --- /dev/null +++ b/testing/edge.go @@ -0,0 +1,38 @@ +package testing + +import ( + "os" + "path/filepath" + + "github.com/Velocidex/ordereddict" + "github.com/alecthomas/assert" + "github.com/sebdah/goldie/v2" +) + +func (self *SQLiteHunterTestSuite) TestEdgeWebAssistDatabase() { + t := self.T() + + cwd, err := os.Getwd() + assert.NoError(t, err) + + golden := ordereddict.NewDict() + argv := []string{ + "--definitions", "../output", + "query", `LET S = scope() + +SELECT *, "" AS OSPath +FROM Artifact.Generic.Forensic.SQLiteHunter( + FilterRegex=S.FilterRegex, + MatchFilename=FALSE, All=FALSE, Edge=TRUE, CustomGlob=CustomGlob) +`, "--env", "CustomGlob=" + filepath.Join(cwd, "../test_files/Edge/WebAssistDatabase")} + + out, err := runWithArgs(argv, "--env", "FilterRegex=Audio") + golden.Set("FilterRegex=Audio", filterOut(out)) + + g := goldie.New(t, + goldie.WithFixtureDir("fixtures"), + goldie.WithNameSuffix(".golden"), + goldie.WithDiffEngine(goldie.ColoredDiff), + ) + g.AssertJson(t, "TestEdgeWebAssistDatabase", golden) +} diff --git a/testing/fixtures/TestEdgeWebAssistDatabase.golden b/testing/fixtures/TestEdgeWebAssistDatabase.golden new file mode 100644 index 0000000..540d226 --- /dev/null +++ b/testing/fixtures/TestEdgeWebAssistDatabase.golden @@ -0,0 +1,17 @@ +{ + "FilterRegex=Audio": [ + "[][", + " {", + " \"_Source\": \"Generic.Forensic.SQLiteHunter/AllFiles\",", + " },", + " {", + " \"ID\": 2,", + " \"Last Visited Time\": \"2023-08-27T11:00:53Z\",", + " \"Title\": \"How to Record Audio on Windows 10\",", + " \"URL\": \"https://www.lifewire.com/how-to-record-audio-on-windows-10-4773840\",", + " \"VisitCount\": 1,", + " \"_Source\": \"Generic.Forensic.SQLiteHunter/Edge Browser Navigation History_Navigation History\",", + " }", + "]" + ] +} \ No newline at end of file diff --git a/testing/sqlitehunter_test.go b/testing/sqlitehunter_test.go index e0e47b3..16582df 100644 --- a/testing/sqlitehunter_test.go +++ b/testing/sqlitehunter_test.go @@ -53,19 +53,13 @@ func (self *SQLiteHunterTestSuite) findAndPrepareBinary() { } } -func (self *SQLiteHunterTestSuite) TestArtifact() { +func (self *SQLiteHunterTestSuite) TestFirefoxHistory() { t := self.T() cwd, err := os.Getwd() assert.NoError(t, err) golden := ordereddict.NewDict() - - // This file has these dates: - // 2020-06-27T09:29:54.51375Z - // 2020-06-27T09:30:05.721357Z - // 2020-06-30T05:53:37.171Z - // 2021-02-21T08:55:10.488Z argv := []string{ "--definitions", "../output", "query", `LET S = scope() @@ -77,15 +71,20 @@ FROM Artifact.Generic.Forensic.SQLiteHunter( FilterRegex=S.FilterRegex || ".", MatchFilename=FALSE, All=FALSE, Chrome=TRUE, CustomGlob=CustomGlob) WHERE VisitID -`, - "--env", "CustomGlob=" + filepath.Join(cwd, "../test_files/Firefox/*")} +`, "--env", "CustomGlob=" + filepath.Join(cwd, "../test_files/Firefox/*")} + // This file has these dates: + // 2020-06-27T09:29:54.51375Z + // 2020-06-27T09:30:05.721357Z + // 2020-06-30T05:53:37.171Z + // 2021-02-21T08:55:10.488Z out, err := runWithArgs(argv) require.NoError(t, err, out) golden.Set("All Records", filterOut(out)) - out, err = runWithArgs(argv, "--env", "DateAfter=2021-02-20T08:55:10.488Z") + out, err = runWithArgs(argv, + "--env", "DateAfter=2021-02-20T08:55:10.488Z") assert.NoError(t, err, out) golden.Set("After 2021-02-20T08:55:10.488Z should be only 2021-02-21T08:55:10.488Z", @@ -97,7 +96,8 @@ WHERE VisitID golden.Set("DateBefore=2020-06-27T09:30:00Z should be only 2020-06-27T09:29:54.51375Z", filterOut(out)) - out, err = runWithArgs(argv, "--env", "FilterRegex=Firefox Developer Edition") + out, err = runWithArgs(argv, + "--env", "FilterRegex=Firefox Developer Edition") golden.Set("FilterRegex=Firefox Developer Edition", filterOut(out))