Been some time since the last post — if you missed me, i love you, and i hope that what I'm about to write will help you somehow (:
Recently i have been studying and researching more about C2 implant development, specifically on old and new evasion techniques. EDR and defense tradecraft improve, so attackers need to do the same. In this post i want to talk about some known ideas and disclose new ones for call stack spoofing on Windows, with and without CET.
PREVIOUS RESEARCH // 00
The GOAT in this topic from what i have been able to research is KlezVirus — he was the one behind the well known SilentMoonwalk and extensive researches on CET compliant spoofing that we will be looking at in a few minutes.
Another goated resource is DarkVortex, the creator of Brute Ratel C2 — here he shows how to abuse callbacks and tail-calls to hide the original caller from the stack trace.
The last one i feel like mentioning is LoudSunRun, who has done an amazing work simplifying the quite complex SilentMoonwalk implementation.
BASICS // 01
All functions need to allocate some memory on the stack in order to properly operate
(except leaf functions); each information stored in that allocated memory can be accessed
using a static offset from RSP or, if the function's UNWIND_INFO foresees it,
using RBP.
What starts the usage of the stack in a function is the prologue — each function has one:
; maybe push some regs (eg: push rbp, r15, r14...)
sub rsp, N
... ; function body
What ends the usage of the stack for a function is the epilogue:
; maybe pop some regs (eg: pop rbp, r15, r14...) add rsp, N ... ret
So each function allocates the needed space onto the stack — this is what we call the frame size of a function.
Every function allocates at least 8 bytes: the ret address, what will be
executed after the ret. The return address will always live above RSP/RBP during the
execution of the function; when that function executes ret, the instruction
will pop the current RSP value inside RIP and jmp to that.
0x1 call func_x
|
|--> save at RSP+x ret addr 0x6
| func_x body execute
| ...
| ret (under the hood: move rsp(0x6) into rip, jmp)
|
V
0x6 [func_x will ret here]
Each ret address resides into the call stack until it's popped out of the stack and executed — this is the call stack: it's just a sequence of "living" ret addresses.
This is a normal looking call stack:
Each thread in the process has its own call stack, and they all start with the same 2 frames: BaseThreadInitThunk (BTIT) and RtlUserThreadStart (RUTS). Every entry in the stack has its own allocated frames from a backed and legitimate dll.
What makes a call stack look bad and suspicious?
As you can see we have unbacked shellcode frames — those are bad because if our
shellcode needs to call some sensitive API (eg: LoadLibrary,
VirtualAlloc...) an EDR could unwind the call stack and directly see those
frames coming from a shellcode memory region. At the same time the BTIT and RUTS frames
have disappeared, because the unwinder doesn't know how to unwind the shellcode frames
and completely messes the stack appearance (more on that later).
Ideally a proper spoofing would try to conceal those frames while being able to execute normally — without crashing or losing anything.
SYNTHETIC FRAMES SPOOFING // 02
I have started by looking more into non CET-compliant techniques, specifically SilentMoonwalk and LoudSunRun.
As a brief introduction: we can place fake frames on the call stack before
tail-jumping to the sensitive API. In this way we are actually creating a 100%
artificial call stack. The fake call stack starts being created when we do a
push 0 — this causes the call stack to be "cut" and all the previous frames
(loader/shellcode) will be gone.
What fake frames do we need to place?
- RUTS
- BTIT
- Thread Start Address
- Desync JOP gadget
As we discussed earlier, the RUTS and BTIT frames are always present — a call stack without them would be anomalous. The thread start address is something not a lot of people consider... if you take a look at all of your call stacks and compare the 3rd entry to the Thread Start Address of the thread hosting that call stack, you will notice they always share the same address.
We will search the desync gadget inside external DLLs such as kernel32, ntdll, or by
manually loading one we know provides the right gadget. The purpose of the desync gadget
is to return the execution flow to the main program; most of the time it will have this
layout: JMP [NON_VOL] — and we place the address of a fixup routine inside
NON_VOL. We call it desync gadget because the JOP gadget is capable of desyncing the
execution of the CPU from the unwinding of the unwinder.
And this is how the call stack appears:
This technique is reliable and fully functional, but very well known — some EDRs are writing rules specifically to detect this type of spoofing.
As an example, this Elastic detection rule looks if we are loading an internet module via Synthetic Stack Spoofing by specifically searching for well known kernel32 functions which have a JOP gadget in it:
this blog really goes in-depth about synthetic stack spoof detections and evasion tradecraft.
Doing a brief list of strong IoCs of this technique:
- CALL prefixed desync gadget
- a frame actually containing a JOP gadget with a non-volatile register
All IoCs could be used as a detection mechanism, but we as attackers are on the right side, because at the end of the day all the detections rely on monitoring the call stack we are spoofing.
How can we address those IoCs? The first one is quite simple: we just search a
CALL-prefixed JOP gadget and use that as the desync frame — in our code we will just go
+0x5 (size of the CALL instruction) to start the frame from the actual JOP
instruction; this way an EDR looking at that frame will see a CALL instruction before
the JOP gadget, marking that frame as a valid return address area. Use
this tool to
search the DLLs of a directory for specific JOP gadgets.
The second one is more subtle and where i had most of the trouble addressing it. It is simple to write a rule that detects a JOP gadget using a non-volatile register, so we must change this architecture somehow.
After some researching i was able to find an actual gadget that could help address the second IoC. The thing is that an EDR would inspect the call stack at the time a suspicious API is called, not after it returns. So the idea is to somehow hide the desync gadget during that time and execute it after the API rets.
A push non_vol; ret could work — non_vol will hold the address of the desync
gadget and the ret will execute it. This successfully hides the JOP gadget by replacing
it with a ROP one (a less scrutinized gadget and host dll). This architecture can still
be improved by not using a non-volatile register; however, doing so requires building a
chain that allows the pointer to the fixup routine to be brought into a volatile
register — hard to do, especially with ROP + call stack spoofing or a full JOP chain.
Those are examples of good candidates for what we want to do:
UNWINDING INTERNALS // 03
Let's talk about how the stack and unwinding work on Windows x64.
When we talk about unwinding we refer to the mechanism the OS uses to handle exceptions: when an exception is raised the OS needs to see where it happened, and for that it uses the call stack, walking it backward until finding the faulty function.
main()
|- func_1()
| |- func_2()
| | |- VirtualAlloc()
| | | goes into kbase, ntdll...
Each frame here is storing a return address at a fixed offset from the current RSP. This offset is what we call frame size — how much the prologue of the function frame has allocated. Knowing the frame size of each frame we can unwind the call stack cleanly.
On Windows there are 2 critical APIs that handle the unwinding: RtlLookupFunctionEntry
and RtlVirtualUnwind — you can read an open source implementation inside the
ReactOS source code.
Every PE file has a .pdata section; the compiler puts an Exception directory
inside it and fills it with RUNTIME_FUNCTION structures, one for each
non-leaf function in the PE.
// Source - https://stackoverflow.com/a/55896238 // Posted by Lewis Kelsey, modified by community typedef struct _RUNTIME_FUNCTION { ULONG BeginAddress; ULONG EndAddress; ULONG UnwindData; } RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;
The BeginAddress points at the start of the function, the EndAddress
points at the end of it, and UnwindData points to an _UNWIND_INFO structure.
// Source - https://stackoverflow.com/a/55896238 typedef struct _UNWIND_INFO { UBYTE Version : 3; UBYTE Flags : 5; UBYTE SizeOfProlog; UBYTE CountOfCodes; UBYTE FrameRegister : 4; UBYTE FrameOffset : 4; UNWIND_CODE UnwindCode[1]; union { OPTIONAL ULONG ExceptionHandler; // if UNW_FLAG_EHANDLER OPTIONAL ULONG FunctionEntry; // if UNW_FLAG_CHAININFO }; OPTIONAL ULONG ExceptionData[]; // if UNW_FLAG_EHANDLER } UNWIND_INFO, *PUNWIND_INFO;
UnwindCode represents the actions that the prologue of the corresponding
function does; some examples:
UWOP_SET_FPREG— frame pointer (RBP) is usedUWOP_ALLOC_SMALL/UWOP_ALLOC_LARGE— stack frame sizeUWOP_PUSH_NONVOL— register was pushed
The unwinding, at a high level, works like this:
RtlLookupFunctionEntry(ctx.Rip, &ImageBase, NULL)— scan the module's .pdata and find the corresponding RUNTIME_FUNCTION covering ctx.RipRtlVirtualUnwind— reads the UNWIND_INFO and interprets the unwind codes- applies the unwind codes to compute the caller's RSP and so on
CET ENFORCEMENT // 04
CET is a mitigation mostly against Return Oriented Programming and other control flow hijack attacks (COP, JOP). CET enforcement works in 2 ways:
- Shadow Stack
- Indirect Branch Tracking (IBT)
SHADOW STACK
All the stacks inside a process have an alter-ego corresponding shadow stack — this
shadow stack is read-only and protected by the kernel. Whenever the CPU executes a
CALL or RET to push/pop a value onto the stack, the shadow stack creates a
corresponding entry inside itself. When a return address mismatch occurs, the system
triggers a crash in order to prevent the exploit or malware from working.
As you may have already understood, this is a problem for the desync gadget implementation: by the time the program rets into that synthetically placed gadget, the shadow stack will throw an error because it doesn't match with what it contains.
You can read more about CET here and here.
INDIRECT BRANCH TRACKING (IBT)
This is less concerning for what we are talking about, but worth giving a look. IBT
operates via a processor state machine that monitors indirect CALL and JMP
instructions. Upon encountering such an instruction, the state machine enters a
"WAIT_FOR_ENDBRANCH" mode, requiring the subsequent instruction to be an
ENDBRANCH (either ENDBR32 or ENDBR64), which marks valid targets.
Read more here.
CET COMPLIANT SPOOFING // 05
With CET out of the way we can talk about how to make stack spoofing compliant to it. The main idea is that stack spoofing doesn't aim to hijack the control flow of the program, but just to make a plausible call stack to evade EDR inspection after an API is called.
An implant will usually have some modules extension; they function like external small programs that amplify the basics of the implant. We call those BoF (based on Cobalt Strike) and they perform stuff like lsass dumping, privesc, loading other payloads...
Those modules are often PIC shellcodes retrieved and loaded by the main beacon, or they
could be normal COFF objects (.o / .obj). When using COFF objects the
beacon needs to handle the in-memory loading of it — this means working with the
sections of the COFF object and relocations, exactly as the linker would do.
COFF loading and COFF structure are outside of this discussion — at the end you will find many good resources.
I have talked about this because the idea is that the beacon will have a
COFF loader (this one slightly
modified); this COFF loader will retrieve and load the extension by using
module stomping on a sacrificial DLL. After that we will fix the call stack of
the extension by registering it to a RUNTIME_FUNCTION table inside the
.pdata of the hollowed DLL. This will theoretically be able to spoof the
extension call stack.
What I'm trying to accomplish here is very similar to what DreamWalkers tries to do — go check it out.
We are doing module stomping in order to have frames originating from a legitimate DLL. Module stomping by itself will add some IoCs, so to make it FUD you should do some research on how to evade those and apply them to this.
Why do we need to register a RUNTIME_FUNCTION to the stomped extension?
This is the call stack with a simple module stomping — as you can see it's quite broken: we have 2 random frames at the bottom and the RUTS/BTIT frames are missing. Why is this happening?
The hollowed DLL (windows.storage.dll) doesn't have the unwind information for
the current stomped memory region (the .text section of the dll)... Actually, i have
said something wrong: it does have unwind information for that region, but it was
for the previous functions that lived there. This mismatch causes the unwinder to
struggle unwinding those frames — and this is the result.
So, in order to register new unwind information corresponding to the loaded extension,
we can use the RtlAddFunctionTable api.
By registering the unwind information we get this:
Now the unwinder can correctly unwind the stack, but we still have the loader frames
inside the stack (in a real scenario those frames come from a beacon shellcode) — we
don't want them. The synthetic approach eliminates those by cutting the stack with the
push 0 instruction and performing the tail-jmp to the API.
The idea for removing those frames is using proxy functions (callbacks): a proxy function is a function that takes as parameter another function (the callback) and executes it in a dedicated thread.
For the proxy function i opted for a simple CreateThreadpoolWork, giving as
callback a tail-jmp trampoline that will jmp inside the stomped DLL.
A tail-jmp trampoline won't push a ret address into the call stack — and it's CET compliant.
This is what we get out of it:
This is looking great: by using a dedicated thread with the callback and trampoline, now the call stack containing the target API doesn't have the loader frames.
But this implementation has a well known IoC: the Tpp* entries. An EDR walking
this stack could see those entries and flag the whole execution chain as malicious, or
scan the originating thread and find the loader frames again.
Like the module stomping IoCs, there are ways to evade even this — however, i will leave this as an exercise for the reader (:
FURTHER IDEAS // 06
An amazing work on novel ways to do call stack spoofing in the CET era was done by KlezVirus here. The main idea is to play with unwind information in order to conceal specific frames from the unwinder.
Something that i would look into more are undocumented Windows APIs such as
RtlCreateUserStack.
CONCLUSION // 07
Both PoC are available in this repo — thank you for coming this long (: