Hi gang. Sorry for the huge delay. I was out of town one weekend, and the following weekend I was sick as a dog. Weekdays are usually difficult to get an article out, but I’ve found some time here.
Let’s discuss ROP gadgets.
The concept of ROP is known as “Return Oriented Programming”. The exploitative side of ROP is that you can bypass data execution prevention protocols. “How?” you might ask? We’ll get to that shortly.
Before You Read
There’s no real required reading for this post, but you should at least be familiar with basic Windows process architecture and C++. You should also understand how the stack works, which I’ve discussed in RTMA Part 1.
What
Like I’ve already discussed. ROP is return oriented programming, but what is a gadget? And what does that have to do with exploitation?
The term “gadgets” is what is used to describe small sequences of instructions found in pre-existing programs. Let’s look at one:
C3 ret
Wow how cool was that?
There’s no real limit on gadgets. They can be 1 instruction or they can be 100. As long as they can be used in a ROP technique, and they already exist, they can be dubbed as ROP gadgets.
Since ROP means return oriented, then clearly we’re looking for return statements, a lot like the example I gave earlier. But let’s look further at the concept of gadgets.
Like I said, gadgets lie within pre-existing programs, meaning you didn’t write them, presumably. This means that when executing or using a gadget, the code will execute in a different module/process. If you’re trying to mess with control flow and, of course, the stack (return orientation), then you can use ROP gadgets to trick the stack into going somewhere else entirely. This is popularly done in function calls that have stack arguments. In x86 Windows, each argument is passed in the stack. In x64, only the 5th+ argument(s) is passed in the stack. The first 4 are RBX, RCX, R8, and R9 respectively.
If you’re cool, you can use multiple gadgets at the same time (called a ROP chain). This is essentially a handful of addresses (gadgets) and arguments on the stack where each ROP gadget RETs into the next gadget.
So, to recap and summarize. ROP gadgets are small pieces of code that lie in pre-existing programs/modules that can be invoked or jumped to in order to abuse return-oriented code.
Why
In case you missed it, there was a big “Exploitation” in the title of this post. So, clearly, you would do this for exploitation.
I mentioned data execution prevention (DEP) earlier. In most (hopefully all) EDRs nowadays, it should be very difficult, if not impossible, to execute injected code.
Let’s think about it in steps:
I am malicious code, I want to hijack a process
I inject some more malicious code into this process
I try to run it
Step 3 might be pretty hard. EDRs watch allocated executable code like a hawk (namely anything RWX). It would be very silly to go “okay now dereference it and pray it runs”. You will be caught by the EDR and then visited by people with guns.
What if you used a ROP gadget?
Let’s say you fiddle with the stack a bit and then jump into a part of kernel32.dll which RETs itself into the code you want to run?
Now we’re talking.
The EDR might go “Oh hey kernel32 wants to run this code, it must be okay”.
Isn’t that cool.
How
I think I’m going to show, rather than tell.
A recent and cool example of this is an NtQueueApcThreadEx injection. Let’s quickly break down how it works. This is a POC for x86, by the way.
We need to first inspect the signature of NtQueueApcThreadEx.
typedef
VOID
(*PPS_APC_ROUTINE)(
_In_opt_ PVOID ApcArgument1,
_In_opt_ PVOID ApcArgument2,
_In_opt_ PVOID ApcArgument3
);
NTSTATUS NTAPI NtQueueApcThreadEx(
_In_ HANDLE ThreadHandle,
_In_opt_ HANDLE UserApcReserveHandle,
_In_ PPS_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcArgument1,
_In_opt_ PVOID ApcArgument2,
_In_opt_ PVOID ApcArgument3
);
Ignore the _In_ and _In_opt_ annotations, they don’t really do much but are useful for understanding how the API is intended to work.
Do you remember how APCs work? If not, read this post. Eventually, the routine will be called and it will pass each of those 3 arguments.
Let’s think about what the stack would look like before and after a call to the APC function.
Remember, this is x86!
Before:
0x34A60000 - Unused
0x34A60004 - Unused
0x34A60008 - ApcArgument1
0x34A6000C - ApcArgument2
0x34A60010 - ApcArgument3
0x34A60014 - SomeValue
Remember that the stack pushes values in reverse order.
After:
0x34A60000 - Unused
0x34A60004 - AddressToGoBackTo
0x34A60008 - ApcArgument1
0x34A6000C - ApcArgument2
0x34A60010 - ApcArgument3
0x34A60014 - SomeValue
Now, how can this stack be manipulated with a ROP gadget?
We have 3 fun and random arguments that we can do pretty much anything with, so what is it?
What if there was a way to, instead of RETing to AddressToGoBackTo
, we RET into one of the arguments?
This could be a great idea. The APC routine could be some harmless code (in NTDLL in this case).
If we are able to find a gadget that pops a 32-bit register and then immediately RETs, then we can end up having the APC routine RET into one of the arguments.
This might sound complicated, but all this does is it performs an extra POP instruction to shift the stack up by an extra 4 bytes. This would of course cause a crash in any normal circumstances, but if we have some special shellcode in ApcArgument1, then we will have the stack proper cleanup and continue executing.
And none is the wiser. The APC pointed to legitimate code, what could the EDR do?
That’s all for this post. Thanks for being patient. Have a great rest of your week!
Go!
-BowTiedCrawfish.