-
Notifications
You must be signed in to change notification settings - Fork 449
4.9. Scan threads callstack (threads)
This scan provides another layer of shellcode detection, allowing to capture "sleeping beacons", and others, decrypted just before the execution.
Sometimes the implanted shellcodes cannot be detected by patterns. It happens due to various reasons.
- First of all, the number of patterns for which we can scan is limited, and with some effort, it is possible to write shellcode that does not contain them.
- Another case are so-called sleeping beacons. They are shellcode beacons that are kept in a memory in an encrypted/obfuscated form, and decrypted only just before the execution. They are deployed for small periods of time, divided by long periods of sleep. Example: ShellcodeFluctuation. We still have a chance to detect them by patterns: if we scan the process in a loop, eventually we will hit the window of time during which the shellcode is decrypted (example here). However, this is not the best solution if we want to search implants among multiple processes - only when we have some clearly defined suspects.
To cover such cases, and extend the capabilities of shellcode detection, thread scan is added. This type of a scan does not search for patterns, but instead, walks through all the threads and examines their stack frames.
Sometimes, although the shellcode itself is obfuscated (i.e. encrypted, or its memory region made inaccessible), we can find a pointer to it by examining the callstack. For example, by searching for return addresses that lead to unknown memory regions.
Example - list of the consecutive return addresses extracted during thread callstack scan.
> 7ffdbd76d3f4 : ntdll.dll
> 7ffdbb22962e : KERNELBASE.dll
> 7ff6f0661269 : ShellcodeFluctuation64.exe
> 20fa9d1022d
20fa9d1022d <=== SHELLCODE
The suspicious region is dumped for further examination. False positives are possible, so the review of the dumped material is required. Keep in mind that at the time of dumping, the shellcode may be in encrypted form. Once we get suspects, we may further scan the process once again in a loop (like shown here), in order to extract the shellcode in a plaintext form.
In some cases, the callstack is additionally altered / obfuscated, and we won't find the pointer to the shellcode at all. This is what happens in case of more advanced implementations of the Sleeping Beacon, for example, AceLdr by Kyle Avery. Still, the thread scan can help to pinpoint the anomalies in the callstack. The full callstack dump, and the suspicious indicators, are included in the scan report. Example:
[...]
"scans" : [
{
"thread_scan" : {
"status" : 1,
"thread_id" : 8080,
"thread_info" : {
"state" : "Waiting",
"wait_reason" : "UserRequest",
"callstack" : {
"stack_ptr" : "51ff8ffa50",
"frames_count" : 1,
"frames" : ["7ffaaf3826b1;ntdll.dll!RtlUserThreadStart+0x21"]
},
"last_sysc" : "NtSignalAndWaitForSingleObject",
"last_func" : "RtlUserThreadStart"
},
"indicators" : ["SUS_RET", "SUS_CALLS_INTEGRITY", "SUS_CALLSTACK_CORRUPT"],
"susp_return_addr" : "1c988087780"
}
}
]
[...]
Once we are aware what processes contains such anomalies, and of what type, we can narrow down the search, and use some more aggressive scan options, to find the actual payload.
See it in action:
🎬 DEMO: Searching for AceLdr in memory, with PE-sieve/HollowsHunter thread scan