In Windows a common DLL hooking technique is to replace a dll with a forwarder dll - i.e. one that forwards all imports to the real dll. If the forwarder DLL is placed earlier in the import order, the malicous DLL will be seamlessly loaded and injected into another process.
This artifact searches for dlls which are named the same as the DLL they are forwarding to.
name: Windows.Detection.ForwardedImports
description: |
In Windows a common DLL hooking technique is to replace a dll with a
forwarder dll - i.e. one that forwards all imports to the real
dll. If the forwarder DLL is placed earlier in the import order, the
malicous DLL will be seamlessly loaded and injected into another
process.
This artifact searches for dlls which are named the same as the DLL
they are forwarding to.
reference:
- https://github.com/monoxgas/Koppeling
- https://silentbreaksecurity.com/adaptive-dll-hijacking/
- https://www.mdsec.co.uk/2020/10/i-live-to-move-it-windows-lateral-movement-part-3-dll-hijacking/
parameters:
- name: DLLGlob
default: C:\windows\**\*.dll
- name: ExcludeRegex
default: WinSXS|Servicing
type: regex
- name: LogPeriod
type: int
description: How often to log progress in seconds (Default every 1 sec)
default: 1
sources:
- query: |
LET DLLs = SELECT OSPath, Name,
-- Remove the .dll extension if present to get the bare dll filename.
lowcase(string=parse_string_with_regex(
regex="^(?P<BareName>[^.]+)", string=Name).BareName) AS DLLBareName,
count() AS Total
FROM glob(globs=DLLGlob)
WHERE NOT OSPath =~ ExcludeRegex
LET ParsedDLLs = SELECT *,
log(message="Examining %v after checking %v DLLs",
args=[OSPath, Total], dedup= LogPeriod ) AS Log
FROM foreach(
row=DLLs, workers=20,
query={
SELECT OSPath, Name,
parse_pe(file=OSPath).Forwards AS Forwards,
DLLBareName, Total
FROM scope()
})
-- Speed up analysis a bit by using more workers.
SELECT * FROM foreach(row=ParsedDLLs,
query={
SELECT OSPath AS DllPath, ForwardedImport,
-- The Bare DLL Name from the forwarded name
Parse.DllPath AS DllImportPath,
-- The export this is forwarding to.
Parse.Export AS DLLExportFunc,
DLLBareName,
-- The bare dll name the export is referring to.
basename(path=lowcase(string=Parse.DllPath)) AS ExportDLLName
FROM foreach(row=Forwards,
query={
SELECT parse_string_with_regex(
regex="(?P<DllPath>.+)\\.(?P<Export>[^.]+$)",
string=_value) AS Parse,
_value AS ForwardedImport
FROM scope()
})
-- Only flag imports for forwarder dll name the same as its own dll.
WHERE ExportDLLName = DLLBareName
})