Root Cause
NtUserSetWindowFNID()는 FNID를 설정할 때, Window가 release 됐는지 아닌지 확인하지 않음.
→ 따라서 이미 free된 Window에 FNID를 설정하여 재사용 할 수 있음.
이 취약점을 이용하여, xxSBTrackInit()에서 pSBTrack의 UAF를 발생시킬 수 있음.
Flow

- 먼저 KernelCallbackTable에 있는 2개의 callback을 Hook함. (fnDWORD, fnClientFree…)
1 | asm.asm |
- Main Window와 ScrollBar 를 만듬.
1 | //Structure 설정. |
SendMessage()함수를 사용해 ScrollBar에 WM_LBUTTONDOWN 메시지를 보냄
(이는 xxxSBTrackInit()함수 호출을 Trigger하기 위함)
1 | SendMessage(hSBWND, WM_LBUTTONDOWN, 0, 0x00020002); |
xxxSBTrackLoop이 호출되어, 마우스 왼쪽 버튼이 해제되거나 다른 메시지가 수신될 때까지 루프에서 마우스 이벤트를 캡쳐함.
xxxSBTrackLoop이 실행되므로 fnDWORD가 Callback됨. (이때 미리 Hook한 fnDWORD가 실행됨)
1 | void fnDWORDCallBack(PULONG64 msg) |
- xxxClientFreeWindowClass..함수는 Callback를 호출 (이때 미리 Hook한 fnClinet…가 실행됨)

1 | void fnClientFreeWindowClassExtraBytesCallBack(PULONG64 msg) |
- Main Window가 파괴됐기 때문에 xxxSBTrackLoop이 돌아와 HMAssignmentUnLock(&pSBTrack->spwndNotify)을 계속 실행하여 Main Window를 완전히 해제하는 관련 역참조를 수행하는데, 이로 인해 xxxFreeWindow가 다시 호출됨.

- Main Window의 FNID는 (xxxFreeWindow에서 8000(Free)되었다가, fnClient..함수에서 82A1로 설정 되었음.) 따라서 SfnDWORD함수가 호출되며 이는 아까 hook했던 fnDWORD가 다시 호출됨.
1 | void fnDWORDCallBack(PULONG64 msg) |
- POC 프로그램은 단일 스레드이기 때문에 스레드에 의해 생성된 모든 창은 동일한 스레드 정보 구조를 가리킨다. SBTrack이 속해 있는 Scrollbar 창이 해제되었더라도, 동일한 스레드에 의해 새 창이 생성되는 한, pSBTrack은 여전히 동일한 창을 가리키고 있다.
- setCaputure(hSBWNDNew)를 호출하여 hSBTWNDNDNew 창에서 마우스 이벤트를 캡처하기 위해 현재 스레드를 설정했다. 마지막으로, UserFreePool(pSBTrack)이 pSBTrack을 릴리스하기 위해 실행되어 HMAssignmentUnLock(&pSBTrack->spwnd를 실행하기 전에 pSBTrack을 해제함SB) 및 결과 pSBTrack에 After Free.
- Usermode에서 pSBTrack을 Free했다.
- 정상적인 흐름은 xxxSBTrackLoop가 끝난 후 xxxSBTrackInit()에서 Free 시켜야 하지만, 미리 UserMode에서 Free 해버렸으므로, 이후에 pSBTrack에 대한 UAF가 발생함.
Windbg !analyze


POC code.
1 |
|
1 | public GetKernelCallbackTableBase |