Uroboros
Revisited: Tracing PatchGuard-Evasive Techniques Beyond SSDT Hooking
A
status-based anti-analysis case study on NtProtectVirtualMemory interception in
x64 Windows
“To understand the
immeasurable, the mind must be extraordinarily quiet, still.”
— Jiddu Krishnamurti
By Seeker(李标明) China
Independent Malware Analyst & Researcher
Download the Full Report (PDF)
Poem: It is one way I talk to myself
Paper Boat
Recalling my childhood,
A small paper boat folded on a rainy day,
Sinking as it drifted down the ditch.
From the little wooden door, it floated
Toward the foot of the mountain.
Again and again, I folded those paper boats —
Just I and the boat.
By
Seeker(李标明), 2025.4.1
Prologue: The Temple and the Kernel
Recently, I still
climbed a mountain and visited the temple again and again, as you know from my
last report, the SSDT hooking report
mentioned. Here I keep moving to find more.
After discovering the
SSDT hooking, I have recovered from a bad cold, and I’m happy and very eager
for knowledge and doing deeper research. And it was a nice day, and I was
sitting in a place in the temple and doing internal observation; the lovely
dogs were sitting around me silently; the feeling was just the feeling.
Sample choice: Uroboros
Yeah,
it’s still the nation-state group APT called Turla.
Sample md5: ed785bbd156b61553aaf78b6f71fb37b
Status-Based
Anti-Analysis: High-Risk
and Rare APIs
In fact, to face the
unknown things, I have no solid solution but try to do more trying from
different paths, especially on the background without enough understanding. I
picked some high-risk APIs to test one by one, such as NtLoadDriver,
NtWriteVirtualMemory, and NtQuerySystemInformation. When I debug the API
NtProtectVirtualMemory, it seems very suspicious.
What’s
the kernel API NtProtectVirtualMemory, and what’s the purpose?
NtProtectVirtualMemory
is an internal
Windows system call (syscall) that modifies the memory protection flags of a specified
region in a process's virtual address space. One of its primary purposes is to
allow modifying permissions (read/write/execute) for memory pages. Malware
often uses it to bypass user-mode API monitoring.
disassemble
NtProtectVirtualMemory with L15, These strings “::NNGAKEGL::“ took my attention
again; very possibly compiler-generated strings, it seems not a normal syscall
layout.
Fig.1 the suspicious
strings
It is highly possible
about potential kernel manipulation. The unrelated error codes 0C00000F1h that Should
Not Return This Status, the debug breakpoints “int 3” that are not present in
clean window kernels, and the cross-function jump to NtCreateThreadEx are very
highly suspicious error handling and critical red flags. And especially
comparison with the standard and clean NtProtectVirtualMemory
, it highly confident
this potential inline hooks or
patches
.
Normally,
NtProtectVirtualMemory only returns:
0x00000000
(STATUS_SUCCESS)
0xC0000045
(STATUS_INVALID_PAGE_PROTECTION)
0xC0000005
(STATUS_ACCESS_VIOLATION)
STATUS_SECTION_NOT_EXTENDED (0xC00000F1) is related to
memory section objects, not memory protection.
The above unexpected
NTSTATUS value—0xC00000F1
(STATUS_SECTION_NOT_EXTENDED
)—was
observed. This status code is conventionally tied to NtExtendSection
, where it signals a
failure to extend a section object. Legitimate use of STATUS_SECTION_NOT_EXTENDED
should only
occur inside NtExtendSection
and closely related
memory expansion scenarios.
So we are highly
confident this is a deception trick. Its value is valid, not custom-defined; it
is a legitimacy disguise. It passes if the lazy scanner checks the error code.
Fake a failure that causes tools relying on NtProtectVirtualMemory to misbehave
even to break, which the primary purpose is to anti-debug and anti-EDR; some
EDRs think it just failed, not that it was tampered with.
Totally, it is unexpected NTSTATUS codes, and it's advanced, elegant,
even artistic. I respect the kind of understanding and thinking. It’s amazing. It’s
deception artistic. I temporarily called this being “Status-Based
Anti-Analysis,” and it's clearly crafted to mislead sandboxes or researchers.
I have to make clear the
concept of “status-based anti-analysis.” I’m not sure if it is widely accepted
in the cybersecurity field, but it is
widely recognized and studied by security researchers, reverse
engineers, and defenders as a common
malware tactic.
So we can understand “Status-Based Anti-Analysis” like this:
A stealth technique
where malware deliberately returns valid but misleading NTSTATUS
values to trick sandboxes, EDRs, or analysts into believing an operation failed
or behaved normally — even when it actually succeeded or did something
suspicious.
Fig.2 rare
statue error code and debug breakpoints
Error Handler at
'fffff80002da15f0', that appears to be a shared error-handling stub for
NtProtectVirtualMemory. It loads different NTSTATUS error codes into EAX before
jumping to a common cleanup routine (NtProtectVirtualMemory+0x183). and Cleanup
Routine at fffff80002cfa303, it is a standard function epilogue, and there are
no signs of tampering here.
Fig.3 No signs of
tampering here
PatchGuard: Partial Visibility in Windows 7 x64
Although this suspicious
clue was discovered by now, we still need to know more and confirm it again. So
I went back to the early part of the flow, which is from user mode to kernel
mode.
NtProtectVirtualMemory
is the kernel-mode counterpart to the Win32 API VirtualProtect and is called
via ZwProtectVirtualMemory. The real execution flow is:
Fig.4 NtProtectVirtualMemory execution flow
This means
NtProtectVirtualMemory is entry 0x4D in the SSDT. The syscall number (0x4D) is
passed to KiSystemServiceStart; here it is normal.
Fig.5 snippet nt!ZwProtectVirtualMemory
Deep dive into the API
KiSystemServiceStart, and we have found the same base address that was
mentioned in the last report. As you see, this instruction loads the address of
KeServiceDescriptorTable into register r10. The table KeServiceDescriptorTable holds
the service descriptor table (SDT) for system calls.
Fig.6 snippet nt!KiSystemServiceStart
Following
the base address to explore the structure of KeServiceDescriptorTable and The
highlighted values do not point to valid kernel code addresses, the detail
please view the last report.
Fig.7 Some SSDT entries’s
addresses are suspicious in windows7 x64
Here, it’s important to
make the SSDT clear, as you see in Fig. 7. Yeah, in the last report, I talked
about the “invalid kernel code addresses,” which strongly seems to imply that
some SSDT entries have been hooked or tampered with. Later on, I continue to do
research and think of the two things as follows:
1.
NtProtectVirtualMemory was intercepted; it’s highly confident.
2.
SSDT hooked? Why can some entries not be accessed, and how to jump to
NtProtectVirtualMemory?
With those questions I
continued to deep dive into more detail about the kernel. Many days gone I had
found nothing. One night, I walked down the street and went across different
bridges to do this thing to have a rest and let me have a good pause. And I
also listened to music…
On that day’s second
night, a flashing thought emerged: why not pick the clean kernel to watch?
So I choose a clean
snapshot without the malware to do it again. Likewise, they still have the same
SSDT entries that, like the showing in Fig. 7, seem to be the design of the
system; the SSDT entries are not directly accessible or are
"intentionally" protected. But it is very obvious that some part of
the SSDT entries can still be visible and readable or accessible with kernel
addresses and kernel symbols in Windows 7 x64. Suddenly I understand it is a
design thinking or mechanism, but without full control, I’m not sure if it is a
temporary plan.
I did not stop here but
chose Windows 7 x86 to do it again.
I found it is very different; all SSDT entries are readable or accessible with
kernel addresses and kernel symbols.
Fig.8 Some SSDT entries’s
addresses in windows7 x86
I started to ask myself,
what’s the design or mechanism added from Windows
7 x86 to Windows 7 ×64, and
finally it told me that it is PatchGuard.
PatchGuard is designed to monitor critical kernel structures and detect unauthorized modifications,
but it is not a perfect solution. And about the PatchGuard, I still need to learn more.
PatchGuard makes it
difficult for malware to directly modify the SSDT, but here, combining the
NtProtectVirtualMemory intercepted without touching the SSDT, it is very
obvious. Turla still has abilities to bypass PatchGuard. It is very high skill
and beyond the SSDT hooking.
But,
my friend, here I really seriously ask myself, is it true? 100% confirmed?
When I compared it to
the clean system again. Oh, my god!
It’s still the same; things happened again. the Unusual Behavior in
NtProtectVirtualMemory, In a clean system, NtProtectVirtualMemory should not
have such “debug breakpoints and NTSTATUS error codes” unless some other
protective measure (like PatchGuard) is in place.
Conclusion: It is a malicious driver that has not
overwritten some SSDT entries but added a limited protection mechanism called PatchGuard.
In my previous report, I
explored the foundational aspects of SSDT hooking and found “invalid kernel
code addresses”, but it is the design thinking or protection mechanism called PatchGuard in Windows 7
x64. And the strategic manipulation of NtProtectVirtualMemory and unexpected NTSTATUS
returns, likely used to evade EDR hooks and mimic legitimate kernel behavior.
And it is a more advanced and stealthy
technique than classic SSDT hooking and evasion tech, but it’s still the
protective measure PatchGuard.
Epilogue: What the Kernel Taught Me
1.
There’s no other best solution but to
understand what it is.
2.
The process matters as much as the results.
3.
Scientific research must be serious.
“Do or
do not, there is no try.”
— Master
Yoda
End of Report
Labels: #kernel, #PatchGuard, #SSDT, #Turla, #Uroboros