0% found this document useful (0 votes)
182 views62 pages

Modern Windows Userspace Exploitation

This document discusses modern Windows userspace exploitation techniques across different Windows versions. It begins by summarizing an Insomni'hack CTF challenge that contains vulnerabilities that can be exploited. It then compares how to exploit the vulnerabilities on Windows 7, Windows 10 TH1, and Windows 10 RS5. On Windows 7, full arbitrary code execution is achieved through heap shaping and ROP. On Windows 10 TH1, bypassing heap randomization and CFG is required. On Windows 10 RS5, bypassing ACG and CIG is needed to achieve code execution due to stronger mitigations.

Uploaded by

ItamarBahar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
182 views62 pages

Modern Windows Userspace Exploitation

This document discusses modern Windows userspace exploitation techniques across different Windows versions. It begins by summarizing an Insomni'hack CTF challenge that contains vulnerabilities that can be exploited. It then compares how to exploit the vulnerabilities on Windows 7, Windows 10 TH1, and Windows 10 RS5. On Windows 7, full arbitrary code execution is achieved through heap shaping and ROP. On Windows 10 TH1, bypassing heap randomization and CFG is required. On Windows 10 RS5, bypassing ACG and CIG is needed to achieve code execution due to stronger mitigations.

Uploaded by

ItamarBahar
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 62

MODERN WINDOWS USERSPACE EXPLOITATION

Saar Amar
MSRC-IL
# whoami

• @AmarSaar

• Security researcher. Live to reverse, breath to exploit.

• MSRC-IL

• Addicted to CTFs!
• @pastenctf team member
The reason to live: Exploit!

• We want to execute arbitrary code (duh…)

• Easy!
• Find an awesome 0day vulnerability

• ???

• Profit!

• Yeah…
Mitigations. Mitigations. Mitigations.
• Control-flow integrity mitigations
• DEP
• CFG
• CFI

• Code integrity mitigations


• ACG
• CIG

• Supporting mitigations
• Child Process Policy
• ASLR
• SafeSEH/SEHOP
• Heap randomization && metadata protection

• Sandboxing / Containers
• LPAC Even more! See https://www.microsoft.com/en-
• WDAG us/msrc/bounty-mitigation-bypass
Exploit through the ages

• aka what is this talk about

• Insomni’hack CTF Teaser as an example vulnerability

• Compare the exploit on different platforms:


• Windows 7

• Windows 10 TH1

• Windows 10 RS5
The “Winworld” challenge

• Awesome challenge by @__awe • Manipulates Person objects

• Story based on “Westworld” – a shared class for:


• Hosts (robots)
• Implements a “narrator” interface,
• Guests (humans)
which lets you
• Create robots and humans
• Configure their behavior
• Move them on the map
• Interact with each other
Vulnerability 1: uninitialized attr in Person copy c’tor

• Robots are initialized with is_conscious=false in the Person’s c’tor

• Person’s copy c’tor used in the narrator clone function skips this initialization!

• The value will thus be uninitialized


• use whatever was already on the heap

• By forcing a robot to become a human, we have is_conscious uninitialized


Vulnerability 2: UAF due to misused std::shared_ptr

• When a robot becomes human, it stays in the robots list


• and gets inserted into the humans list as well!

• Instead of incrementing the refcount of the object’s std::shared_ptr, we create a new


std::shared_ptr that points to the same object
• Due to guests.push_back(std::move(p));

• Therefore, when one of the std::shared_ptr gets to 0 – we have a dangling pointer!


Vulnerability 2: UAF due to misused std::shared_ptr
Vulnerability 2: UAF due to misused std::shared_ptr
global
robot list

friend
old shared_ptr refcount = 3
p->friends[0] before copy ctr

friend
p->friends[1] before copy ctr
Person p

global
humans list

friend
p->friends[0] after copy ctr
new shared_ptr refcount = 4
friend
p->friends[1] after copy ctr

friend
p->friends[2] after copy ctr
Triggering the clone

• The clone logic is triggered when a robot becomes human

• This happens when a robot reaches the center of the maze, using the move command

• And the following conditions must be met:


• Currently moved person must be a host (i.e. robot)

• person->is_conscious is set (!false)

• There is a guest (i.e. human) in the maze center as well

• For that, we need the uninitialized is_conscious vulnerability


LET THE FUN BEGIN
WINDOWS 7
Windows 7 Heap
• Low fragmentation heap (LFH)

• In Windows 7:
• Chunks in userblocks allocated continuously

• Different userblocks allocated continuously

• FreeEntryOffset in free chunks content (first 2 bytes)

• Can redirect next-next allocation to an arbitrary address

• Shaping the LFH is relatively easy


