Threads
A process does nothing by itself, because its threads run the code. Each thread has a start address, the place in memory where it began. A healthy thread starts inside the program or a DLL it loaded, in memory a file on disk backs. A thread that starts in memory no file backs is a lead, since it points to code that arrived some other way or to a just-in-time compiler. Read where each thread starts, and which file is mapped there.
What It Is
A thread is a worker inside a process that runs its code, one step after another. A process does nothing until one of its threads runs.
- Why Windows Needs It
- A program often has several jobs at once, such as answering the keyboard while it saves a file. So Windows lets a process have many threads, and shares the processor's time between them in small slices.
- An Everyday Example
- Notepad's first thread draws its window and answers alice's typing, while Windows' own worker threads, which start in ntdll.dll, do tasks in the background. They all share Notepad's memory, handles and token, and each keeps its own stack and thread ID (TID).
notepad.exe
- Its code, notepad.exe and its DLLs
- Its heap
- Its handles
- Its token
TID 4316
Draws the window and answers alice's typing
Starts In
notepad.exeIts own stack and ID
TID 4320
A Windows worker for tasks in the background
Starts In
ntdll.dllIts own stack and ID
TID 4324
Another Windows worker
Starts In
ntdll.dllIts own stack and ID
- Kept by the Kernel
- The kernel keeps every thread, where it started and any token it carries. Where a thread started is not what it runs now.
Where Threads Start
Tools show two start addresses for a thread. Use the Win32 start address, the code the thread was asked to run, which the Threads tab, Sysmon event 8 and windows.threads' Win32StartAddress column show.
The other, StartAddress in the kernel's thread record, is where Windows itself set the thread going, nearly always inside ntdll.dll, so it tells you little. Never set one against the other.
An address such as spoolsv.exe+0x4B10 means 0x4B10 bytes into spoolsv.exe's code. A thread can also work under another account's token, and Token and Privileges explains impersonating threads.
Image
- Image region: spoolsv.exeTID 3276Win32 Start Address
spoolsv.exe+0x4B10 - Image region: ntdll.dllTID 3304Win32 Start Address
ntdll.dll+0x2A3B0
- Image region: spoolsv.exeTID 3276Win32 Start Address
Private
- Private region at 0x21C4F3A0000, no fileTID 9012Win32 Start Address
0x21C4F3A1A40
- Private region at 0x21C4F3A0000, no fileTID 9012Win32 Start Address
What Normal Looks Like
On a healthy machine this part looks like this.
- Threads start in the program itself or in a DLL it loaded, from System32 or its own folder.
- Many threads start in ntdll.dll, where Windows runs its pools of worker threads.
- Programs create some threads suspended and resume them shortly after.
Why It Matters
Code placed in a process does nothing until a thread runs it. A thread that starts outside every file the process loaded is one of the clearest traces injected code leaves.
Normal and Suspicious
A clean machine sits beside a suspicious one, and the pointer marks where to look.
Normal
A clean Windows 11 install
| TID | Start Address | File at the Start |
|---|---|---|
| TID 2460 | spoolsv.exe | C:\ |
| TID 2464 | ntdll.dll | C:\ |
Suspicious
ws-042,
| TID | Start Address | File at the Start |
|---|---|---|
| TID 3276 | spoolsv.exe | C:\ |
| TID 3304 | ntdll.dll | C:\ |
| TID 9012 | 0x21C4F3A1A40 | None (no file) |
| File at the StartStarts in memory no file backs: a spooler thread starts in spoolsv.exe or a DLL it loaded. Remote Thread Injection | ||
What a Malicious Example Shows
Each card is what malicious use of this part looks like, and where it shows.
- A thread whose start address lies in memory no file backs.Where it shows: windows.
malware. suspicious_ threads, or Sysmon event 8 with an empty StartModule, where Sysmon runs. - A thread one process created in another, unrelated process.Where it shows: Sysmon event 8 from an uncommon source, or Create
Remote Thread Api Call in DeviceEvents. - A thread steered from another process, which changed where it was about to run or gave it extra work to run next.Where it shows: Set
Thread Context Remote Api Call or Queue User Apc Remote Api Call in DeviceEvents. - A thread starting inside a second executable that is not the process's own.Where it shows: windows.
malware. suspicious_ threads notes it, and windows. suspended_ threads lists threads left suspended.
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.
In a memory image, windows.threads lists each thread with both start addresses and the file mapped at each, while windows.malware.suspicious_threads flags the running threads that start outside every file. Live, Windows.System.Threads and Process Explorer show the Win32 start address.
Where does each thread start, and is a file mapped there?
On a Running Machine
Windows.(documentation, opens in a new tab)Live Machine A Velociraptor artifact. It is a ready-made question Velociraptor asks of running machines, named from general to specific. Each thread with its creation time, its Win32 start address and the file mapped there.System. Threads - Process Explorer (documentation, opens in a new tab)Live Machine A free Microsoft tool that works like a detailed Task Manager. It shows each process under the one that started it, with its DLLs and handles. Its Threads tab shows each thread's start address as a module and offset, and its stack on request.
In a Memory Image
windows.threads(documentation, opens in a new tab)Memory Image A Volatility 3 plugin. It is one check run on a saved copy of a Windows machine's memory. Both start addresses with the file at each. Read Win32StartAddress and Win32StartPath.windows.(documentation, opens in a new tab)Memory Image A Volatility 3 plugin from its malware group. It checks a memory image for code that hides, or code put into a process. Threads still running at capture that start in memory no file backs, in a region not created as Execute, write copy, or in a second executable.malware. suspicious_ threads
Did another process start or steer a thread here?
On a Running Machine
Create(documentation, opens in a new tab)Logs and Records A Defender XDR action type, which is one kind of event Defender for Endpoint records in its DeviceEvents table. Defender XDR records a thread one process created in another, with both processes named.Remote Thread Api Call Set(documentation, opens in a new tab)Logs and Records A Defender XDR action type, which is one kind of event Defender for Endpoint records in its DeviceEvents table. Defender XDR records one process setting the registers of another process's thread.Thread Context Remote Api Call
In a Memory Image
windows.(documentation, opens in a new tab)Memory Image A Volatility 3 plugin. It is one check run on a saved copy of a Windows machine's memory. Threads left suspended, with both start addresses.suspended_ threads
Has a thread ended?
On a Running Machine
- PsList (documentation, opens in a new tab)Live Machine A free Microsoft tool that lists processes from the command line.
pslist -dEach process's live threads, so a thread that has ended is no longer listed.
In a Memory Image
windows.thrdscan(documentation, opens in a new tab)Memory Image A Volatility 3 plugin. It is one check run on a saved copy of a Windows machine's memory. thrdscan means thread scan. Scans memory for thread objects, so exited threads can appear, and ExitTime tells them apart.
- PsList (documentation, opens in a new tab)Live Machine A free Microsoft tool that lists processes from the command line.
What It Misses
Every way of looking has gaps, so cover these from another source.
- A start address records where a thread began, not what it runs now. A thread whose registers were set from another process, or that was handed queued work, keeps a normal start. Defender's DeviceEvents table records those as SetThreadContextRemoteApiCall and QueueUserApcRemoteApiCall.
- windows.malware.suspicious_threads ignores threads that have exited, but windows.thrdscan can still find them.
- windows.threads resolves no path for System's (PID 4) threads, so an empty path there is not a finding.
False Positives
These look odd but are normal, so rule them out before you act.
- .NET, Java, browsers and other just-in-time compilers run threads in private memory, so compare with a clean machine of the same build.
- Debuggers, security products and some installers create threads in other processes.
Red Flags It Explains
Each red flag below is confirmed by reading this part.
- Network Connections That Should Not Exist Read the owner's threads and where each one starts. A thread that starts in memory no file backs is a lead to code running inside it.
- Remote Thread Injection Read where each thread starts, live with Windows.System.Threads or in a memory image with windows.threads' Win32StartAddress. Where Sysmon runs, event 8 with an empty StartModule records the same start.
Checks That Use It
These checks read this part as one of their steps.
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. Confirm the action types with DeviceEvents | distinct ActionType, as Microsoft's page lists none. Security products and debuggers do this too, so a rare pair is a lead to read in memory, not proof.
// One process creating, queuing work to or steering a thread in another, rarest pairs firstDeviceEvents| where Timestamp > ago( 30d)| where ActionType in ( "CreateRemoteThreadApiCall", "QueueUserApcRemoteApiCall", "SetThreadContextRemoteApiCall")| where ProcessId != InitiatingProcessId| summarize Devices = dcount( DeviceId), Events = count( ), Actions = make_set( ActionType), FirstSeen = min( Timestamp), LastSeen = max( Timestamp) by InitiatingProcessFileName, InitiatingProcessFolderPath, FileName| order by Devices asc, Events ascSources
- Microsoft Learn: about processes and threads (opens in a new tab)
- Microsoft Learn: multitasking (opens in a new tab)
- Microsoft Learn: when to use multitasking (opens in a new tab)
- Microsoft Learn: !thread debugger command (opens in a new tab)
- Microsoft Learn: asynchronous procedure calls (opens in a new tab)
- Microsoft Learn: troubleshoot issues using Process Explorer (opens in a new tab)
- Microsoft Sysinternals: Sysmon (opens in a new tab)
- Microsoft Learn: DeviceEvents table (opens in a new tab)
- Volatility 3: windows.threads (opens in a new tab)
- Volatility 3: windows.malware.suspicious_threads (opens in a new tab)
- Volatility 3: windows.thrdscan (opens in a new tab)
- Velociraptor: Windows.System.Threads (opens in a new tab)
MITRE ATT&CK is a free public catalogue of the techniques attackers use. Each technique has an ID, such as T1055 for Process Injection, so defenders can name the same behaviour the same way. Reading this part helps find these techniques.
- T1055 (opens in a new tab) Process Injection
- T1055.001 (opens in a new tab) Process Injection: Dynamic-link Library Injection
- T1055.003 (opens in a new tab) Process Injection: Thread Execution Hijacking
- T1055.004 (opens in a new tab) Process Injection: Asynchronous Procedure Call
- T1055.012 (opens in a new tab) Process Injection: Process Hollowing