Collects current running .NET assemblies and replicates ProcessHacker’s .NET Assemblies view.
By Default, the artifact will filter out assemblies with a ModulePath on disk and built in Modules with Dynamic flag which indicate reflectively loaded assemblies.
Typical workflow would be to run once and scope the target ModuleID the rerun and specifiy ModuleIDRegex to pull back module information.
Queries the Microsoft-Windows-DotNETRuntimeRundown ETW provider so ensure timeout path is sufficient.
name: Windows.Detection.ReflectedAssemblies
author: "Matt Green - @mgreen27"
description: |
Collects current running .NET assemblies and replicates ProcessHacker's .NET
Assemblies view.
By Default, the artifact will filter out assemblies with a ModulePath on disk
and built in Modules with Dynamic flag which indicate reflectively loaded
assemblies.
Typical workflow would be to run once and scope the target ModuleID the rerun
and specifiy ModuleIDRegex to pull back module information.
Queries the Microsoft-Windows-DotNETRuntimeRundown ETW provider so ensure
timeout path is sufficient.
type: CLIENT
parameters:
- name: ProcessRegex
default: .
type: regex
- name: PidRegex
default: .
type: regex
- name: Timeout
default: 60
type: int
- name: AssemblyNameRegex
default: .
type: regex
- name: ModuleIDRegex
description: Regex of ModuleID to return Module Methods. Module Methods will not return unless configured.
type: regex
- name: ModulePathExclusion
default: 'C:\\|\.dll$'
type: regex
- name: ShowAll
description: By default we only show unmapped assemblies. This will ignore ModulePathExclusion to show all.
type: bool
precondition: SELECT OS From info() where OS = 'windows'
sources:
- query: |
LET VQL_MATERIALIZE_ROW_LIMIT <= 27000
LET collected <= SELECT System.ID AS EventID,
System.ProcessID AS ProcessID,
*
FROM watch_etw(description="CLR Rundown Provider",
guid="{A669021C-C450-4609-A035-5AF59AF4DF18}",
any=if(condition= ModuleIDRegex,
then= 0x78,
else= 0x48),
timeout=Timeout)
WHERE ProcessID =~ PidRegex
AND EventID in (151,153,155,157,187,143)
AND log(message="CLR Rundown Provider any=" + if(condition=ModuleIDRegex,
then="0x78",
else="0x48"))
LET ordered = SELECT *,
format(format='%v_%v',
args=[System.ProcessID,System.ID]) AS __OrderMe
FROM collected
WHERE NOT EventID = 143
ORDER BY __OrderMe DESC
LET Cache <= lru()
LET results = SELECT
process_tracker_get(id=ProcessID) AS ProcessDetails,
get(item=Cache,
field=format(format="AppDomainName%v_%v",
args=[ProcessID, EventData.AppDomainID])) ||
get(item=Cache,
field=format(format="AppDomainName%v_%v",
args=[ProcessID, get(
item=Cache,
field=format(
format="AppDomainID%v_%v",
args=[ProcessID, EventData.AssemblyID]))])) AS AppDomainName,
EventData.ModuleID AS ModuleID,
EventData.AssemblyID AS AssemblyID,
EventData.ModuleFlags AS ModuleFlags,
get(
item=Cache,
field=format(
format="FullyQualifiedAssemblyName%v_%v",
args=[ProcessID, EventData.AssemblyID])) AS FullyQualifiedAssemblyName,
EventData.ModuleILPath AS ModuleILPath,
get(
item=Cache,
field=format(
format="ClrDetails%v",
args=ProcessID)) AS ClrDetails
FROM ordered
WHERE (EventID = 157
&& set(
item=Cache,
field=format(
format="AppDomainName%v_%v",
args=[ProcessID, EventData.AppDomainID]),
value=EventData.AppDomainName),
EventID = 155
&& set(
item=Cache,
field=format(
format="FullyQualifiedAssemblyName%v_%v",
args=[ProcessID, EventData.AssemblyID]),
value=EventData.FullyQualifiedAssemblyName),
EventID = 155
&& set(
item=Cache,
field=format(
format="AppDomainID%v_%v",
args=[ProcessID, EventData.AssemblyID]),
value=EventData.AppDomainID),
EventID = 187
&& set(
item=Cache,
field=format(
format="ClrDetails%v",
args=ProcessID),
value=dict(
ClrInstanceID=EventData.ClrInstanceID,
ClrVersion=format(
format='%s.%s.%s.%s',
args=[
EventData.VMMajorVersion,
EventData.VMMinorVersion,
EventData.VMBuildNumber,
EventData.VMQfeNumber]),
RuntimeDllPath=EventData.RuntimeDllPath
)),
EventID = 151,
EventID = 153)
AND EventID IN (151, 153)
AND ProcessDetails.Data.Name =~ ProcessRegex
-- match process hacker ModuleFlags
LET PROFILE = '''[
["ClrDeets", 0, [
["ModuleFlags", 0, "Flags", {
type: "uint32b",
bitmap: {
"DomainNeutral": 0,
"Native": 1,
"Dynamic": 2,
}
}],
]],
]'''
LET hex_value(number) = format(format='0x%08x',args=int(int=number))
LET final <= SELECT
dict(
StartTime=ProcessDetails.StartTime,
Pid=ProcessDetails.Id,
Name=ProcessDetails.Data.Name,
Exe=ProcessDetails.Data.Exe,
CommandLine=ProcessDetails.Data.CommandLine,
Username=ProcessDetails.Data.Username ) AS ProcessDetails,
AppDomainName,
ModuleID,
AssemblyID,
join(array=parse_binary(accessor='data',filename=unhex(string=hex_value(number=ModuleFlags)),
profile=PROFILE,struct='ClrDeets').ModuleFlags,sep=',') as ModuleFlags,
FullyQualifiedAssemblyName,
ModuleILPath,
ClrDetails
FROM results
WHERE NOT ModuleILPath =~ ModulePathExclusion
AND NOT ModuleFlags = 'Dynamic'
AND FullyQualifiedAssemblyName =~ AssemblyNameRegex
GROUP BY Address, AppDomainName, FullyQualifiedAssemblyName
SELECT * FROM final
- name: Module Methods
query: |
LET lookup <= SELECT * FROM final
LET ModuleIDRegex2 <= join(array=final.ModuleID,sep='|')
LET final_lookup(moduleid) = SELECT AppDomainName,AssemblyID,FullyQualifiedAssemblyName
FROM lookup WHERE ModuleID = moduleid
LET module_results = SELECT ProcessID,
process_tracker_get(id=System.ProcessID).Data.Name AS ProcessName,
EventData.MethodID as MethodID,
EventData.ModuleID as ModuleID,
EventData.MethodStartAddress as MethodStartAddress,
EventData.MethodSize as MethodSize,
EventData.MethodToken as MethodToken,
EventData.MethodFlags as MethodFlags,
EventData.MethodNamespace as MethodNamespace,
EventData.MethodName as MethodName,
EventData.MethodSignature as MethodSignature
FROM collected
WHERE System.ID = 143
AND ProcessName =~ ProcessRegex
AND ModuleID =~ ModuleIDRegex
AND ModuleID =~ ModuleIDRegex2
SELECT ProcessID, ProcessName,
final_lookup(moduleid=ModuleID )[0] as AssemblyDetails,
ModuleID,
MethodID,
MethodStartAddress,
MethodSize,
MethodToken,
MethodFlags,
MethodNamespace,
MethodName,
MethodSignature
FROM module_results