Tag Archive for 'KiUserCallbackDispatcher'

[NT Inside Study] Kernel To User Callback Function의 Address는 어디에 가지고 있나 ??

아래의 3가지 Function은 Kernel To User Callback을 대표하는 함수 입니다. 그러면 이 녀석들의 Address는 Kernel의 어디에서 들고 있을까 ?

7c93ead0 ntdll!KiUserCallbackDispatcher = <no type information>
7c93eac0 ntdll!KiUserApcDispatcher = <no type information>
7c93eaec ntdll!KiUserExceptionDispatcher = <no type information>

간단히 nt Symbol에서 확인이 가능합니다.

lkd> X nt!KeUser*
80563474 nt!KeUserCallbackDispatcher = <no type information>
80563478 nt!KeUserApcDispatcher = <no type information>
805701cc nt!KeUserModeCallback = <no type information>
80563470 nt!KeUserExceptionDispatcher = <no type information>

 커널쪽 심볼중 이름이 비슷한 세녀석이 있지요. 실제로 이 세녀석의 Memory를 열어 보면 User Mode Callback의 Address가 담겨 있는것을 확인 할 수가 있습니다.

lkd> dd 80563470
80563470 7c93eaec 7c93ead0 7c93eac0 00002626
80563480 00000000 00000000 00000000 00000000
80563490 00000000 00000000 00000000 00000000
805634a0 00000000 00000000 00000000 00000000
805634b0 00000000 00000000 00000000 00000000
805634c0 804e68b0 00000000 0000011c 80519fc4
805634d0 bf999980 00000000 0000029b bf99a690
805634e0 00000000 00000000 00000000 00000000

User Callback Function 세녀석의 주소가 나란히 저장되어 있는것을 확인할 수 가 있습니다.

[NT Inside Study] Kernel To User Callback 2 - KiUserCallbackDispatcher

본 내용은 정확한 Windows의 구조중 일부라 장담 할 수 없으면 특정 동작의 개인적인 추정을 바탕으로 작성한 글입니다.

Win32 의 GDI 프로그래밍을 하다보면 KiUserCallbackDispatcher 라는 녀석이 스택에 떡하니 있는걸 간혹 볼수 있습니다. ( 사실 거의 보이지 않지만 … )


00 0013f380 77cf84c6 77d0e355 0000001d 5a4b139c ntdll!KiUserCallbackDispatcher (FPO: [0,0,0])
01 0013f3bc 5a4848c3 0000001f 00000000 0013f500 USER32!NtUserCallNoParam+0xc

02 0013f48c 5a484860 00000000 5a4b10b0 0013f4c0 uxtheme!CInternalNonclientMetrics::Acquire+0×29 (FPO: [Non-Fpo])
03 0013f49c 5a48a6d4 00000000 00000000 5a4b1088 uxtheme!NcGetNonclientMetrics+0×37 (FPO: [Non-Fpo])
04 0013f4b0 5a48bacb 00000001 00000000 0013f4e4 uxtheme!AcquireNcThemeMetrics+0×30 (FPO: [0,0,0])
05 0013f4c0 5a48be53 00010002 00000000 5a48b06d uxtheme!NewThemeCheck+0×52 (FPO: [Non-Fpo])
06 0013f4cc 5a48b06d 5a480000 00000000 5a48af15 uxtheme!OnHooksEnabled+0×13 (FPO: [0,0,0])
07 0013f4e4 77d0e33d 00000000 0013f500 5a480000 uxtheme!ThemeInitApiHook+0×1af (FPO: [Non-Fpo])
08 0013f568 77d0dc0f 5a48af15 0000af15 ffff0000 USER32!InitUserApiHook+0×2c (FPO: [Non-Fpo])
09 0013f590 7c93eae3 0013f5a0 00000068 00000068 USER32!__ClientLoadLibrary+0×46 (FPO: [Non-Fpo])
0a 0013f604 77cffe13 77cffdd9 00050101 00008002 ntdll!KiUserCallbackDispatcher+0×13 (FPO: [0,0,0])
0b 0013faa8 77d042e9 00050101 00008002 0013fb0c USER32!NtUserCreateWindowEx+0xc

0c 0013fb80 77d04704 00400000 00008002 00000108 USER32!InternalCreateDialog+0×5bc (FPO: [Non-Fpo])
0d 0013fba4 77d1f002 00400000 00428e78 00000000 USER32!CreateDialogIndirectParamAorW+0×33 (FPO: [Non-Fpo])
0e 0013fbc4 7839df34 00400000 00428e78 00000000 USER32!CreateDialogIndirectParamW+0×1b (FPO: [Non-Fpo])
0f 0013fc8c 7839e650 00428e78 00000000 00400000 MFC80UD!CWnd::CreateDlgIndirect+0×2a4 (FPO: [Non-Fpo])
10 0013fd00 0041295e 9e4d6e2b 02e3f6f2 02e3f75a MFC80UD!CDialog::DoModal+0×1a0 (FPO: [Non-Fpo])
11 0013fecc 78375512 cccccccc cccccccc cccccccc ThreadTest!CThreadTestApp::InitInstance+0×1fe (FPO: [Non-Fpo]) (CONV: thiscall) [e:\per\threadtest\threadtest\threadtest.cpp @ 153]
12 0013fef0 004177a8 00400000 00000000 00020914 MFC80UD!AfxWinMain+0×82 (FPO: [Non-Fpo])
13 0013ff08 00413d79 00400000 00000000 00020914 ThreadTest!wWinMain+0×18 (CONV: stdcall) [f:\sp\vctools\vc7libs\ship\atlmfc\src\mfc\appmodul.cpp @ 33]

CreateDialogIndirectParam->NtUserCreateWindowEx->xxxCreateWindowEx 라는 기본 메커니즘은 누구나 알고 계실겁니다. 과연 그렇다면 KiUserCallbackDispatcher라는 Kernel Mode Callback은 어떻게 추가되는 것일까 ?

ntdll!KiUserCallbackDispatcher:
7c93ead0 83c404 add esp,4
7c93ead3 5a pop edx
7c93ead4 64a118000000 mov eax,dword ptr fs:[00000018h]
7c93eada 8b4030 mov eax,dword ptr [eax+30h]
7c93eadd 8b402c mov eax,dword ptr [eax+2Ch]
7c93eae0 ff1490 call dword ptr [eax+edx*4]
7c93eae3 33c9 xor ecx,ecx
7c93eae5 33d2 xor edx,edx
7c93eae7 cd2b int 2Bh
7c93eae9 cc int 3
….

KiUserCallbackDispatcher 는 기본적으로 Teb를 통해서 PEB를 얻어 KernelCallbackTable에 등록된 Function를 호출해주는 역할을 합니다. NtUserCreateWindowEx나 NtUserCallNoParam은 어떤 메커니즘으로 KiUserCallbackDispatcher를 호출하게 된것인가 ? NtUserCreateWindowEx는 내부적으로 xxxCreateWindowEx를 호출하고 Windows를 초기화 하게 된다. xxxCreateWindowEx이러한 초기화 과정이 끝나게 되면 내부적으로 초기화가 완료되었음을 알리는 Callback을 User Mode로 보내기 위해서 xxxWindowEvent를 호출하게 됩니다.

PWND xxxCreateWindowEx(
DWORD dwExStyle,
PLARGE_STRING cczpstrClass,
PLARGE_STRING cczpstrName,
DWORD style,
int x,
int y,
int cx,
int cy,
PWND pwndParent,
PMENU pMenu,
HANDLE hInstance,
LPVOID lpCreateParams,
DWORD dwExpWinVerAndFlags)
{
//Initialize Windows

win32k!xxxCreateWindowEx+0xc03:
bf845896 804b1b80 or byte ptr [ebx+1Bh],80h
bf84589a 33f6 xor esi,esi
bf84589c 56 push esi
bf84589d 56 push esi
bf84589e 56 push esi
bf84589f 53 push ebx
bf8458a0 6800800000 push 8000h
bf8458a5 e8b62dfcff call win32k!xxxWindowEvent (bf808660)
bf8458aa f6431410 test byte ptr [ebx+14h],10h
bf8458ae 753c jne win32k!xxxCreateWindowEx+0xc59 (bf8458ec)

}


VOID
xxxWindowEvent(
DWORD event,
PWND pwnd,
LONG idObject,
LONG idChild,
DWORD dwFlags)
{
...
win32k!xxxWindowEvent+0x177:
bf8087cd f60102 test byte ptr [ecx],2
bf8087d0 0f8563ffffff jne win32k!xxxWindowEvent+0×185 (bf808739)
win32k!xxxWindowEvent+0x17c:
bf8087d6 50 push eax
bf8087d7 e8a14f1100 call win32k!xxxProcessNotifyWinEvent (bf91d77d)
bf8087dc 89450c mov dword ptr [ebp+0Ch],eax
bf8087df e955ffffff jmp win32k!xxxWindowEvent+0×185 (bf808739)

}


PEVENTHOOK
xxxProcessNotifyWinEvent(PNOTIFY pNotify)
{
...
win32k!xxxProcessNotifyWinEvent+0xa2:
bf91d81f 8b4624 mov eax,dword ptr [esi+24h]
bf91d822 eb06 jmp win32k!xxxProcessNotifyWinEvent+0xad (bf91d82a)
win32k!xxxProcessNotifyWinEvent+0xa7:
bf91d824 56 push esi
bf91d825 e8c6feffff call win32k!xxxGetEventProc (bf91d6f0)
win32k!xxxProcessNotifyWinEvent+0xad:
bf91d82a 85c0 test eax,eax
bf91d82c 7408 je win32k!xxxProcessNotifyWinEvent+0xb9 (bf91d836)
...
}


WINEVENTPROC
xxxGetEventProc(PEVENTHOOK pEventOrg)
{
...
win32k!xxxGetEventProc+0x2b:
bf91d71b a138ac9abf mov eax,dword ptr [win32k!gptiCurrent (bf9aac38)]
bf91d720 8b402c mov eax,dword ptr [eax+2Ch]
bf91d723 33d2 xor edx,edx
bf91d725 42 inc edx
bf91d726 d3e2 shl edx,cl
bf91d728 8590a4000000 test dword ptr [eax+0A4h],edx
bf91d72e 750a jne win32k!xxxGetEventProc+0×4a (bf91d73a)
win32k!xxxGetEventProc+0x40:
bf91d730 51 push ecx
bf91d731 e89968f4ff call win32k!xxxLoadHmodIndex (bf863fcf)
bf91d736 85c0 test eax,eax
bf91d738 7414 je win32k!xxxGetEventProc+0×5e (bf91d74e)

}


HANDLE xxxLoadHmodIndex(
int iatom,
BOOL bWx86KnownDll)
{
...
win32k!xxxLoadHmodIndex+0x34:
bf863ff5 53 push ebx
bf863ff6 6804010000 push 104h
bf863ffb 8d85f4fdffff lea eax,[ebp-20Ch]
bf864001 50 push eax
bf864002 33c0 xor eax,eax
bf864004 668b0475e0919abf mov ax,word ptr win32k!aatomSysLoaded (bf9a91e0)[esi*2]
bf86400c 50 push eax
bf86400d e85c4bfcff call win32k!UserGetAtomName (bf828b6e)
bf864012 8d85f4fdffff lea eax,[ebp-20Ch]
bf864018 50 push eax
bf864019 8d85ecfdffff lea eax,[ebp-214h]
bf86401f 50 push eax
bf864020 ff1580c998bf call dword ptr [win32k!_imp__RtlInitUnicodeString (bf98c980)]
bf864026 8bc6 mov eax,esi
bf864028 2b0554ab99bf sub eax,dword ptr [win32k!gihmodUserApiHook (bf99ab54)]
bf86402e f7d8 neg eax
bf864030 1bc0 sbb eax,eax
bf864032 f7d0 not eax
bf864034 230504ac9abf and eax,dword ptr [win32k!goffPfnInitUserApiHook (bf9aac04)]
bf86403a 50 push eax
bf86403b 8d85ecfdffff lea eax,[ebp-214h]
bf864041 50 push eax
bf864042 e84efeffff call win32k!ClientLoadLibrary (bf863e95)
bf864047 8bd8 mov ebx,eax
bf864049 85db test ebx,ebx
bf86404b 7511 jne win32k!xxxLoadHmodIndex+0×8c (bf86405e)

}

xxxWindowEvent 는 다시 xxxProcessNotifyWinEvent, xxxGetEventProc, xxxLoadHmodIndex, ClientLoadLibrary 를 순서로 호출하게 되고 ClientLoadLibrary에서는 실제로 UserModeCallback에서 이루어질 Function인 __ClientLoadLibrary를 호출할 수 있도록 해주는 Function Index와 함께 KeUserModeCallback를 호출하게 됩니다. KeUserModeCallback에서는 새로운 UserModeStack을 구성하고 KiCallUserMode를 호출함 으로써 새로 구성된 Stack을 바탕으로 User Mode를 Call하게 되죠


