Windows.Detection.ReflectedAssemblies

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