Lucene search

K
googleprojectzeroGoogleProjectZeroGOOGLEPROJECTZERO:B6FB33FF19AE0AD0E463FE10F35B3778
HistoryAug 13, 2019 - 12:00 a.m.

Down the Rabbit-Hole...

2019-08-1300:00:00
googleprojectzero.blogspot.com
19

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

0.072 Low

EPSS

Percentile

93.9%

Posted by Tavis Ormandy, Security Research Over-Engineer.

“Sometimes, hacking is just someone spending more time on something than anyone else might reasonably expect.”[1]

I often find it valuable to write simple test cases confirming things work the way I think they do. Sometimes I can’t explain the results, and getting to the bottom of those discrepancies can reveal new research opportunities. This is the story of one of those discrepancies; and the security rabbit-hole it led me down.

It all seemed so clear…

Usually, windows on the same desktop can communicate with each other. They can ask each other to move, resize, close or even send each other input. This can get complicated when you have applications with different privilege levels, for example, if you “Run as administrator”.

It wouldn’t make sense if an unprivileged window could just send commands to a highly privileged window, and that’s what UIPI, User Interface Privilege Isolation, prevents. This isn’t a story about UIPI, but it is how it began.

The code that verifies you’re allowed to communicate with another window is part of win32k!NtUserPostMessage. The logic is simple enough, it checks if the application explicitly allowed the message, or if it’s on a whitelist of harmless messages.

BOOL __fastcall IsMessageAlwaysAllowedAcrossIL(DWORD Message)

{

BOOL ReturnCode; // edx

BOOL IsDestroy; // zf

ReturnCode = FALSE;

if…

switch ( Message )

{

case WM_PAINTCLIPBOARD:

case WM_VSCROLLCLIPBOARD:

case WM_SIZECLIPBOARD:

case WM_ASKCBFORMATNAME:

case WM_HSCROLLCLIPBOARD:

ReturnCode = IsFmtBlocked() == 0;

break;

case WM_CHANGECBCHAIN:

case WM_SYSMENU:

case WM_THEMECHANGED:

return 1;

default:

return ReturnCode;

}

return ReturnCode;

}


Snippet of win32k!IsMessageAlwaysAllowsAcrossIL showing the whitelist of allowed messages, what could be simpler… ?

I wrote a test case to verify it really is as simple as it looks. If I send every possible message to a privileged window from an unprivileged process, the list should match the whitelist in win32k!IsMessageAlwaysAllowedAcrossIL and I can move onto something else.

Ah, I was so naive. The code I used is available here, and a picture of the output is below.

What the…?! Scanning which messages are allowed across IL produces unexpected results.

The tool showed that unprivileged applications were allowed to send messages in the 0xCNNN range to most of the applications I tested, even simple applications like Notepad. I had no idea message numbers even went that high!

Message numbers use predefined ranges, the system messages are in the range 0 - 0x3FF. Then there’s the WM_USER and WM_APP ranges that applications can use for their own purposes.

This is the first time I’d seen a message outside of those ranges, so I had to look it up.

The following are the ranges of message numbers.


|

Range

|

Meaning

—|—

0 through WM_USER –1

|

Messages reserved for use by the system.

WM_USER through 0x7FFF

|

Integer messages for use by private window classes.

WM_APP (0x8000) through 0xBFFF

|

Messages available for use by applications.

0xC000 through 0xFFFF

|

String messages for use by applications.

Greater than 0xFFFF

|

Reserved by the system.

This is a snippet from Microsoft’s WM_USER documentation, explaining reserved message ranges.

Uh, string messages?

The documentation pointed me to RegisterWindowMessage(), which lets two applications agree on a message number when they know a shared string. I suppose the API uses Atoms, a standard Windows facility.

My first theory was that RegisterWindowMessage() automatically calls ChangeWindowMessageFilterEx(). That would explain my results, and be useful information for future audits… but I tested it and that didn’t work!

…Something must be explicitly allowing these messages!

I needed to find the code responsible to figure out what is going on.

Tracking down the culprit…

I put a breakpoint on USER32!RegisterWindowMessageW, and waited for it to return one of the message numbers I was looking for. When the breakpoint hits, I can look at the stack and figure out what code is responsible for this.

$ cdb -sxi ld notepad.exe

Microsoft ® Windows Debugger Version 10.0.18362.1 AMD64

Copyright © Microsoft Corporation. All rights reserved.

CommandLine: notepad.exe

(a54.774): Break instruction exception - code 80000003 (first chance)

ntdll!LdrpDoDebuggerBreak+0x30:

00007ffa`ce142dbc cc int 3

0:000> bp USER32!RegisterWindowMessageW “gu; j (@rax != 0xC046) ‘gc’; ‘’”

0:000> g

0:000> r @rax

rax=000000000000c046

0:000> k

Child-SP RetAddr Call Site

0000003a3c9ddab0 00007ffacbc4a010 MSCTF!EnsurePrivateMessages+0x4b

0000003a3c9ddb00 00007ffacd7f7330 MSCTF!TF_Notify+0x50

0000003a3c9ddc00 00007ffacd7f1a09 USER32!CtfHookProcWorker+0x20

0000003a3c9ddc30 00007ffacd7f191e USER32!CallHookWithSEH+0x29

0000003a3c9ddc80 00007fface113494 USER32!_fnHkINDWORD+0x1e

0000003a3c9ddcd0 00007ffaca2e1f24 ntdll!KiUserCallbackDispatcherContinue

0000003a3c9ddd58 00007ffacd7e15df win32u!NtUserCreateWindowEx+0x14

0000003a3c9ddd60 00007ffacd7e11d4 USER32!VerNtUserCreateWindowEx+0x20f

0000003a3c9de0f0 00007ffacd7e1012 USER32!CreateWindowInternal+0x1b4

0000003a3c9de250 00007ff65d8889f4 USER32!CreateWindowExW+0x82

0000003a3c9de2e0 00007ff65d8843c2 notepad!NPInit+0x1b4

0000003a3c9df5f0 00007ff65d89ae07 notepad!WinMain+0x18a

0000003a3c9df6f0 00007ffacdcb7974 notepad!__mainCRTStartup+0x19f

0000003a3c9df7b0 00007fface0da271 KERNEL32!BaseThreadInitThunk+0x14

0000003a3c9df7e0 0000000000000000 ntdll!RtlUserThreadStart+0x21


So… wtf is ctf? A debugging session trying to find who is responsible for these windows messages.

The debugger showed that while the kernel is creating a new window on behalf of a process, it will invoke a callback that loads a module called “MSCTF”. That library is the one creating these messages and changing the message filters.

The hidden depths reveal…

It turns out CTF[2] is part of the Windows Text Services Framework. The TSF manages things like input methods, keyboard layouts, text processing and so on.

If you change between keyboard layouts or regions, use an IME like Pinyin or alternative input methods like handwriting recognition then that is using CTF.

The only discussion on the security of Text Services I could find online was this snippet from the “New Features” page:

7.5 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

NONE

Availability Impact

NONE

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

5 Medium

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

NONE

Availability Impact

NONE

AV:N/AC:L/Au:N/C:P/I:N/A:N

0.072 Low

EPSS

Percentile

93.9%