• Spraying chunks of the same size => continuous allocation

• malloc returns the last freed chunk


Predictable. Everything’s predictable
Arbitrary Read

• Person->name is an std::string

• info <id> prints the name of a human/robot

• Arbitrary read:
• Corrupt the std::string pointer and size

• Use info to read an arbitrary number of bytes from an arbitrary address


Arbitrary Write

• Person->name is an std::string

• update <id> name <name> sets a new name

• Arbitrary write:
• Corrupt the std::string pointer and size

• Use update to write arbitrary data to an arbitrary address


Code Execution

• Move two persons to the same location on the map

• A function pointer is called on both:


• this->onEncounter(other)

• Corrupt this function pointer and jump to an arbitrary address


Rule them all

• Corrupt onEncounter and set it to gets:


• this->onEncounter(other) becomes gets(other)

• Which means that on every encounter, we can reset an object

• Much easier than exploiting the UAF every time

• I use it for arbitrary reads, mostly


Breaking ASLR

• We need to leak our image base / ntdll / some libraries for gadgets and functions

• Shape the LFH so an std::vector object will be allocated on a dangling Person after the
UAF

• Execute info on the dangling Person

• Output is the std::vector vtable address, from the base image .rdata

• The Ugly: the process dies right after...


• Libraries VAs are randomized once per boot, and the challenge relaunched ☺
Land of possibilities

• Corrupt vtables / functions pointers

• Corrupt the stack

• Load unsigned DLLs

• Modify or create unsigned code pages:


• VirtualProtect(…, PAGE_EXECUTE, …)

• VirtualAllocEx(…, PAGE_EXECUTE, …)

• Everything works!
Windows 7 Exploit

• From here, ROP

• Start with VirtualAllocEx for a new RWX page

• Write shellcode into the new page

• Jump into it

• PROFIT
WINDOWS 7 DEMO
WINDOWS 10 TH1
Shape fails

• Windows 7 version of the exploit fails on Windows 10 TH1

• Turns out the heap shaping fails

• We never get the same allocation as the dangling Person object


Heap Randomization and Metadata Protection

NAME WORKAROUNDS
• Avoid touching the heap metadata
Heap Randomization and Metadata
• Spray allocations against randomization
Protection

EXPLAINED BOUNTY
The integrity of heap metadata cannot be Up to $15K
subverted and the layout of heap allocations is
not predictable to an attacker
What the RANDOM!
Bypassing LFH randomization on TH1

• How random are these random allocations?

• Seeded from a global random data pool


• ntdll!RtlpLowFragHeapRandomData

• Pool is CRNG random, but constant for the lifetime of the process

• TH1 had an issue that lets us deterministically allocate same or contiguous


chunks in memory

• Details && POC: https://github.com/saaramar/Deterministic_LFH


Bypassing LFH randomization on TH1

x x x
x
0 1 1 1 x
x
0 1 0 0
0 0 1 0
0 1 0 0
Bypassing LFH randomization on TH1

x x x
x
0 1 1 1 x x
x
0 1 0 0
1 0 1 0
0 1 0 0
Bypassing LFH randomization on TH1

x x x
x x
0 1
1
1 1 x x
x
1 1 0 0
1 0 1 0
0 1 0 0
Back to our TH1 exploit

• New LFH shaping strategy for UAF re-allocation

• Should be able to jump to an arbitrary address


• Using the same function point (onEncounter)

• Let’s jump to a function in ntdll…


CFG

NAME WORKAROUNDS
Known bypasses, see The Evolution of CFI Attacks
Control Flow Guard
and Defenses by @JosephBialek

EXPLAINED BOUNTY
Indirect branches are checked against a whitelist of Currently out of scope
targets, and if the check fails – terminate the
process
Windows 10 TH1 Exploit

• Still harder to bypass CFG than to continue with ROP

• Just avoid indirect calls as much as we can


• Still needed for our gets trick, but it is whitelisted

• New exploit:
• Leak stack address
• Corrupt return address with the arbitrary write primitive
• Execute same ROP chain from before
• PROFIT
WINDOWS 10 TH1 DEMO
WINDOWS 10 RS5
Again…

• Windows 10 TH1 version of the exploit fails on Windows 10 RS5

• The LFH randomization is fixed since build 16179

• However, we already have a strong allocation primitive

• How good will a large random spray be?


Still…

• Execute the new exploit on Windows 10 RS5 it still fails

• This time it’s ACG!


ACG

NAME WORKAROUNDS
• Known bypasses on older versions
Arbitrary Code Guard
• Execute code in ROP

EXPLAINED BOUNTY
• Restricts allocating and mapping of +X pages UP TO $45K
• Restricts editing existing +X pages permissions
CIG

