Profiting from Desktop heap
information
Having
followed all the late buzz about the Windows graphics and messaging subsystem
kernel part, I decided to take a look at it to understand how it worked, and
see if I could find anything interesting and useful. Inspired by Alex Ionescu
online presentation, I dove into the realms of the desktop heap, and started to
understand the richness of information available. This post is about a little
game that shows some of the potential behind that data. The game I'm proposing
here will be to run a packed application and, from user mode, try to get some
critical information from that process without interfering with it. As passive
and non-invasive as possible. Without calling the kernel and not touching the
packed process in any way.
Note: As always, the full paper is available here.
For
the setting, I grabbed a notepad.exe executable from a XP machine and packed it
with UPX.
If
you
want to know why I didn't use notepad.exe from Windows 7 read my last
post: "MUI hell". I renamed notepad.exe to popo.exe as to avoid any
confusion with
names and I ran it. Running ProcExp afterwards, one can see that the
popo.exe
process has high entropy, so it is identified as packed images "purple".
Figure 1: Packed process
Next,
as I needed another GUI process and I didn't want to lock explorer.exe, I run
another notepad.exe. This time I used the one from Windows 7.
As
a side note, I'm going to jump around between kernel and user debuggers. Please
try to keep in mind that the kernel debugger is just used to validate some of
the demonstration data.
Continuing,
find a GUI process, :):
!process
0 0 notepad.exe
PROCESS
85cb7d40 SessionId: 1 Cid:
0a14 Peb: 7ffd4000 ParentCid: 0804
DirBase: 1be3b000 ObjectTable: 998c05d0 HandleCount:
58.
Image: notepad.exe
From
that process, get a GUI thread.
!process
PROCESS 85cb7d40 SessionId: 1 Cid: 0a14
Peb: 7ffd4000 ParentCid: 0804
DirBase: 1be3b000 ObjectTable:
998c05d0 HandleCount: 58.
Image:
notepad.exe
VadRoot
84444c88 Vads 70 Clone 0 Private 1454.
Modified 1084. Locked 0.
THREAD 8458d838 Cid 0a14.0a1c Teb: 7ffdf000 Win32Thread: ff9ba008 WAIT:
(WrUserRequest) UserMode Non-Alertable
84caaec8 SynchronizationEvent
.thread 8458d838
Implicit thread is now 8458d838
k
*** Stack
trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr
96b4eb10 82c9ad75 nt!KiSwapContext+0x26
96b4eb48 82c99bd3 nt!KiSwapThread+0x266
96b4eb70 82c9388f nt!KiCommitThreadWait+0x1df
96b4ebe8 915296d6 nt!KeWaitForSingleObject+0x393
96b4ec44 915294e3 win32k!xxxRealSleepThread+0x1d7
96b4ec60 91526550 win32k!xxxSleepThread+0x2d
96b4ecb8 91529aa2
win32k!xxxRealInternalGetMessage+0x4b2
96b4ed1c 82c7487a win32k!NtUserGetMessage+0x3f
96b4ed1c 779c70c6 nt!KiFastCallEntry+0x12a
000ff998 76d7cde0 ntdll!KiIntSystemCall+0x6
000ff99c 76d7ce13 USER32!NtUserGetMessage+0xc
000ff9b8 0080148a USER32!GetMessageW+0x33
000ff9f8 008016ec notepad!WinMain+0xe6
000ffa88 77473c45 notepad!_initterm_e+0x1a1
000ffa94 779e37f5
kernel32!BaseThreadInitThunk+0xe
000ffad4 779e37c8 ntdll!__RtlUserThreadStart+0x70
000ffaec 00000000 ntdll!_RtlUserThreadStart+0x1b
Ok, now we need to get information from the
desktop heap, where it's mapped.
!teb
TEB at 7ffdf000
dt nt!_TEB 7ffdf000 win*
+0x040
Win32ThreadInfo : 0xff9ba008 Void
+0x6cc
Win32ClientInfo : [62] 0x20000188
+0xf6c
WinSockData : (null)
dt win32k!tagCLIENTINFO 0x7ffdf6cc
+0x000 CI_flags : 0x20000188
+0x018 pDeskInfo : 0x01d00578
tagDESKTOPINFO
+0x01c ulClientDelta : 0xfcb00000
+0x028
CallbackWnd : _CALLBACKWND
+0x03c
pClientThreadInfo : 0x01d0fd68 tagCLIENTTHREADINFO
+0x064
hKL : 0x08160816 HKL__
+0x068 CodePage
: 0x4e4
+0x06a achDbcsCF : [2]
""
+0x06c msgDbcsCB : tagMSG
+0x088 lpdwRegisteredClasses :
0x76dc90d4 -> 0x90
dt win32k!tagDESKTOPINFO 0x01d00578
+0x000 pvDesktopBase : 0xfe800000 Void
+0x004 pvDesktopLimit : 0xff400000 Void
+0x008 spwnd : 0xfe800618 tagWND
+0x00c
fsHooks : 0x4000
+0x010
aphkStart : [16] (null)
+0x050
spwndShell : 0xfe8082c0 tagWND
+0x054
ppiShellProcess : 0xff9c9608
tagPROCESSINFO
+0x058
spwndBkGnd : 0xfe808508 tagWND
+0x05c
spwndTaskman : 0xfe804428 tagWND
+0x060
spwndProgman : (null)
+0x064
pvwplShellHook : 0xff919638 VWPL
+0x068
cntMBox : 0n0
+0x06c
spwndGestureEngine : (null)
+0x070
pvwplMessagePPHandler : (null)
+0x074
fComposited : 0y0
+0x074
fIsDwmDesktop : 0y1
!kvas 0xfe800000
kvas : Show region containing fe800000
### Start
End Length ( MB)
Count Type
000 fdc00000 ffbfffff 2400000 (
36) 8 SessionSpace
!ptelist 0xfe800000
ptelist : Using fe800000 as VA
VA
|PDE
|PTE
FE800000 |Hard Pfn=128E0 Attr=---DA--KWEV |Hard Pfn=13FE1 Attr=---DA--KWEV
Here, we have the kernel address (0xfe800000) of an executable heap in 32 bits
architecture.
According to the help from windbg:
"Executable page. For platforms that do not support a hardware
execute/noexecute bit, including many x86
systems, the E is always displayed.". Basically where NX is
not present - Can anyone out there validate the executability of this heap on
0x64?
A kernel address that we got from user mode and a
heap we can iterate and validate, also from user mode.
A heap we can fill with GUI objects. Shellcode
anyone? Stack pivoting?
This heap is called the Desktop heap and is
shared and mapped read only in the user mode region, between all processes that
share the same desktop and session, independently of its integrity level.
!vad 84444c88
VAD
level start end
commit
85ccf578 ( 5) 10 1f 0 Mapped READWRITE Pagefile-backed section
...
8458f448 ( 1) 800
82f 4 Mapped Exe
EXECUTE_WRITECOPY
\Windows\System32\notepad.exe
8518bb90 ( 5) 830
182f 41 Private READWRITE
85206270 ( 4) 1830
18f7 0 Mapped READONLY Pagefile-backed section
845ed280 ( 5) 1900
19de 0 Mapped READONLY Pagefile-backed section
84600e10 ( 3) 19e0
19ef 1 Private READWRITE
85935718 ( 5) 19f0
1aef 128 Private NO_ACCESS
85cb43e0 ( 4) 1af0
1bef 130 Private NO_ACCESS
845770c0 ( 2) 1bf0
1cf0 0 Mapped READONLY Pagefile-backed section
85220368 ( 4) 1d00
28ff 0 Mapped READONLY Pagefile-backed section
85e32560 ( 3) 2900
29ff 2 Private NO_ACCESS
Although this alone might get you interested in
this portion of memory, I'm not going, in this post, to talk about the
potential of using this heap on privilege escalation exploitation cases.
I'm going to focus only on tagCLS type objects
and what we can do with the information they provide from a user mode
perspective.
The tagCLS objects are created by the win32k
module, specifically by the ClassAlloc function, that allocates them in the
Desktop heap when called by InternalRegisterClassEx. This USER32 system call is
invoked whenever the user32.dll counterpart, RegisterClassEx function, is used
to register a window class (tagWNDCLASSEX).
typedef struct tagWNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE
hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
Let's see what the Desktop heap has for us, from
user mode. To calculate its address we grab the pDeskInfo member from the TEB's
Win32ClientInfo offset, and subtract the size of heap metadata (0x570). Having
the heap's base address we can dump it now:
dt nt!_heap 0x01d00000
+0x000
Entry : _HEAP_ENTRY
+0x008 SegmentSignature : 0xffeeffee
+0x00c
SegmentFlags : 1
+0x010
SegmentListEntry : _LIST_ENTRY [ 0xfe8000a8 - 0xfe8000a8 ]
+0x018
Heap : 0xfe800000 _HEAP
+0x01c
BaseAddress : 0xfe800000 Void
+0x020
NumberOfPages : 0xc00
+0x024
FirstEntry : 0xfe800570 _HEAP_ENTRY
+0x028
LastValidEntry : 0xff400000 _HEAP_ENTRY
+0x02c
NumberOfUnCommittedPages : 0xbaa
+0x030
NumberOfUnCommittedRanges : 1
+0x034
SegmentAllocatorBackTraceIndex : 0
+0x036
Reserved : 0
+0x038
UCRSegmentList : _LIST_ENTRY [
0xfe855ff0 - 0xfe855ff0 ]
+0x064
Signature : 0xeeffeeff
+0x0a0
VirtualAllocdBlocks : _LIST_ENTRY [ 0xfe8000a0 - 0xfe8000a0 ]
+0x0a8
SegmentList : _LIST_ENTRY [ 0xfe800010
- 0xfe800010 ]
+0x0b8
BlocksIndex : 0xfe800138 Void
+0x0c4
FreeLists : _LIST_ENTRY [
0xfe81ef70 - 0xfe8489b8 ]
+0x0d0
CommitRoutine : 0x91497343 long
win32k!UserCommitDesktopMemory+0
+0x0d4
FrontEndHeap : (null)
+0x0d8
FrontHeapLockCount : 0
+0x0da
FrontEndHeapType : 0 ''
+0x0dc
Counters : _HEAP_COUNTERS
+0x130
TuningParameters : _HEAP_TUNING_PARAMETERS
All this information pertains to kernel mode, but
we can easily determine an offset from the kernel mode values to user mode:
?0xfe800000-0x01d00000
Evaluate
expression: -55574528 = fcb00000
As we know that the first chunk of memory for a
heap allocation is a HEAP_ENTRY, notice that the tagDESKTOPINFO is the first
entry of the heap:
?0xfe800570+@@(sizeof(nt!_heap_entry))
Evaluate expression: -25164424 = fe800578
?(fe800578&0000ffff)+01d00000
Evaluate expression: 30410104 = 01d00578
Knowing the offset between kernel mode and user
mode mappings of the Desktop heap, we can adjust the kernel addresses found and
rebase them to user mode addresses. Doing so allows us to iterate through the
whole heap, chunk by chunk.
dt nt!_HEAP_ENTRY 01d00570
+0x000
Size : 0x10
?01d00570+0x10*8
Evaluate expression: 30410224 = 01d005f0
dt nt!_HEAP_ENTRY 01d005f0
+0x000
Size : 4
?01d005f0+0x4*8
Evaluate expression: 30410256 = 01d00610
dt nt!_HEAP_ENTRY 01d00610
+0x000
Size : 0x17
Etc. But I said we're going to hunt for tagCLS
objects. Let's have a look at them. The size of a tagCLS structure is:
?@@(sizeof(win32k!tagCLS)+sizeof(nt!_heap_entry))
Evaluate expression: 100 = 00000064
But as 64 isn't a multiple of eight we need to
round it up, giving:
?68/8
Evaluate expression: 13 = 0000000d
So we need to seek for heap entries of size 0xd.
Recapitulating, the first heap chunk is a
tagDESKTOPINFO.
?@@(sizeof(win32k!tagDESKTOPINFO))
Evaluate expression: 120 = 00000078
?01d00578+0x78
Evaluate expression: 30410224 = 01d005f0
So, our first seekable element is at 01d005f0, which is validated by our previous
manual heap walking.
Although I could loop through all the heap
entries, like before, I just need to find a first valid tagCLS to validate
data. Let's then search for hints on objects sized 0xd then:
s -w 01d005f0 l1000 d
01d006c8 000d 0001 0017 0c00 06d0 ff68 8001 8001 ..........h.....
01d00734 000d 0900 3323 3732 3936 fe00 0018 0001 ....#32769......
01d00800 000d 0001 0018 0c00 0808 ff68 c039 c039 ..........h.9.9.
dt nt!_HEAP_ENTRY 01d006c8
+0x000
Size : 0xd
+0x002
Flags : 0x1 ''
+0x003
SmallTagIndex : 0 ''
+0x000
SubSegmentCode : 0x0001000d Void
+0x004
PreviousSize : 0x17
This seems a valid entry. Let's dump it as
tagCLS:
dt win32k!tagCLS 01d006c8+8
+0x000
pclsNext : 0xff6806d0 tagCLS
+0x004
atomClassName : 0x8001
+0x006
atomNVClassName : 0x8001
+0x008
fnid : 0x29d
+0x00c
rpdeskParent : 0x8585d678 tagDESKTOP
+0x010
pdce : (null)
+0x014
hTaskWow : 0
+0x016 CSF_flags
: 0x41
+0x018 lpszClientAnsiMenuName : (null)
+0x01c lpszClientUnicodeMenuName
: (null)
+0x020
spcpdFirst : (null)
+0x024
pclsBase : 0xffb22758 tagCLS
+0x028
pclsClone : (null)
+0x02c
cWndReferenceCount : 0n1
+0x030
style : 8
+0x034
lpfnWndProc : 0x914fcd91 long
win32k!xxxDesktopWndProc+0
+0x038 cbclsExtra
: 0n0
+0x03c cbwndExtra : 0n0
+0x040 hModule : 0x91460000 Void
+0x044
spicn : (null)
+0x048
spcur : 0xffb75608 tagCURSOR
+0x04c
hbrBackground : 0x00000002 HBRUSH__
+0x050
lpszMenuName : (null)
+0x054
lpszAnsiClassName : 0xfe800738
"#32769"
+0x058
spicnSm : (null)
This is what we need. Let's get the tagDESKTOP
address and validate it. This time we’ll be using the kernel to verify its
address, by grabbing Win32ThreadInfo member from the TEB.
dt win32k!tagTHREADINFO 0xff9ba008
rpdesk
+0x0c8
rpdesk : 0x8585d678 tagDESKTOP
Yes, it is the same. Now, if we search for all
occurrences of this tagDESKTOP address in the Desktop heap, subtract 0xc from
the obtained address, we get a list of all tagCLS objects in the heap.
Note that if I were to do a program or script to
do this, I'd register a tagWNDCLASSEX with a known lpfnWndProc like 0xbadecafe, seek for this value to
locate my tagCLS object and get my corresponding tagDESKTOP; then, iterate all
the heap items grabbing those with the same desktop. But, as I'm using the debugger
for the demo, it's easier and simpler to do it this way.
s -d 01d005f0 l1000000
0x8585d678
01d00624 8585d678 fe800618 00040000 82000100 x...............
01d006dc
8585d678 00000000 00410000 00000000
x.........A.....
01d00754
8585d678 fe800748 00040000 80000100
x...H...........
01d00814
8585d678 00000000 00410000 00000000
x.........A.....
01d0088c 8585d678 fe800880 00040000 80000100 x...............
01d0096c 8585d678 00000000 03410000 00000000 x.........A.....
01d009e4 8585d678 fe8009d8 00000000 00000000 x...............
01d00a2c 8585d678 fe800a20 00000000 00000000 x... ...........
01d00a54
8585d678 00000000 00410000 00000000
x.........A........
The most interesting parts of a tagCLS are
lpfnWndProc and hModule because these are user mode addresses that we can use
and validate from user mode.
.foreach (meuval {s -[1]d 01d005f0 l1000000 0x8585d678 }
) {
.if
((poi(${meuval}-0x14)&0x0000ffff) >= 0000000d) {
.printf "ObjAddr=[%08x], hInstance[%08x], WndProc=",
poi(${meuval}+18),
poi(${meuval}+34);
dds
(${meuval}+28) l1;
}
}
Note: The search size given in the command above
is greater or equal to the size of tagCLS, because the kernel can reserve some
extra space if requested by the user, as indicated in the cbclsExtra class
member.
...
ObjAddr=[fe800ac0],
hInstance[00800000], WndProc=01d00af4
008072c5 notepad!NpSaveDialogHookProc+0x97
ObjAddr=[fe800b58], hInstance[76d60000],
WndProc=01d00b8c 76d70666
USER32!ImeWndProcW
ObjAddr=[fe800dd0], hInstance[77090000],
WndProc=01d00e04 770941b5
MSCTF!UIWndProc
ObjAddr=[fe800e38], hInstance[77090000],
WndProc=01d00e6c 770fdd77 MSCTF!UIComposition::CompWndProc
...
...
ObjAddr=[fe805228], hInstance[749f0000],
WndProc=01d0525c 74a1fe38
COMCTL32!CListView::s_WndProc
ObjAddr=[fe805490], hInstance[749f0000],
WndProc=01d054c4 74a16022
COMCTL32!Header_WndProc
...
ObjAddr=[fe807320], hInstance[01000000],
WndProc=01d07354 01003429
...
ObjAddr=[fe814950], hInstance[70830000],
WndProc=01d14984 708432bc
ObjAddr=[fe814ba8], hInstance[70830000],
WndProc=01d14bdc 708431dc
ObjAddr=[fe814c40], hInstance[70830000],
WndProc=01d14c74 70842954
...
ObjAddr=[fe82b580], hInstance[77820000],
WndProc=01d2b5b4 778663e5
ole32!OleMainThreadWndProc
ObjAddr=[fe82cb70],
hInstance[00800000], WndProc=01d2cba4
008014de notepad!NPWndProc
...
ObjAddr=[fe842790], hInstance[77090000],
WndProc=01d427c4 770fdd77 MSCTF!UIComposition::CompWndProc
ObjAddr=[fe8428a8], hInstance[76d60000],
WndProc=01d428dc 76db3f06
USER32!EditWndProcW
ObjAddr=[fe842910], hInstance[749f0000],
WndProc=01d42944 749f99d0
COMCTL32!Edit_WndProc
ObjAddr=[fe842ac8], hInstance[749f0000], WndProc=01d42afc 74a90d49 COMCTL32!StatusWndProc
ObjAddr=[fe843c78], hInstance[01010000],
WndProc=01d43cac 01044270
Dumping lpfnWndProc and hModule from all the
tagCLS objects found in the desktop heap, we can observe that all the user
windows message dispatch handlers registered in the system for the current
desktop are available for analysis from user mode. From the dump above I've
identified two from notepad: NpSaveDialogHookProc and
NPWndProc which is the main window dispatcher
function; and module 0x01000000. This entire post is about module 0x01000000.
Module 0x01000000 is popo.exe as can be seen from
the procExp.
Figure 2: Packed popo.exe module address.
And the address from WndProc at memory position
0x1d07354, 0x01003429 is the NPWndProc windows message handler dispatcher
function from popo.exe as seen in the figure. The figure shows the unpacked
version of popo.exe so that symbol resolution is available.
Figure 3: Window dispatcher registration.
Figure 4: Window dispatcher registration arguments.
So here you have it. A fine simple non-invasive
way of getting some information from packed or protected applications.
Other thoughts: Do you think that the same info
is available for Protected (Media Path) processes? Those to which we were not
supposed to get any info about?
Is there any information available for other
desktops windows if in the same session? Can we link this data in any way with
the aheList?
Hope you enjoyed it.
Sem comentários:
Enviar um comentário