Skip to content

Checking for Injected Code

How to Check

Injected code runs inside a genuine process, so its name, path and signature all look right. What gives it away is memory, whether executable memory that no file on disk backs, a thread that starts there, or an image in memory that differs from its file. On a clean machine code runs from images mapped from files, though .NET and other just-in-time compilers and security products make executable memory of their own. So a hit is a lead to dump and examine, never a verdict on its own. Start with the threads, where each one starts, and which file is mapped there.

What It Shows

Code normally runs from images mapped from files, so executable memory that no file backs, a thread that starts in it, or an image in memory that differs from its file all point to code that did not arrive the normal way. These checks read memory, live or in an image, and the thread records Sysmon and Defender keep. They also flag .NET and other just-in-time code and security products, so dump what they find and check it before calling it injection.

Inside Parts It Reads

This check reads these parts, and each page shows what normal looks like.

Records of One Process Reaching Into Another

When one process reaches into another, these records catch it, each from a different sensor. Sysmon writes events 8 and 25 to Microsoft-Windows-Sysmon/Operational on machines running Sysmon, and Defender writes the DeviceEvents rows on devices onboarded to Defender for Endpoint.

RecordFields to ReadWhat It Tells You
Sysmon event 8SourceImage, TargetImage, StartAddress, StartModule, StartFunctionA thread started in another process. Sysmon infers StartModule and StartFunction and can leave them empty when the start lies outside every loaded module, a lead to examine in memory
Sysmon event 25Image, TypeSysmon saw a process image change in memory, the trace hollowing and similar tampering leave
CreateRemoteThreadApiCall, QueueUserApcRemoteApiCall and NtAllocateVirtualMemoryRemoteApiCall in DeviceEventsInitiatingProcessFileName, FileName, ProcessId, InitiatingProcessIdDefender's records of one process starting a thread in, queuing work to, or allocating memory in another. Microsoft names these action types in the portal's schema reference and in the data catalogue it published with its NL2KQL research, so confirm the one you need on your own tenant

Live and in Memory

Each question has the tools that answer it on a running machine, and in a memory image, a copy of its memory saved to a file.

  • Is there executable memory that no file backs?

    On a Running Machine

    In a Memory Image

  • Does a thread start in memory no file backs?

    On a Running Machine

    In a Memory Image

  • Is an image in memory different from its file on disk, or have the Windows functions it calls been changed in memory?

    On a Running Machine

    In a Memory Image

  • Who holds the process open with the rights to write its memory, and does it connect anywhere its job never needs?

    On a Running Machine

    In a Memory Image

What It Misses

Every way of looking has gaps, so cover these from another source.

  • windows.malware.malfind judges a region by the protection it was created with, so it can miss a region whose protection changed later, and it never judges an image region created as Execute, write copy. Pair it with windows.malware.suspicious_threads and Windows.Memory.Mem2Disk, because a clean result never clears a process.
  • A start address records where a thread began, not what it runs now. A thread whose context was set from another process, or that was handed queued work, keeps a normal start, and Defender's SetThreadContextRemoteApiCall and QueueUserApcRemoteApiCall record those. A thread that ended is gone from memory, kept only by Sysmon event 8 or a Defender record.
  • Pages saved to the page file are missing from a raw image unless the page file is added, and pages Windows compresses sit in the Memory Compression process, not the one they belong to. A region you cannot read is not proof it was empty.

False Positives

These look odd but are normal, so rule them out before you act.

  • .NET, Java and other just-in-time compilers write code into memory no file backs, and security products inject into processes to watch them, so compare with a clean machine of the same build before calling it injection.
  • Packed and copy-protected programs unpack themselves in memory and look injected.
  • PE-sieve, HollowsHunter and Get-InjectedThreadEx flag legitimate hooks and generated code too, so treat a hit as a region to dump and examine, not a verdict.

Proves These Red Flags

This check is a step in confirming each sign below.

How Malware Hides in a Process
Remote Thread InjectionProcess Hollowing

Hunt It

The same hunt is written for four tools, so pick the one you use. Defender XDR and Sentinel queries are in KQL, which you paste into Advanced hunting in the Defender portal or into Sentinel's Logs page. Splunk queries are in SPL, for its Search page, and Velociraptor queries are in VQL, which you run as a hunt.

Needs Defender for Endpoint. Microsoft lists DeviceEvents action types in the portal's schema reference and in the data catalogue it published with its NL2KQL research, so confirm this one with DeviceEvents | distinct ActionType before you rely on it. Security products and other legitimate programs start remote threads too, so rare pairs come first, and a hit is a lead to examine in memory.

KQLDefender XDR, Advanced hunting
// One process starting a thread in another (CreateRemoteThreadApiCall), by source and target, rarest pairs firstDeviceEvents| where Timestamp > ago(30d)| where ActionType == "CreateRemoteThreadApiCall" and ProcessId != InitiatingProcessId| summarize Devices = dcount(DeviceId), Threads = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp) by InitiatingProcessFileName, InitiatingProcessFolderPath, FileName| order by Devices asc, Threads asc

Sources