diff --git a/content/artifact_references/pages/generic.forensic.sqlitehunter.md b/content/artifact_references/pages/generic.forensic.sqlitehunter.md index 42f2855d0a2..e6c758d2a24 100644 --- a/content/artifact_references/pages/generic.forensic.sqlitehunter.md +++ b/content/artifact_references/pages/generic.forensic.sqlitehunter.md @@ -16,6 +16,12 @@ in many types of applications: This artifact can hunt for these artifacts in a mostly automated way. More info at https://github.com/Velocidex/SQLiteHunter +NOTE: If you want to use this artifact on just a bunch of files already +collected (for example the files collected using the +Windows.KapeFiles.Targets artifact) you can use the CustomGlob parameter +(for example set it to "/tmp/unpacked/**" to consider all files in the +unpacked directory). +
@@ -33,6 +39,12 @@ description: |
This artifact can hunt for these artifacts in a mostly automated way.
More info at https://github.com/Velocidex/SQLiteHunter
+ NOTE: If you want to use this artifact on just a bunch of files already
+ collected (for example the files collected using the
+ Windows.KapeFiles.Targets artifact) you can use the CustomGlob parameter
+ (for example set it to "/tmp/unpacked/**" to consider all files in the
+ unpacked directory).
+
column_types:
@@ -40,7 +52,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")
@@ -508,13 +520,17 @@ sources:
WHERE Table =~ "Container_"
GROUP BY Table
+ LET S = scope()
+
LET AllHits(OSPath) = SELECT * FROM foreach(row={
SELECT * FROM Containers(OSPath=OSPath)
}, query={
- SELECT timestamp(winfiletime=ExpiryTime) AS ExpiryTime,
- timestamp(winfiletime=ModifiedTime) AS ModifiedTime,
- timestamp(winfiletime=AccessedTime) AS AccessedTime, Url, *
+ SELECT timestamp(winfiletime=S.ExpiryTime || 0) AS ExpiryTime,
+ timestamp(winfiletime=S.ModifiedTime || 0) AS ModifiedTime,
+ timestamp(winfiletime=S.AccessedTime || 0) AS AccessedTime,
+ S.Url AS Url, *
FROM parse_ese(file=OSPath, table=Table)
+ WHERE Url
})
SELECT * FROM foreach(row=MatchingFiles, query={
diff --git a/content/artifact_references/pages/generic.system.hostsfile.md b/content/artifact_references/pages/generic.system.hostsfile.md
new file mode 100644
index 00000000000..a6601fa32c9
--- /dev/null
+++ b/content/artifact_references/pages/generic.system.hostsfile.md
@@ -0,0 +1,134 @@
+---
+title: Generic.System.HostsFile
+hidden: true
+tags: [Client Artifact]
+---
+
+The system hosts file maps hostnames to IP addresses. In some cases,
+entries in this file take precedence and overrides the results from
+the system DNS service.
+
+The file is a simple text file, with one line per IP address. Each
+whitespace-separated word following the IP address is a hostname.
+The Linux man page refers to the the first hostname as *canonical_hostname*,
+and any following words as *aliases*. They are treated the same by this
+artifact.
+
+The hosts file is typically present on all Linux-based systems (including macOS),
+with entries for localhost. The same file format is also supported on Windows.
+
+The source *Hosts* returns each line in each hosts file that matches
+the glob parameters for address and hostname. The hostname and aliases
+are combined in a single column *Hostnames*. Columns returned:
+
+- OSPath
+- Hostnames
+- Comment
+
+Only comments that follows the hostname on the same line are captured in Comment.
+Comments on their own lines are ignored.
+
+A second source *HostsFlattened* provides a flattened result, with each row
+containing an IP address and a single hostname.
+
+This artifact also exports a function `parse_hostsfile()` that returns Hostname
+and Aliases individually.
+
+
+
+name: Generic.System.HostsFile
+description: |
+ The system hosts file maps hostnames to IP addresses. In some cases,
+ entries in this file take precedence and overrides the results from
+ the system DNS service.
+
+ The file is a simple text file, with one line per IP address. Each
+ whitespace-separated word following the IP address is a hostname.
+ The Linux man page refers to the the first hostname as *canonical_hostname*,
+ and any following words as *aliases*. They are treated the same by this
+ artifact.
+
+ The hosts file is typically present on all Linux-based systems (including macOS),
+ with entries for localhost. The same file format is also supported on Windows.
+
+ The source *Hosts* returns each line in each hosts file that matches
+ the glob parameters for address and hostname. The hostname and aliases
+ are combined in a single column *Hostnames*. Columns returned:
+
+ - OSPath
+ - Hostnames
+ - Comment
+
+ Only comments that follows the hostname on the same line are captured in Comment.
+ Comments on their own lines are ignored.
+
+ A second source *HostsFlattened* provides a flattened result, with each row
+ containing an IP address and a single hostname.
+
+ This artifact also exports a function `parse_hostsfile()` that returns Hostname
+ and Aliases individually.
+
+reference:
+ - https://manpages.debian.org/bookworm/manpages/hosts.5.en.html
+
+export: |
+ LET _parse_hostsfile(OSPath) = SELECT parse_string_with_regex(
+ string=Line,
+ regex='''^[\t ]*(?P<Address>[^\s#]+)[\t ]+(?P<Hostname>[^\s#]+)(?P<Aliases>[^#\n\r]+)?(?:[\t ]*#(?P<Comment>.+))?''') AS Parsed
+ FROM parse_lines(filename=OSPath)
+ WHERE Parsed.Address
+
+ LET parse_hostsfile(OSPath) = SELECT Parsed.Address AS Address,
+ Parsed.Hostname AS Hostname,
+ filter(list=split(sep='''\s+''', string=Parsed.Aliases), regex='.') AS Aliases,
+
+ /* Remove any whitespace between comment character and comment: */
+ regex_replace(re='''^\s+''', source=Parsed.Comment, replace='$1') AS Comment
+ FROM _parse_hostsfile(OSPath=OSPath)
+
+ LET Files = SELECT OSPath FROM glob(globs=hostsFileGlobs.HostsFileGlobs)
+
+ LET HostsFiles = SELECT * FROM foreach(row=Files, query={
+ SELECT OSPath, Address, Hostname, Aliases, Comment
+ FROM parse_hostsfile(OSPath=OSPath)
+ })
+
+parameters:
+ - name: hostsFileGlobs
+ description: Globs to find hosts files
+ type: csv
+ default: |
+ HostsFileGlobs
+ C:\Windows\System32\drivers\etc\hosts
+ /etc/hosts
+ - name: HostnameRegex
+ description: Hostname or aliases to match
+ default: .
+ type: regex
+ - name: AddressRegex
+ description: IP addresses to match
+ default: .
+ type: regex
+
+sources:
+ - name: Hosts
+ query: |
+ SELECT OSPath, Address,
+ (Hostname, ) + Aliases AS Hostname,
+ Comment
+ FROM HostsFiles
+ WHERE Hostname =~ HostnameRegex
+ AND Address =~ AddressRegex
+
+ - name: HostsFlattened
+ query: |
+ SELECT OSPath, Address, Hostname, Comment
+ FROM flatten(query={
+ SELECT OSPath, Address, (Hostname, ) + Aliases AS Hostname, Comment
+ FROM HostsFiles
+ })
+ WHERE Address =~ AddressRegex
+ AND Hostname =~ HostnameRegex
+
+
+
diff --git a/content/artifact_references/pages/linux.debian.aptsources.md b/content/artifact_references/pages/linux.debian.aptsources.md
index 2cd3d1ec9b3..ec7043ca4e1 100644
--- a/content/artifact_references/pages/linux.debian.aptsources.md
+++ b/content/artifact_references/pages/linux.debian.aptsources.md
@@ -22,7 +22,7 @@ deb indicates a source for binary packages, and deb-src instructs APT where
to find source code for packages.
`*.sources` files (deb822-style format) are in the form of key–value
-lines, and as opposed to the one–line format, they can contain
+lines, and as opposed to the one–line format, they may contain
multiple URIs, components and types (deb/deb-src), along with
embedded GPG keys. Example:
@@ -33,8 +33,9 @@ Suites: unstable
Components: main contrib non-free
```
-The exported function parse_aptsources(OSPath, flatten) parses
-both formats and returns a (optionally flattened) table with
+The exported function `parse_aptsources(OSPath, flatten)` parses
+both formats and returns an (optionally flattened) table with
+
- OSPath
- Types (deb/deb-src)
- Components (e.g. main/contrib/non-free/restricted,universe)
@@ -43,7 +44,9 @@ both formats and returns a (optionally flattened) table with
- _Transport (e.g. http/https/file/cdrom/ftp)
- URIs (e.g. http://us.archive.ubuntu.com/ubuntu/)
-Any option is added to an individual column. Typical options are
+Any option is added to an individual column. The most common options
+are
+
- Architectures (e.g. amd64/i386/armel)
- Signed-By (e.g. /usr/share/keyrings/osquery.gpg)
@@ -51,22 +54,26 @@ All known option names are transformed to the plural PascalCase
variants as listed in the sources.list man page. Any undocumented
options will still be included in the results, with names unchanged.
Options in the one-line format of the form "lang+=de"/"arch-=i386"
-will be in columns like "Languages-Add"/"Architectures-Remove", matching
-the option names having the same effect in deb822.
+will be put in columns like "Languages-Add"/"Architectures-Remove",
+matching the option names having the same effect in deb822.
Entries in deb822 sources files may be disabled by including
"Enabled: no" instead of commenting out all lines. If this field
is not present with a falsly value, the entry is enabled. Use the
exported functions DebTrue()/DebFalse() to correctly parse all
-accepted true/false strings, or use the VQL suggestion "Enabled"
-to filter on this column (true), if present.
+accepted true/false strings, or use the VQL suggestion "Only enabled
+sources" to filter on this column (true), if present.
If the GPG key is embedded in a .sources file, the whole GPG key
will be included in the cell. Otherwise the value will be a file
-path.
+path. Use the VQL suggestion "Hide embedded GPG keys" to replace
+embedded GPG keys with "(embedded)" in the results. In order to
+inspect the keys themselves (files or embedded data), use the
+exchange artifact Linux.Debian.GPGKeys.
-If flatten is False, multi–value fields (like Components) will
-be combined in a single-space-separated string in each row.
+If the function parameter "flatten" is False, multi–value fields
+(like Components) will be combined in a single space-separated
+string in each row.
In addition to the two apt sources tables, a third table correlates
information from InRelease and Release files to provide additional
@@ -95,7 +102,7 @@ description: |
to find source code for packages.
`*.sources` files (deb822-style format) are in the form of key–value
- lines, and as opposed to the one–line format, they can contain
+ lines, and as opposed to the one–line format, they may contain
multiple URIs, components and types (deb/deb-src), along with
embedded GPG keys. Example:
@@ -106,8 +113,9 @@ description: |
Components: main contrib non-free
```
- The exported function parse_aptsources(OSPath, flatten) parses
- both formats and returns a (optionally flattened) table with
+ The exported function `parse_aptsources(OSPath, flatten)` parses
+ both formats and returns an (optionally flattened) table with
+
- OSPath
- Types (deb/deb-src)
- Components (e.g. main/contrib/non-free/restricted,universe)
@@ -116,7 +124,9 @@ description: |
- _Transport (e.g. http/https/file/cdrom/ftp)
- URIs (e.g. http://us.archive.ubuntu.com/ubuntu/)
- Any option is added to an individual column. Typical options are
+ Any option is added to an individual column. The most common options
+ are
+
- Architectures (e.g. amd64/i386/armel)
- Signed-By (e.g. /usr/share/keyrings/osquery.gpg)
@@ -124,22 +134,26 @@ description: |
variants as listed in the sources.list man page. Any undocumented
options will still be included in the results, with names unchanged.
Options in the one-line format of the form "lang+=de"/"arch-=i386"
- will be in columns like "Languages-Add"/"Architectures-Remove", matching
- the option names having the same effect in deb822.
+ will be put in columns like "Languages-Add"/"Architectures-Remove",
+ matching the option names having the same effect in deb822.
Entries in deb822 sources files may be disabled by including
"Enabled: no" instead of commenting out all lines. If this field
is not present with a falsly value, the entry is enabled. Use the
exported functions DebTrue()/DebFalse() to correctly parse all
- accepted true/false strings, or use the VQL suggestion "Enabled"
- to filter on this column (true), if present.
+ accepted true/false strings, or use the VQL suggestion "Only enabled
+ sources" to filter on this column (true), if present.
If the GPG key is embedded in a .sources file, the whole GPG key
will be included in the cell. Otherwise the value will be a file
- path.
+ path. Use the VQL suggestion "Hide embedded GPG keys" to replace
+ embedded GPG keys with "(embedded)" in the results. In order to
+ inspect the keys themselves (files or embedded data), use the
+ exchange artifact Linux.Debian.GPGKeys.
- If flatten is False, multi–value fields (like Components) will
- be combined in a single-space-separated string in each row.
+ If the function parameter "flatten" is False, multi–value fields
+ (like Components) will be combined in a single space-separated
+ string in each row.
In addition to the two apt sources tables, a third table correlates
information from InRelease and Release files to provide additional
@@ -380,10 +394,6 @@ export: |
columns='Section',
regex='^ #', record_regex='''\n{2,}'''
)
- /* Sections may be empty due to several newlines or comments on their own
- separated by newlines. Ensure that at least one field is present
- (URIs are mandatory): */
- WHERE URIs
LET Deb822_Flattened_(OSPath) = SELECT * FROM foreach(
row=Deb822Sections(OSPath=OSPath),
@@ -394,6 +404,9 @@ export: |
)
})}
)
+ /* DEB822_Sections() may produce empty rows. Exclude these by filtering
+ for a required column, like URIs: */
+ WHERE URIs
/* Parse a deb822 sources file with options in individual columns.
Note that, as opposed to DebOneLine and Deb822_Flattened, this
@@ -406,6 +419,7 @@ export: |
column='Contents'
)}
)
+ WHERE URIs
/* Parse a deb822 sources file with options in individual columns, flattened: */
LET Deb822_Flattened(OSPath) = SELECT * FROM flatten(query={
@@ -461,14 +475,34 @@ sources:
- type: vql_suggestion
name: Only enabled sources
template: |
- SELECT * FROM source(artifact='Custom.Linux.Debian.AptSources/Sources')
- WHERE get(field='Enabled', default='yes') =~ '(?i)^(?:yes|true|with|on|enable)$'
+ /*
+ # Sources (enabled only)
+ */
+ SELECT * FROM source()
+ WHERE Enabled =~ '(?i)^(?:yes|true|with|on|enable)$' || true
- type: vql_suggestion
name: Trusted sources (apt-secure bypassed)
template: |
- SELECT * FROM source(artifact='Custom.Linux.Debian.AptSources/Sources')
- WHERE get(field='Trusted', default='') =~ '(?i)^(?:yes|true|with|on|enable)$'
+ /*
+ # "Trusted" sources (apt-secure bypassed)
+
+ When the Trusted option is true, apt does not verify the GPG
+ signature of the Release files of the repository, and it also
+ doe not warn about this.
+ */
+ SELECT * FROM source()
+ WHERE Trusted =~ '(?i)^(?:yes|true|with|on|enable)$' || false
+
+ - type: vql_suggestion
+ name: Hide embedded GPG keys
+ template: |
+ /*
+ # Sources (embedded GPG keys hidden)
+ */
+ SELECT *, if(condition=get(field='Signed-By')=~'BEGIN PGP PUBLIC KEY',
+ then='(embedded)', else=get(field='Signed-By')) AS `Signed-By`
+ FROM source()
- name: SourcesFlattened
query: |
diff --git a/content/artifact_references/pages/server.internal.tooldependencies.md b/content/artifact_references/pages/server.internal.tooldependencies.md
index 0a033dafe4f..cd0064e7f97 100644
--- a/content/artifact_references/pages/server.internal.tooldependencies.md
+++ b/content/artifact_references/pages/server.internal.tooldependencies.md
@@ -20,19 +20,19 @@ description: |
tools:
- name: VelociraptorWindows
- url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-windows-amd64.exe
+ url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-2-windows-amd64.exe
serve_locally: true
- version: 0.7.0
+ version: 0.7.0-2
- name: VelociraptorWindows_x86
- url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-windows-386.exe
+ url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-2-windows-386.exe
serve_locally: true
- version: 0.7.0
+ version: 0.7.0-2
- name: VelociraptorLinux
- url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-linux-amd64-musl
+ url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-2-linux-amd64-musl
serve_locally: true
- version: 0.7.0
+ version: 0.7.0-2
# On MacOS we can not embed the config in the binary so we use a
# shell script stub instead. See
@@ -44,14 +44,14 @@ tools:
serve_locally: true
- name: VelociraptorWindowsMSI
- url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-windows-amd64.msi
+ url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-2-windows-amd64.msi
serve_locally: true
- version: 0.7.0
+ version: 0.7.0-2
- name: VelociraptorWindows_x86MSI
- url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-windows-386.msi
+ url: https://github.com/Velocidex/velociraptor/releases/download/v0.7.0/velociraptor-v0.7.0-2-windows-386.msi
serve_locally: true
- version: 0.7.0
+ version: 0.7.0-2
diff --git a/content/artifact_references/pages/server.utils.createcollector.md b/content/artifact_references/pages/server.utils.createcollector.md
index d12149d9277..a61a8093aaf 100644
--- a/content/artifact_references/pages/server.utils.createcollector.md
+++ b/content/artifact_references/pages/server.utils.createcollector.md
@@ -357,13 +357,14 @@ parameters:
LET get_ext(filename) = parse_string_with_regex(
regex="(\\.[a-z0-9]+)$", string=filename).g1
- LET temp_binary <= if(condition=matching_tools,
- then=tempfile(
- extension=get_ext(filename=matching_tools[0].Filename),
- remove_last=TRUE,
- permissions=if(condition=IsExecutable, then="x")))
-
- SELECT copy(filename=Filename, accessor="me", dest=temp_binary) AS OSPath,
+ LET FullPath <= if(condition=matching_tools,
+ then=copy(filename=matching_tools[0].Filename,
+ accessor="me", dest=tempfile(
+ extension=get_ext(filename=matching_tools[0].Filename),
+ remove_last=TRUE,
+ permissions=if(condition=IsExecutable, then="x"))))
+
+ SELECT FullPath, FullPath AS OSPath,
Filename AS Name
FROM matching_tools
diff --git a/content/artifact_references/pages/windows.activedirectory.bloodhound.md b/content/artifact_references/pages/windows.activedirectory.bloodhound.md
index 93259cd1ec5..8b35fda1d0a 100644
--- a/content/artifact_references/pages/windows.activedirectory.bloodhound.md
+++ b/content/artifact_references/pages/windows.activedirectory.bloodhound.md
@@ -13,9 +13,6 @@ used to identify and eliminate potentially risky domain configuration.
The Sharphound collection is in json format and upload to the server for
additional processing.
-RemovePayload - Due to potential malicious use of this tool I have also
-included an option to remove payload after execution.
-
NOTE: Do not run this artifact as an unrestricted hunt. General recommendation
is to run this artifact on only a handful of machines in a typical domain,
then deduplicate output.
@@ -33,9 +30,6 @@ description: |
The Sharphound collection is in json format and upload to the server for
additional processing.
- RemovePayload - Due to potential malicious use of this tool I have also
- included an option to remove payload after execution.
-
NOTE: Do not run this artifact as an unrestricted hunt. General recommendation
is to run this artifact on only a handful of machines in a typical domain,
then deduplicate output.
@@ -56,11 +50,6 @@ tools:
type: CLIENT
-parameters:
- - name: RemovePayload
- description: Select to remove payload after execution.
- type: bool
-
sources:
- precondition:
SELECT OS From info() where OS = 'windows'
@@ -73,24 +62,13 @@ sources:
LET payload <= SELECT * FROM Artifact.Generic.Utils.FetchBinary(
ToolName="SharpHound")
-
-- build tempfolder for output
LET tempfolder <= tempdir()
-
-- execute payload
LET deploy = SELECT * FROM execve(argv=[payload.OSPath[0],'--outputdirectory',
tempfolder,'--nozip','--outputprefix',hostname.Fqdn[0] ])
-
- -- remove payload if selected
- LET remove <= SELECT * FROM if(condition=RemovePayload,
- then={
- SELECT * FROM execve(argv=['powershell','Remove-Item',
- payload.OSPath[0],'-Force' ])
- })
-
-
-- output rows
SELECT * FROM if(condition= deploy.ReturnCode[0]= 0,
then={
diff --git a/content/artifact_references/pages/windows.forensics.lnk.md b/content/artifact_references/pages/windows.forensics.lnk.md
index f0508db0547..1bc3df4cf41 100644
--- a/content/artifact_references/pages/windows.forensics.lnk.md
+++ b/content/artifact_references/pages/windows.forensics.lnk.md
@@ -14,10 +14,16 @@ user accesses a file from a supported application or manually created by the use
This artifact has several configurable options:
-- TargetGlob: glob targeting. Default targets *.lnk files in user path.
+- TargetGlob: glob targeting. Default targets *.lnk files in Startup and Recent paths.
- IOCRegex: Regex search on key fields: StringData, TrackerData and PropertyStore.
- IgnoreRegex: Ignore regex filter on key fields.
- UploadLnk: uploads lnk hits.
+- SuspiciousOnly: only returns LNK files reporting a suspicious attribute.
+- SusSize: Any lnk over this size in bytes is suspicious.
+- SusArgSize: Any lnk with Argument strings over this size is suspicious.
+- SusArgRegex: Regex for suspicious strings in Arguments.
+- SusHostnameRegex: Regex for suspicious TrackerData Hostname.
+- CheckHostnameMismatch: Compare TrackerData.MachineID with Hostname (noisy in many networks)
List of fields targeted by filter regex:
@@ -32,7 +38,24 @@ List of fields targeted by filter regex:
- TrackerData.MachineID
- TrackerData.MacAddress
- NOTE: regex startof (^) and endof ($) line modifiers will not work.
+ NOTE: regex startof (^) and endof ($) line modifiers will not work.
+
+
+ Windows.Forensics.Lnk also will highlight suspicious lnk attributes in a Suspicious field.
+
+ * Large Size - default over 20000 bytes
+ * Startup Path - path with \Startup\
+ * Environment variable script - environment vatiable with a common script configured (bat|cmd|ps1|js|vbs|vbe|py)
+ * No Target with environmant variable - environment variable only execution
+ * Suspicious argument size - large sized arguments over 250 characters as default
+ * Arguments have ticks - ticks are common in malicious LNK files
+ * Arguments have environment variables - environment variables (%|\$env:) are common in malicious LNKs
+ * Arguments have rare characters - looks for specific rare characters that may indicate obfuscation (\?|\!|\~|\@)
+ * Arguments have leading space malicious LNK files may have a many leading spaces to obfuscate some tools
+ * Arguments have http strings - LNKs are reguarly used as a download cradle - https?://
+ * Suspicious arguments - some common malicious arguments observed in field (with mind to False positive)
+ * Suspicious hostname - some common malicious hostnames
+ * Hostname mismatch - if selected will compare trackerdata hostname to machine name (lots of FPs)
@@ -49,10 +72,16 @@ description: |
This artifact has several configurable options:
- - TargetGlob: glob targeting. Default targets *.lnk files in user path.
+ - TargetGlob: glob targeting. Default targets *.lnk files in Startup and Recent paths.
- IOCRegex: Regex search on key fields: StringData, TrackerData and PropertyStore.
- IgnoreRegex: Ignore regex filter on key fields.
- UploadLnk: uploads lnk hits.
+ - SuspiciousOnly: only returns LNK files reporting a suspicious attribute.
+ - SusSize: Any lnk over this size in bytes is suspicious.
+ - SusArgSize: Any lnk with Argument strings over this size is suspicious.
+ - SusArgRegex: Regex for suspicious strings in Arguments.
+ - SusHostnameRegex: Regex for suspicious TrackerData Hostname.
+ - CheckHostnameMismatch: Compare TrackerData.MachineID with Hostname (noisy in many networks)
List of fields targeted by filter regex:
@@ -67,7 +96,24 @@ description: |
- TrackerData.MachineID
- TrackerData.MacAddress
- NOTE: regex startof (^) and endof ($) line modifiers will not work.
+ NOTE: regex startof (^) and endof ($) line modifiers will not work.
+
+
+ Windows.Forensics.Lnk also will highlight suspicious lnk attributes in a Suspicious field.
+
+ * Large Size - default over 20000 bytes
+ * Startup Path - path with \Startup\
+ * Environment variable script - environment vatiable with a common script configured (bat|cmd|ps1|js|vbs|vbe|py)
+ * No Target with environmant variable - environment variable only execution
+ * Suspicious argument size - large sized arguments over 250 characters as default
+ * Arguments have ticks - ticks are common in malicious LNK files
+ * Arguments have environment variables - environment variables (%|\$env:) are common in malicious LNKs
+ * Arguments have rare characters - looks for specific rare characters that may indicate obfuscation (\?|\!|\~|\@)
+ * Arguments have leading space malicious LNK files may have a many leading spaces to obfuscate some tools
+ * Arguments have http strings - LNKs are reguarly used as a download cradle - https?://
+ * Suspicious arguments - some common malicious arguments observed in field (with mind to False positive)
+ * Suspicious hostname - some common malicious hostnames
+ * Hostname mismatch - if selected will compare trackerdata hostname to machine name (lots of FPs)
reference:
@@ -75,7 +121,7 @@ reference:
parameters:
- name: TargetGlob
- default: C:\Users\**\*.lnk
+ default: C:\{ProgramData,Users\*\AppData\*}\Microsoft\Windows\{Start Menu\Programs\StartUp,Recent\**}\*.lnk
- name: IocRegex
type: regex
description: A regex to filter on all fields
@@ -85,6 +131,26 @@ parameters:
- name: UploadLnk
description: Also upload the link files themselves.
type: bool
+ - name: SuspiciousOnly
+ description: Only returns LNK files reporting a suspicious attribute
+ type: bool
+ - name: SusSize
+ description: Any lnk over this size in bytes is suspicious.
+ default: 20000
+ type: int
+ - name: SusArgSize
+ default: 250
+ description: Any lnk with Argument strings over this size is suspicious.
+ type: int
+ - name: SusArgRegex
+ description: Regex for suspicious strings in Argumetns.
+ default: \\AppData\\|\\Users\\Public\\|\\Temp\\|comspec|&cd&echo| -NoP | -W Hidden | [-/]decode | -e.* (JAB|SUVYI|SQBFAFgA|aWV4I|aQBlAHgA)|start\s*[\\/]b|\.downloadstring\(|\.downloadfile\(|iex
+ - name: SusHostnameRegex
+ description: Regex for suspicious TrackerData Hastname.
+ default: ^(Win-|Desktop-|Commando$)
+ - name: CheckHostnameMismatch
+ description: Compare TrackerData.MachineID with Hostname (noisy in many networks)
+ type: bool
export: |
LET Profile = '''
@@ -1264,6 +1330,8 @@ export: |
sources:
- query: |
+ LET hostname <= if(condition=CheckHostnameMismatch, then={ SELECT Hostname FROM info()})
+
LET targets = SELECT OSPath, Mtime,Atime,Ctime,Btime,Size,
read_file(filename=OSPath,offset=0,length=2) as _Header
FROM glob(globs=TargetGlob)
@@ -1354,15 +1422,37 @@ sources:
join(array=PropertyStore.Value,sep='\n')
]) =~ IgnoreRegex,
else= False)
+
+ LET add_suspicious = SELECT *, dict(
+ `Large Size` = SourceFile.Size > SusSize,
+ `Startup Path` = SourceFile.OSPath =~ '''\\Startup\\''',
+ `Environment variable script` = ExtraData.EnvironmentVariable =~ '''\.(bat|cmd|ps1|js|vbs|vbe|py)$''',
+ `No Target with environmant variable` = ExtraData.EnvironmentVariable AND StringData.Arguments AND NOT (StringData.TargetPath OR StringData.RelativePath),
+ `Suspicious argument size` = len(list=StringData.Arguments) > SusArgSize,
+ `Arguments have ticks` = StringData.Arguments=~'''\^''',
+ `Arguments have environment variables` = StringData.Arguments=~'''\%|\$env:''',
+ `Arguments have rare characters` = StringData.Arguments=~'''\?\!\~\@''',
+ `Arguments have leading space` = StringData.Arguments =~ '^ ',
+ `Arguments have http strings` = StringData.Arguments =~'''https?://''',
+ `Suspicious arguments` = StringData.Arguments =~ SusArgRegex,
+ `Suspicious hostname` = ExtraData.TrackerData.MachineID AND SusHostnameRegex AND ExtraData.TrackerData.MachineID=~SusHostnameRegex AND NOT lowcase(string=ExtraData.TrackerData.MachineID)=~lowcase(string=hostname[0].Hostname),
+ `Hostname mismatch` = CheckHostnameMismatch AND ExtraData.TrackerData.MachineID AND NOT lowcase(string=ExtraData.TrackerData.MachineID)=~lowcase(string=hostname[0].Hostname)
+ ) as Suspicious
+ FROM results
+ WHERE if(condition=SuspiciousOnly,
+ then= join(array=Suspicious) =~ ':true',
+ else= True )
LET upload_results = SELECT *,
upload(file=SourceFile.OSPath) as UploadedLnk
- FROM results
+ FROM add_suspicious
- SELECT *
+ -- finally return rows and remove suspicious attributes that are not true
+ SELECT *,
+ to_dict(item={SELECT * FROM items(item=Suspicious) WHERE _value = True}) as Suspicious
FROM if(condition=UploadLnk,
then= upload_results,
- else= results )
+ else= add_suspicious )
column_types:
- name: SourceFile.Mtime
diff --git a/content/artifact_references/pages/windows.forensics.partitiontable.md b/content/artifact_references/pages/windows.forensics.partitiontable.md
index 8f2f422c056..7cb43e72098 100644
--- a/content/artifact_references/pages/windows.forensics.partitiontable.md
+++ b/content/artifact_references/pages/windows.forensics.partitiontable.md
@@ -37,6 +37,14 @@ parameters:
- name: SectorSize
type: int
default: 512
+ - name: MagicRegex
+ type: regex
+ description: Filter partitions by their magic
+ default: .
+ - name: NameRegex
+ type: regex
+ description: Filter partitions by their magic
+ default: .
export: |
LET MBRProfile = '''[
@@ -141,22 +149,41 @@ sources:
FROM foreach(row=PrimaryPartitions.PrimaryPartitions)
WHERE start_sec > 0
- SELECT StartOffset, EndOffset, Size, name, {
- SELECT OSPath.Path AS OSPath
- FROM glob(globs="/*",
- accessor="raw_ntfs",
- root=pathspec(
- DelegateAccessor="offset",
- DelegatePath=pathspec(
- DelegateAccessor="raw_file",
- DelegatePath=ImagePath,
- Path=format(format="%d", args=StartOffset))))
- } AS TopLevelDirectory,
+ -- Handle the correct partition types
+ LET GetAccessor(Magic) =
+ if(condition=Magic =~ "NTFS", then="raw_ntfs",
+ else=if(condition=Magic =~ "FAT", then="fat"))
+
+ LET ListTopDirectory(PartitionPath, Magic) =
+ SELECT * FROM if(condition=GetAccessor(Magic=Magic), then={
+ SELECT OSPath.Path AS OSPath
+ FROM glob(globs="/*",
+ accessor=GetAccessor(Magic=Magic),
+ root=PartitionPath)
+ })
+
+ LET PartitionList = SELECT StartOffset, EndOffset, Size, name,
magic(accessor="data", path=read_file(
accessor="raw_file",
filename=ImagePath,
- offset=StartOffset, length=10240)) AS Magic
+ offset=StartOffset, length=10240)) AS Magic,
+
+ -- The OSPath to access the partition
+ pathspec(
+ DelegateAccessor="offset",
+ DelegatePath=pathspec(
+ DelegateAccessor="raw_file",
+ DelegatePath=ImagePath,
+ Path=format(format="%d", args=StartOffset))) AS _PartitionPath
FROM chain(a=PARTS, b=GPT)
+ WHERE name =~ NameRegex
+ AND Magic =~ MagicRegex
+
+ SELECT StartOffset, EndOffset, Size, name,
+ ListTopDirectory(Magic=Magic,
+ PartitionPath= _PartitionPath).OSPath AS TopLevelDirectory,
+ Magic, _PartitionPath
+ FROM PartitionList
diff --git a/content/artifact_references/pages/windows.forensics.sam.md b/content/artifact_references/pages/windows.forensics.sam.md
index 19b5d9da022..1e2b8997843 100644
--- a/content/artifact_references/pages/windows.forensics.sam.md
+++ b/content/artifact_references/pages/windows.forensics.sam.md
@@ -125,10 +125,11 @@ export: |
]
'''
-sources:
- - precondition:
- SELECT OS From info() where OS = 'windows'
+precondition:
+ SELECT OS From info() where OS = 'windows'
+sources:
+ - name: Parsed
query: |
SELECT Key.OSPath.Path AS Key,
Key.OSPath.DelegatePath AS Hive,
@@ -145,6 +146,15 @@ sources:
accessor="raw_reg")
WHERE _F AND _V
+ - name: CreateTimes
+ description: "Show the modified times of the \\SAM\\Domains\\Account\\Users\\Names keys"
+ query: |
+ SELECT Name AS Username, Mtime AS CreatedTime
+ FROM glob(globs='SAM\\Domains\\Account\\Users\\Names\\*',
+ root=pathspec(DelegatePath=SAMPath),
+ accessor="raw_reg")
+ WHERE Data.type =~ "Key"
+
column_types:
- name: F
type: hex
diff --git a/content/artifact_references/pages/windows.forensics.uefi.md b/content/artifact_references/pages/windows.forensics.uefi.md
new file mode 100644
index 00000000000..14d76a28d45
--- /dev/null
+++ b/content/artifact_references/pages/windows.forensics.uefi.md
@@ -0,0 +1,47 @@
+---
+title: Windows.Forensics.UEFI
+hidden: true
+tags: [Client Artifact]
+---
+
+Searches for an EFI partition in the current partition table and
+enumerates all files in it.
+
+
+
+name: Windows.Forensics.UEFI
+description: |
+ Searches for an EFI partition in the current partition table and
+ enumerates all files in it.
+
+parameters:
+ - name: ImagePath
+ default: "\\\\?\\GLOBALROOT\\Device\\Harddisk0\\DR0"
+ description: Raw Device for main disk containing partition table to parse.
+ - name: SectorSize
+ type: int
+ default: 512
+
+sources:
+- query: |
+ SELECT * FROM foreach(row={
+ SELECT *, Size AS PartitionSize
+ FROM Artifact.Windows.Forensics.PartitionTable(
+ ImagePath=ImagePath, SectorSize=SectorSize)
+ WHERE name =~ "EFI"
+
+ }, query={
+ SELECT StartOffset, PartitionSize, name,
+ OSPath.Path AS OSPath, Size, Mtime, Atime, Ctime, Data
+ FROM glob(globs="/*",
+ accessor="fat",
+ root=pathspec(
+ DelegateAccessor="offset",
+ DelegatePath=pathspec(
+ DelegateAccessor="raw_file",
+ DelegatePath=ImagePath,
+ Path=format(format="%d", args=StartOffset))))
+ })
+
+
+
diff --git a/content/artifact_references/pages/windows.network.netstatenriched.md b/content/artifact_references/pages/windows.network.netstatenriched.md
index 807caf7de1e..c245c23f322 100644
--- a/content/artifact_references/pages/windows.network.netstatenriched.md
+++ b/content/artifact_references/pages/windows.network.netstatenriched.md
@@ -10,11 +10,11 @@ enables verbose search options.
Examples include: Process name and path, authenticode information or
network connection details.
-WARNING:
-KillProcess - attempts to use Taskill to kill the processes returned.
-DumpProcess - dumps the process as a sparse file for post processing.
+WARNING:
+KillProcess - attempts to use Taskill to kill the processes returned.
+DumpProcess - dumps the process as a sparse file for post processing.
-Please only use these switches after scoping as there are no guardrails on
+Please only use these switches after scoping as there are no guardrails on
shooting yourself in the foot.
@@ -27,17 +27,17 @@ description: |
Examples include: Process name and path, authenticode information or
network connection details.
-
- WARNING:
- KillProcess - attempts to use Taskill to kill the processes returned.
- DumpProcess - dumps the process as a sparse file for post processing.
-
- Please only use these switches after scoping as there are no guardrails on
+
+ WARNING:
+ KillProcess - attempts to use Taskill to kill the processes returned.
+ DumpProcess - dumps the process as a sparse file for post processing.
+
+ Please only use these switches after scoping as there are no guardrails on
shooting yourself in the foot.
required_permissions:
- EXECVE
-
+
precondition: SELECT OS From info() where OS = 'windows'
parameters:
@@ -101,7 +101,7 @@ parameters:
- name: ProcessNameRegex
description: "regex search over source process name"
- default: ^malware\.exe$
+ default: ^(malware\.exe|.*)$
type: regex
- name: ProcessPathRegex
description: "regex search over source process path"
@@ -150,7 +150,7 @@ parameters:
- name: KillProcess
description: "WARNING: If selected will attempt to kill process from all results."
type: bool
-
+
sources:
- name: Netstat
query: |
@@ -191,13 +191,13 @@ sources:
FamilyString as Family,
TypeString as Type,
Status,
- Laddr.IP as SrcIP,
+ Laddr.IP as SrcIP,
Laddr.Port as SrcPort,
- Raddr.IP as DestIP,
+ Raddr.IP as DestIP,
Raddr.Port as DestPort,
Timestamp
FROM netstat()
- WHERE
+ WHERE
Name =~ ProcessNameRegex
AND Path =~ ProcessPathRegex
and CommandLine =~ CommandLineRegex
@@ -216,7 +216,7 @@ sources:
or format(format="%v", args=DestIP) =~ IPRegex )
and ( format(format="%v", args=SrcPort) =~ PortRegex
or format(format="%v", args=DestPort) =~ PortRegex )
-
+
LET Regions(Pid) = SELECT dict(Offset=Address, Length=Size) AS Sparse
FROM vad(pid=Pid)
WHERE Protection =~ "r"
@@ -228,19 +228,19 @@ sources:
DelegatePath=format(format="/%d", args=Pid)),
name=pathspec(Path=format(format="%d.dd", args=Pid))) AS ProcessMemory
FROM results
- LET kill = SELECT *, pskill(pid=Pid) AS KillProcess
+ LET kill = SELECT *, pskill(pid=Pid) AS KillProcess
FROM results
- LET dumpandkill = SELECT *, pskill(pid=Pid) AS KillProcess
+ LET dumpandkill = SELECT *, pskill(pid=Pid) AS KillProcess
FROM dump
-
+
SELECT * FROM switch(
- a = {
+ a = {
SELECT *, if(condition= KillProcess=Null,then='Success',else=KillProcess) AS KillProcess
FROM if(condition= DumpProcess AND KillProcess, then= dumpandkill )},
b = { SELECT * FROM if(condition= DumpProcess, then= dump )},
- c = {
+ c = {
SELECT *, if(condition= KillProcess=Null,then='Success',else=KillProcess) AS KillProcess
- FROM if(condition= KillProcess, then= kill)
+ FROM if(condition= KillProcess, then= kill)
},
catch = results
)
diff --git a/content/artifact_references/pages/windows.system.vad.md b/content/artifact_references/pages/windows.system.vad.md
index b5b08a09094..8c7af548b45 100644
--- a/content/artifact_references/pages/windows.system.vad.md
+++ b/content/artifact_references/pages/windows.system.vad.md
@@ -14,7 +14,7 @@ or by content with yara.
NOTE:
- ProtectionChoice is a choice to filter on section protection. Default is
-all sections and ProtectionRegex can override selection.
+all sections and ProtectionRegex can override selection.
- To filter on unmapped sections the MappingNameRegex: ^$ can be used.
@@ -30,9 +30,9 @@ description: |
or by content with yara.
NOTE:
-
+
- ProtectionChoice is a choice to filter on section protection. Default is
- all sections and ProtectionRegex can override selection.
+ all sections and ProtectionRegex can override selection.
- To filter on unmapped sections the MappingNameRegex: ^$ can be used.
parameters:
@@ -72,7 +72,7 @@ sources:
- query: |
-- firstly find processes in scope
LET processes = SELECT int(int=Pid) AS Pid,
- Name, Exe, CommandLine, CreateTime
+ Name, Exe, CommandLine, StartTime
FROM process_tracker_pslist()
WHERE Name =~ ProcessRegex
AND format(format="%d", args=Pid) =~ PidRegex
@@ -82,7 +82,7 @@ sources:
LET sections = SELECT * FROM foreach(
row=processes,
query={
- SELECT CreateTime as ProcessCreateTime,Pid, Name,MappingName ,
+ SELECT StartTime as ProcessCreateTime,Pid, Name, MappingName,
format(format='%x-%x', args=[Address, Address+Size]) AS AddressRange,
Address as _Address,
State,Type,ProtectionMsg,Protection,
diff --git a/static/artifact_reference/data.json b/static/artifact_reference/data.json
index 9b43ff174bd..ed9080ae3d1 100644
--- a/static/artifact_reference/data.json
+++ b/static/artifact_reference/data.json
@@ -278,6 +278,15 @@
"Client Artifact"
]
},
+ {
+ "title": "Generic.System.HostsFile",
+ "description": "The system hosts file maps hostnames to IP addresses. In some cases,\nentries in this file take precedence and overrides the results from\nthe system DNS service.",
+ "link": "/artifact_references/pages/generic.system.hostsfile",
+ "type": "client",
+ "tags": [
+ "Client Artifact"
+ ]
+ },
{
"title": "Generic.System.ProcessSiblings",
"description": "This artifact queries the process tracker to display all known\nsibling processes of the target process (i.e. all other processes\nfrom the same parent).",
@@ -2519,6 +2528,15 @@
"Client Artifact"
]
},
+ {
+ "title": "Windows.Forensics.UEFI",
+ "description": "Searches for an EFI partition in the current partition table and\nenumerates all files in it.\n",
+ "link": "/artifact_references/pages/windows.forensics.uefi",
+ "type": "client",
+ "tags": [
+ "Client Artifact"
+ ]
+ },
{
"title": "Windows.Forensics.UserAccessLogs",
"description": "Parse and collect the SUM database",