NAME WORKAROUNDS
• Use signed DLLs
Code Integrity Guard
• Execute code in ROP

EXPLAINED BOUNTY
Restricts loading of unsigned DLLs UP TO $45K
ACG bypasses – Edge use case

• Edge uses separate process for JIT (it has to…)

CreateFileMapping()

PAGE_EXECUTE_READ PAGE_READWRITE

Content Process JIT Process


ACG bypasses – Edge use case

Load script
Compile the bytecode
Parse into bytecode

Interpret bytecode Allocate and write


native code to shared
memory
Execute native code

Compiled code Shared memory Compiled code


PAGE_EXECUTE_READ PAGE_READWRITE

MicrosoftEdgeCP.exe MicrosoftEdgeCP.exe
(content process) (JIT server)
BlockDynamicCode=ON BlockDynamicCode=OFF

Based on a diagram from “Bypassing Mitigations by Attacking JIT Server in Microsoft Edge” by Ivan Fratric
Edge ACG Old bypasses - Duplicate Handle

• Credit: Ivan Fratric, GPZ, issue 1299

• JIT process needs to map +X memory in the calling process

• For that, it must have a handle of the calling process

• In order to send its handle to the JIT process, the calling process first needs to call
DuplicateHandle on its (pseudo)handle.

• Content process needs to keep the handle of the target process (JIT process) with the
PROCESS_DUP_HANDLE access right
Edge ACG Old bypasses - UnmapViewOfFile

• Credit: Ivan Fratric, GPZ, issue 1435 • Write shellcode

• Unmap the shared memory using • When JIT process calls VirtualAllocEx(),
UnmapViewOfFile() even though the memory is already

• Predict the address the JIT server is going allocated:

to write • the call is going to succeed


• the memory protection is going to be set to
• Allocate a writable memory region in the
PAGE_EXECUTE_READ, content survives!
same address
• The “true” JITted payload will be written into
the JIT server’s "side" of the shared memory
Edge ACG Old bypasses - UnmapViewOfFile

Load script
Compile the bytecode
Parse into bytecode

Interpret bytecode Allocate and write


native code to shared
momory
Execute native code

Compiled code Compiled code


PAGE_EXECUTE_READ PAGE_READWRITE

MicrosoftEdgeCP.exe MicrosoftEdgeCP.exe
(content process) (JIT server)
BlockDynamicCode=ON BlockDynamicCode=OFF

Based on a diagram from “Bypassing Mitigations by Attacking JIT Server in Microsoft Edge” by Ivan Fratric
“Pure” ACG Bypass - Warbird

• Credit: Alex Ionescu

• Added DRM to the kernel a few years ago - Warbird API


• Accessible from NtSetSystemInformation(SystemControlFlowInformation)

• API doesn’t restrict caller


• can be Low IL, App Container, LPAC…

• Allows creating +X memory inside calling process

• Using MDLs this memory is then made writable

• Caller can load a new trap frame with an arbitrary return address in userspace
• Nothing checks the CFG bitmap against it.
Working our way around ACG && CIG

• Avoid VirtualAllocEx / VirtualProtect

• Executing everything in ROP => no shellcode anymore

• Need a different ROP chain

• Simplest solution would be to execute a process


• CreateProcess / ShellExecuteEx / system

• However…
Child Process Restriction

NAME WORKAROUNDS
• Implement execve in userspace
Child Process Restriction
• Chain a kernel exploit

EXPLAINED BOUNTY
A child process cannot be created when this UP TO $15K
restriction is enabled
WINDOWS 10 RS5 DEMO
Even more mitigations

• Containers/sandboxing
• LPAC, WDAG, etc.

• New improved CFG

• Intel CET to mitigate ROP


• We tried RFG, had a by-design bypass…

• Many others in kernelspace

• We need your help!


Contact

• Send us cool mitigation bypasses to [email protected]

• Ping me on Twitter, @AmarSaar

• https://www.microsoft.com/en-us/msrc/bounty-mitigation-bypass
Killing bugs before they’re born

• From the writeup on the challenge:


Thanks

• @__awe for the awesome challenge


• We need more Windows CTF challenges!

• @tom41sh for the help and the whiskey

• All of the brilliant engineers who work on mitigations


• And keeps our lives interesting as exploit developers ☺
Refs

• Security Servicing Criteria for Windows / Bounty Mitigation Bypass

• JIT Server / JIT Server whitepaper

• The Evolution of CFI Attacks and Defenses

• The “Bird” that killed ACG

• Deterministic LFH

• If you like Hebrew (Sorry!) – LFH internals and exploitation

• Exploiting a misused C++ shared pointer on Windows 10


THANKS!

You might also like