Infiltrate 2015
Infiltrate 2015
Infiltrate 2015
Demo Time
QA & Wrap-Up
◦ They don’t need a name – because they are bound by a file object on disk
◦ Cache Manager and Memory Manager take care of finding the section object for an already-
mapped file
◦ But they CAN actually have a name – this is how KnownDLLs work
◦ Abused by myself at SyScan 2013
◦ They don’t need a name because ALPC maps the server/client ends as
needed during message passing
◦ No way to get access to underlying object
◦ Pretty much the same as the ALPC Sections, but not managed through ALPC
◦ Object Manager returns handle to the Section Object to the caller
◦ Caller must find a way to pass the handle to the process it wants to share the
data with
◦ Inheritance: Caller spawns a new child process, and the handles are inherited
◦ Forking: Caller uses fork() API (HI BEN!!!), and the memory address space is inherited
◦ Duplication: Caller uses DuplicateHandle() API, and the handle is duplicated to another process
◦ Unnamed Section Objects do not have security checks enabled (no ACLs)
◦ Not an issue for ALPC Sections, because user-mode code doesn’t have the handle
◦ But big problem for non-ALPC Sections
◦ [ref: http://googleprojectzero.blogspot.com/2014/10/did-man-with-no-name-feel-insecure.html
James Forshaw (@tiranid) from Google]
◦ Very similar to the previous type (identical at the memory manager level)
◦ Caller uses the Object Manager to assign a name to the section object
◦ Any process on the system can now attempt to open the section by name
◦ ACLs are critical to ensure only designated processes receive a handle to the section
◦ ACLs can enforce READ vs WRITE permissions on the shared memory itself
◦ Windows ACL Rule: No DACL (NULL) means everyone has access
Also, the tools only show you the security descriptor, but not who owns the
object, who created it, who is accessing it, etc..
◦ All this information is in the kernel debugger, however…
You can enable this really useful behavior with the Global Flags Utility
◦ Set the option “maintain a list of objects for each type”
◦ Can also use !gflag +otl in the kernel debugger (sets flag 0x4000)
Every unique object has a main header and a possible set of extra
headers that define the attributes for the particular object
◦ Name, memory quota consumption, etc…
◦ When +otl is used, a new creator info header is allocated for each object
◦ dt nt!_OBJECT_HEADER_CREATOR_INFO
The object creator info header is always on top of the main header
◦ dt nt!_OBJECT_HEADER
The !sd extension will then display the DACL and SACL in the security descriptor
◦ Add flag 0x1 after the pointer and you’ll get SID->Name translation too
We are particularly interested in DACLs that are either empty, or which grant
“Everyone” RW access
◦ No way to filter at the extension level, we need .foreach or a custom script
The actual “Creating Process” which Windows does not normally track…
◦ But we have the +otl flag!
But remember that for section objects, the Segment does also
independently track the creator!
◦ $$ Section Object in T0
!process @@(((nt!_OBJECT_HEADER_CREATOR_INFO*)((unsigned
int64)#CONTAINING_RECORD(@$t0, nt!_OBJECT_HEADER, Body) -
sizeof(nt!_OBJECT_HEADER_CREATOR_INFO)))-
>CreatorUniqueProcess) 0
In Windows 10, the Section Object has a pointer to the Control Area at
u1.ControlArea
◦ dt nt!_SECTION u1.ControlArea
So in Windows 10:
◦ $$ Assumes Section Object in T0
!process @@(@$t0->u1.ControlArea->Segment->u1.CreatingProcess) 0
We can use the !sid extension to dump it, and pass flag 0x1 to perform
SID->Name conversion
◦ $$ Section Object Header in T1
!sid @@(((nt!_SECURITY_DESCRIPTOR_RELATIVE*)((unsigned
int64)@$t1->SecurityDescriptor & ~0xF))->Owner + ((unsigned
int64)@$t1->SecurityDescriptor & ~0xF)) 1
If we take a look at the Token of the Process, this will tell us the User
Account that it’s running as
◦ The Token field in EPROCESS stores a fast reference to the token object
The !token extension will display and format a token, and the –n flag
will convert SIDs to names
◦ We’re interested in SYSTEM/LOCAL SERVICE, for starters
This might not work if the creator never mapped the section…
◦ Or has since unmapped it (and maybe mapped elsewhere)
This will work and since we’re on a live system TLB flush will happen on
its own anyway
Check for any changes to the process
◦ Strange memory consumption, crash, etc
◦ Having user-mode WinDBG attached can easily display exceptions / DbgPrint
Not only is it trivial to exploit, but I also wonder if the JSON payloads
themselves can be legitimately laid out but malicious in nature
◦ i.e.: do a JSON payload attack, not fuzzing the JSON data/memory itself
Only discovered this interface by chance on the plane on the way here…
◦ Doesn’t show up until you actually play music using Windows Media Player
◦ Or watch a movie
KASLR is not an issue here: kernel base is leaked, and kernel file can be
mapped in user-mode
8/12/2015 COPYRIGHT 2015 ALEX IONESCU. ALL RIGHTS RESERVED. 58
Finding the “right” Compare
The compare routine has the following signature
FirstStruct is, in this case, the string containing the font name
Return value is important too: if the function returns 2+, we are golden,
because win32k!IsTrustedFontPath only checks for !=NULL, doesn’t
actually read the data
◦ But if the function returns 1, we must make Table->Root.LeftChild == NULL
◦ If the function returns 0, we have a problem, because it will read RightChild
On x64, Windows 8.1 offers 128 TB of address space, and few processes
ever use anywhere close to this
◦ Meaning that most of the PXE/PML4 entries are empty (each PML4 entry
covers 512GB)
◦ Therefore, by picking an address “far enough” in the address space, and
making the target of the Write-what-where a given PML4 entry, nearby
corruption won’t matter as long as nobody attempts to dereference anything
within the adjacent 512GB blocks
◦ Repeat the process for other entries (PPE, PDE, PTE)
8/12/2015 COPYRIGHT 2015 ALEX IONESCU. ALL RIGHTS RESERVED. 62
Improving the Technique
The original technique uses the Write-what-where four times for each
of the paging structures.
◦ It describes how self-referencing entries work, but doesn’t actually leverage
their power!
Now only a single write is needed, and the PXE will recursively be a PTE
How do we do that?
8/12/2015 COPYRIGHT 2015 ALEX IONESCU. ALL RIGHTS RESERVED. 64
Address Windowing Extensions
Designed for Windows 2000, which supported PAE (Physical Address
Extension)
◦ Intel technology allowed for access of up to 64GB of RAM on a 32-bit OS
“MSDN, you just invited Alex Ionescu to take your API apart”
Bottom line: by leaking the address of the PFNs, and by zeroing out the
working set index, AWE pages’ PTEs can be fully forged with the
needed bits
But it turns out, thanks to AWE, you don’t even need a vulnerability.
AWE pages can have two states:
◦ PAGE_READWRITE: Non-executable, user-mode, RW
◦ PAGE_NOACCESS: How can we emulate “no access”?!