NTSTATUS
KeUserModeCallback (
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength,
OUT PVOID *OutputBuffer,
IN PULONG OutputLength
)
{
...
nt!KeUserModeCallback:
805701cc 6a30 push 30h
805701ce 68e8994e80 push offset nt!KiDebugRegisterContextOffsets+0x24 (804e99e8)
805701d3 e8ab4cf7ff call nt!_SEH_prolog (804e4e83)
805701d8 e8d76cf7ff call nt!KiGetUserModeStackAddress (804e6eb4)

80570218 8943fc mov dword ptr [ebx-4],eax
8057021b 895bf8 mov dword ptr [ebx-8],ebx
8057021e 8b4508 mov eax,dword ptr [ebp+8]
80570221 8943f4 mov dword ptr [ebx-0Ch],eax
80570224 83c3f0 add ebx,0FFFFFFF0h
80570227 832300 and dword ptr [ebx],0
8057022a 8bcb mov ecx,ebx
8057022c 894ddc mov dword ptr [ebp-24h],ecx
8057022f 64a124010000 mov eax,dword ptr fs:[00000124h]
80570235 8945c8 mov dword ptr [ebp-38h],eax
80570238 8b7020 mov esi,dword ptr [eax+20h]
8057023b 8975c4 mov dword ptr [ebp-3Ch],esi
8057023e 8b3e mov edi,dword ptr [esi]
80570240 897dc0 mov dword ptr [ebp-40h],edi
80570243 8b5de4 mov ebx,dword ptr [ebp-1Ch]
80570246 890b mov dword ptr [ebx],ecx
80570248 ff7518 push dword ptr [ebp+18h]
8057024b ff7514 push dword ptr [ebp+14h]
8057024e e8cd6af7ff call nt!KiCallUserMode (804e6d20)
80570253 8945d4 mov dword ptr [ebp-2Ch],eax
80570256 893e mov dword ptr [esi],edi
80570258 834dfcff or dword ptr [ebp-4],0FFFFFFFFh
8057025c 33c0 xor eax,eax
8057025e 40 inc eax
...
}


nt!KiCallUserMode:
804e6d20 55 push ebp
804e6d21 53 push ebx
804e6d22 56 push esi
804e6d23 57 push edi
804e6d24 648b1d24010000 mov ebx,dword ptr fs:[124h] //Current Thread Address를 얻음
804e6d2b 8d842400d0ffff lea eax,[esp-3000h] // Bottom Address 계산
804e6d32 3b431c cmp eax,dword ptr [ebx+1Ch] // Stack Limit와 비교
804e6d35 7317 jae nt!KiCallUserMode+0×2e (804e6d4e)
nt!KiCallUserMode+0x17:
804e6d37 54 push esp
804e6d38 e8774e0100 call nt!MmGrowKernelStack (804fbbb4) // Kernel Stack Grow
804e6d3d 0bc0 or eax,eax
804e6d3f 0f85dc000000 jne nt!KiCallUserMode+0x101 (804e6e21)
nt!KiCallUserMode+0x25:
804e6d45 8b431c mov eax,dword ptr [ebx+1Ch] //새로운 Stack Limit를 얻음
804e6d48 64a308000000 mov dword ptr fs:[00000008h],eax // Stack Limit Set
nt!KiCallUserMode+0x2e:
804e6d4e ffb32c010000 push dword ptr [ebx+12Ch] //Save Callback address
804e6d54 8b9334010000 mov edx,dword ptr [ebx+134h] //Current Trap Frame을 얻는다.
804e6d5a 52 push edx //Save Trap Frame
804e6d5b 8b7318 mov esi,dword ptr [ebx+18h] //Initialize Stack Address
804e6d5e 56 push esi //Save Stack Address
804e6d5f 89a32c010000 mov dword ptr [ebx+12Ch],esp //Save Callback Stack Addresss
804e6d65 83e4f0 and esp,0FFFFFFF0h
804e6d68 8bfc mov edi,esp //새로운 Initialize Stack Set
804e6d6a 81ec10020000 sub esp,210h //NPX Status 관련 코드
804e6d70 81ee10020000 sub esi,210h
804e6d76 fa cli //Disable Interrupt
804e6d77 8b0e mov ecx,dword ptr [esi] //
804e6d79 890c24 mov dword ptr [esp],ecx
804e6d7c 8b4e04 mov ecx,dword ptr [esi+4]
804e6d7f 894c2404 mov dword ptr [esp+4],ecx
804e6d83 8b4e08 mov ecx,dword ptr [esi+8]
804e6d86 894c2408 mov dword ptr [esp+8],ecx
804e6d8a 8b4e18 mov ecx,dword ptr [esi+18h]
804e6d8d 894c2418 mov dword ptr [esp+18h],ecx
804e6d91 8b8e0c020000 mov ecx,dword ptr [esi+20Ch]
804e6d97 898c240c020000 mov dword ptr [esp+20Ch],ecx
804e6d9e 648b3540000000 mov esi,dword ptr fs:[40h] // TSS의 Address를 얻는다.
804e6da5 897b18 mov dword ptr [ebx+18h],edi // Reset Stack Address
804e6da8 64892504000000 mov dword ptr fs:[4],esp // Set Stack Base Address
804e6daf 8bcc mov ecx,esp // Set Kernel Entry Stack Addresss
804e6db1 83ec10 sub esp,10h
804e6db4 f7427000000200 test dword ptr [edx+70h],20000h
804e6dbb 7502 jne nt!KiCallUserMode+0×9f (804e6dbf)
nt!KiCallUserMode+0x9d:
804e6dbd 8bcc mov ecx,esp
nt!KiCallUserMode+0x9f:
804e6dbf 894e04 mov dword ptr [esi+4],ecx
804e6dc2 83ec7c sub esp,7Ch // Trap Frame할당
804e6dc5 8bec mov ebp,esp // Set Trap Frame
804e6dc7 b90b000000 mov ecx,0Bh
804e6dcc 8d7c2450 lea edi,[esp+50h] // Trap Frame 정보 복사
804e6dd0 8d7250 lea esi,[edx+50h]
804e6dd3 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
804e6dd5 f6432cff test byte ptr [ebx+2Ch],0FFh
804e6dd9 7536 jne nt!KiCallUserMode+0xf1 (804e6e11)

