Skip to content
Please update to the latest release 0.77.2 to address Multiple CVEs.
Windows.Memory.DotnetDumper

Windows.Memory.DotnetDumper

Response artifact to triage Windows .NET processes and collect .NET assemblies from memory using DotnetDumper.

Collected output includes recovered .NET assemblies, suspicious managed strings, full managed string listings, loaded module details, and patch detection findings. These results support DFIR workflows focused on inspecting potentially malicious or tampered managed code executing in memory.

The original process dump can optionally be uploaded.

NOTE: this artifact writes a process dump to the Velociraptor temporary directory before analysis. Dump files may be large, similar to standard Velociraptor process dump collection.


name: Windows.Memory.DotnetDumper
author: Matt Green - @mgreen27
description: |
  Response artifact to triage Windows .NET processes and collect .NET assemblies from 
  memory using DotnetDumper.

  Collected output includes recovered .NET assemblies, suspicious managed strings, 
  full managed string listings, loaded module details, and patch detection findings. 
  These results support DFIR workflows focused on inspecting potentially malicious 
  or tampered managed code executing in memory.

  The original process dump can optionally be uploaded.

  NOTE: this artifact writes a process dump to the Velociraptor temporary directory 
  before analysis. Dump files may be large, similar to standard Velociraptor process 
  dump collection.
  
tools:
  - name: DotNetDumper
    url: https://github.com/mgreen27/DotnetDumper/releases/download/continuous/DotnetDumper.exe
    serve_locally: true
    expected_hash: c7cfb900675e5394548ee3c1da39c0d3a365235b40d5566b26d89f22cc4ca3bd
  - name: DotNetDumper_x86
    url: https://github.com/mgreen27/DotnetDumper/releases/download/continuous/DotnetDumper_x86.exe
    serve_locally: true
    expected_hash: 1b7d6fabd150eda572522e3fce351452517abff071d1bbf6346790b5e032fd48
  - name: DotNetDumper_arm64
    url: https://github.com/mgreen27/DotnetDumper/releases/download/continuous/DotnetDumper_arm64.exe
    serve_locally: true
    expected_hash: 4b4f9f4bf42ad4758d4c51e62c7be92267c7f0612ee2f9b6608bc847634eb68e

implied_permissions:
  - EXECVE
  - FILESYSTEM_WRITE
  
precondition: SELECT OS From info() where OS = 'windows'

parameters:
  - name: ProcessRegex
    default: notepad
    type: regex
  - name: PidRegex
    default: .
    type: regex
  - name: EncodeKey
    default: infected
    description: password used when dumping assemblies to bypass AV detection
    type: hidden
  - name: UploadDmp
    type: bool
    description: |
      If specified we upload process dump in addition to Dotnetdumper triage results

sources:
  - query: |
      LET tempfolder <= str(str=tempdir())
      
      LET processes <= SELECT
          Name AS ProcessName,
          ImagePathName AS Exe,
          CommandLine,
          Pid,
          Env.PROCESSOR_ARCHITECTURE AS Type,
          path_join(components=[tempfolder, str(str=Pid)]) AS OutPath,
          parse_pe(file=ImagePathName) AS __PEInfo
        FROM Artifact.Windows.Memory.ProcessInfo(
          ProcessNameRegex=ProcessRegex,
          PidRegex=PidRegex)
        WHERE __PEInfo.Imports =~ 'mscoree|iisutil|mscorwks|clr\.dll' OR __PEInfo.Directories.DotNet_Directory
      
      LET fetch_x64 <= SELECT *
        FROM if(condition=processes.Type =~ "AMD64",
                then={
          SELECT *
          FROM Artifact.Generic.Utils.FetchBinary(ToolName="DotNetDumper")
        },
                else=FALSE)
      
      LET fetch_x86 <= SELECT *
        FROM if(condition=processes.Type =~ "x86",
                then={
          SELECT *
          FROM Artifact.Generic.Utils.FetchBinary(ToolName="DotNetDumper_x86")
        },
                else=FALSE)
      
      LET fetch_arm64 <= SELECT *
        FROM if(condition=processes.Type =~ "ARM64",
                then={
          SELECT *
          FROM Artifact.Generic.Utils.FetchBinary(ToolName="DotNetDumper_arm64")
        },
                else=FALSE)
      
      LET find_arch(type) = get(member=type,
                                item=dict(AMD64=fetch_x64[0].OSPath,
                                          x86=fetch_x86[0].OSPath,
                                          ARM64=fetch_arm64[0].OSPath))
      
      LET collection <= SELECT *
        FROM foreach(row=processes,
                     query={
          SELECT *, Type,
                 ProcessName,
                 Pid,
                 Exe,
                 CommandLine,
                 OutPath
          FROM execve(argv=[find_arch(type=Type), "--pid",
                        Pid, OutPath, "--json", "--encode",EncodeKey],
                      length=999999999)
        })
      
      LET find_dmp(path) = SELECT * FROM glob(globs="*.dmp",root=path)
      
      LET results <= SELECT
          *, parse_json(data=read_file(filename=path_join(
                                         components=[OutPath, "modules.json"]))) AS Modules,
          parse_json(
            data=read_file(
              filename=path_join(
                components=[OutPath, "patch_detection.json"]))) AS PatchDetection
        FROM collection
      
      LET list_assemblies(path) = SELECT
          Name,
          upload(
            accessor='data',
            file=crypto_rc4(key=EncodeKey, string=read_file(filename=OSPath)),
            name=Name ) as Assembly
        FROM glob(globs="assemblies/*", root=path)
      
      LET final = SELECT
          Pid,
          ProcessName,
          CommandLine,
          Type,
          list_assemblies(path=OutPath).Assembly AS Assemblies,
          upload(name="managed_strings_suspicious.txt",
                 file=path_join(
                   components=[OutPath, "managed_strings_suspicious.txt"])) AS SuspiciousManagedStrings,
          upload(
            name="managed_strings_all.txt",
            file=path_join(
              components=[OutPath, "managed_strings_all.txt"])) AS AllManagedStrings,
          OutPath as __Outpath
      FROM results
      
      LET upload_dmp = SELECT *, upload(file=find_dmp(path=__Outpath)[0].OSPath) as Dmp 
        FROM final
        
      SELECT * FROM if(condition= UploadDmp,
                            then= upload_dmp,
                            else= final )

  - name: Modules
    query: |
      SELECT *
      FROM foreach(row=results,
                   query={
          SELECT Pid,
                 ProcessName,
                 *
          FROM Modules.modules
        })
  - name: PatchDetection
    query: |
      LET lookup <= SELECT *
        FROM results
      
      SELECT *
      FROM foreach(row=results,
                   query={
          SELECT Pid,
                 ProcessName,
                 *
          FROM PatchDetection.findings
        })
column_types:
  - name: SuspiciousManagedStrings
    type: preview_upload
  - name: AllManagedStrings
    type: preview_upload
  - name: Dmp
    type: preview_upload