nt!KiCallUserMode+0xbb:
// Callback의 Start Address
804e6ddb a174345680 mov eax,dword ptr [nt!KeUserCallbackDispatcher (80563474)]
804e6de0 89442468 mov dword ptr [esp+68h],eax
804e6de4 64a100000000 mov eax,dword ptr fs:[00000000h] // SEH 관련 코드
804e6dea 8944244c mov dword ptr [esp+4Ch],eax
804e6dee 8b4248 mov eax,dword ptr [edx+48h]
804e6df1 89442448 mov dword ptr [esp+48h],eax
804e6df5 fb sti // Interrupt Enable
804e6df6 8b5d60 mov ebx,dword ptr [ebp+60h]
804e6df9 8b7d68 mov edi,dword ptr [ebp+68h]
804e6dfc 89550c mov dword ptr [ebp+0Ch],edx // KiServiceExit 호출
804e6dff c74508000ddbba mov dword ptr [ebp+8],0BADB0D00h
804e6e06 895d00 mov dword ptr [ebp],ebx
804e6e09 897d04 mov dword ptr [ebp+4],edi
804e6e0c e9a08bffff jmp nt!KiServiceExit (804df9b1)

nt!KiCallUserMode+0xf1:
804e6e11 b906000000 mov ecx,6
804e6e16 8d7c2418 lea edi,[esp+18h]
804e6e1a 8d7218 lea esi,[edx+18h]
804e6e1d f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
804e6e1f ebba jmp nt!KiCallUserMode+0xbb (804e6ddb)

nt!KiCallUserMode+0×101:
804e6e21 5f pop edi
804e6e22 5e pop esi
804e6e23 5b pop ebx
804e6e24 5d pop ebp
804e6e25 c20800 ret 8

stdENDP _KiCallUserMode


이러한 과정을 통해서 NtUserCreateWindowEx의 System Stub 호출후 KiUserCallbackDispatcher와 __ClientLoadLibrary가 나타나게 됩니다.

정리하게 되면
NtUserCreateWindowEx -> xxxCreateWindowEx -> xxxWindowEvent -> xxxProcessNotifyWinEvent -> xxxGetEventProc -> xxxLoadHmodIndex -> ClientLoadLibrary ->KeUserModeCallback -> _KiCallUserMode -> KiUserCallbackDispatcher -> __ClientLoadLibrary 의 순으로 CallStack이 구성됩니다.
KiUserCallbackDispatcher의 정체는 User Stack 및 EIP를 조작함으로서 Kernel Mode에서 User Mode로의 Callback을 가능하게 되고 이러한 Callback의 실제 수행 매개체로 추정 할 수 있습니다. 실제로 이러한 Stack은 Win32 GDI Api를 사용하다 보면 종종 만날 수 있으며 Win32k.sys로의 System Stub Code가 호출됬을 때 주로 발생하게 됩니다.