겸손한 개발을 위한 자양분

참고 : NTDLL.DLL 에서의 sysenter

NTDLL 이 제공하는 API 는,
eax 에 Service Table 의 Index 를 설정하고 sysenter 를 호출합니다.
하지만, USER32.DLL 은 eax 에 설정되는 값의 모양이 약간 다릅니다.

SendMessageA 함수의 호출 구조를 살펴보면서, 다른 점을 찾아보겠습니다.

SendMessageA 가 호출하는 함수들을 확인하면 다음과 같습니다. 
kd> uf /c USER32!SendMessageA
Flow analysis was incomplete, some code may be missing
USER32!SendMessageA (77d4e2ae)
  USER32!SendMessageA+0x2a (77d4e2db):
    call to USER32!ValidateHwnd (77d48490)
  USER32!SendMessageA+0x41 (77d4e2f2):
    call to USER32!SendMessageWorker (77d4b67b)
  USER32!SendMessageA+0x48 (77d6da92):
    call to USER32!GetDesktopWindow (77d4d7bb)

함수들의 용도를 볼때, 메인 함수는 SendMessageWorker 일 것으로 생각됩니다.
다시 이 함수가 호출하는 함수를 확인하면,

kd> uf /c USER32!SendMessageWorker
Flow analysis was incomplete, some code may be missing
USER32!SendMessageWorker (77d4b67b)
  USER32!SendMessageWorker+0x31 (77d4b6ac):
    call to USER32!PtiCurrent (77d48625)
  USER32!SendMessageWorker+0x26f (77d4b70e):
    call to USER32!IsInsideUserApiHook (77d4860c)
  USER32!SendMessageWorker+0x4a0 (77d4b73e):
    call to USER32!UserCallWinProcCheckWow (77d48734)
  USER32!SendMessageWorker+0x4d2 (77d4c0d9):
    call to USER32!NtUserMessageCall (77d494d7)
  USER32!SendMessageWorker+0x4e4 (77d4de67):
    unresolvable call: call    dword ptr USER32!gapfnScSendMessage (77d418e8)[eax*4]
  USER32!SendMessageWorker+0x149 (77d70edd):
    call to USER32!UserSetLastError (77d4f41f)
  USER32!SendMessageWorker+0x180 (77d70ef7):
    call to USER32!RtlMBMessageWParamCharToWCS (77d4da9f)
  USER32!SendMessageWorker+0x1c1 (77d70f38):
    call to kernel32!IsDBCSLeadByteEx (7c87a17a)
  USER32!SendMessageWorker+0x1e1 (77d70f58):
    call to USER32!RtlWCSMessageWParamCharToMB (77d4c4ef)

NtUserMessageCall 이라는 함수가 보입니다.
( 정확히 하자면, 이 함수가 메인인지 확인해야 하지만, sysenter 에서의 system service 확인이 목적이므로 생략합니다. )

kd> uf USER32!NtUserMessageCall
USER32!NtUserMessageCall:
77d494d7 b8cc110000      mov     eax,11CCh
77d494dc ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
77d494e1 ff12            call    dword ptr [edx]
77d494e3 c21c00          ret     1Ch

NTDLL.DLL 에서 어플리케이션 레벨의 마지막 호출과 마찬가지로
ntdll!KiFastSystemCall 이 호출되는 것을 알 수 있습니다.

kd> dds 7ffe0300
7ffe0300  7c90eb8b ntdll!KiFastSystemCall


이제 실제 NtUserMessageCall 에 브레이크 포인트를 걸고

kd> !process
PROCESS 820c7020  SessionId: 0  Cid: 07b8    Peb: 7ffd4000  ParentCid: 058c
kd> bp /p 820c7020 USER32!NtUserMessageCall

kd> g
Breakpoint 13 hit
USER32!NtUserMessageCall:
001b:77d494d7 b8cc110000      mov     eax,11CCh


sysenter 직전의 레지스터를 확인합니다.

kd> r
eax=000011cc ebx=00000000 ecx=0012fad8 edx=0012faac esi=00521770 edi=00000202
eip=7c90eb8d esp=0012faac ebp=0012fae4 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000246
ntdll!KiFastSystemCall+0x2:
001b:7c90eb8d 0f34            sysenter


sysenter 의 동작 방식에 따라, 브레이크포인트를 걸고

kd> bp /p 820c7020 nt!KiFastCallEntry

kd> g
Breakpoint 1 hit
nt!KiFastCallEntry:
8053c710 b923000000      mov     ecx,23h
kd> r
eax=000011cc ebx=00000000 ecx=0012fad8 edx=0012faac esi=00521770 edi=00000202
eip=8053c710 esp=f8ac6000 ebp=0012fae4 iopl=0         nv up di pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000046
nt!KiFastCallEntry:
8053c710 b923000000      mov     ecx,23h

kd> bd 0-1


커널에서의 KiFastCallEntry 를 분석하면,

nt!KiFastCallEntry:
8053c710 b923000000      mov     ecx,23h
8053c715 6a30            push    30h
8053c717 0fa1            pop     fs
8053c719 8ed9            mov     ds,cx
8053c71b 8ec1            mov     es,cx
8053c71d 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
8053c723 8b6104          mov     esp,dword ptr [ecx+4]
8053c726 6a23            push    23h
8053c728 52              push    edx
8053c729 9c              pushfd
8053c72a 6a02            push    2
8053c72c 83c208          add     edx,8
...
8053c79d 8bf8            mov     edi,eax  -> edi=000011cc
8053c79f c1ef08          shr     edi,8  -> edi=00000011
8053c7a2 83e730          and     edi,30h  -> edi=00000010
8053c7a5 8bcf            mov     ecx,edi  -> ecx=edi=00000010
8053c7a7 03bee0000000    add     edi,dword ptr [esi+0E0h] ds:0023:820dfc10={nt!KeServiceDescriptorTableShadow (80552140)}

8053c7ad 8bd8            mov     ebx,eax
8053c7af 25ff0f0000      and     eax,0FFFh  -> eax=000001cc
8053c7b4 3b4708          cmp     eax,dword ptr [edi+8]
8053c7b7 0f8345fdffff    jae     nt!KiBBTUnexpectedRange (8053c502)
8053c7bd 83f910          cmp     ecx,10h
8053c7c0 751a            jne     nt!KiFastCallEntry+0xcc (8053c7dc)
8053c7c2 8b0d18f0dfff    mov     ecx,dword ptr ds:[0FFDFF018h]
8053c7c8 33db            xor     ebx,ebx
8053c7ca 0b99700f0000    or      ebx,dword ptr [ecx+0F70h]
8053c7d0 740a            je      nt!KiFastCallEntry+0xcc (8053c7dc)
8053c7d2 52              push    edx
8053c7d3 50              push    eax
8053c7d4 ff15c4215580    call    dword ptr [nt!KeGdiFlushUserBatch (805521c4)]
8053c7da 58              pop     eax
8053c7db 5a              pop     edx
8053c7dc ff0538f6dfff    inc     dword ptr ds:[0FFDFF638h]
8053c7e2 8bf2            mov     esi,edx
8053c7e4 8b5f0c          mov     ebx,dword ptr [edi+0Ch]
8053c7e7 33c9            xor     ecx,ecx
8053c7e9 8a0c18          mov     cl,byte ptr [eax+ebx]
8053c7ec 8b3f            mov     edi,dword ptr [edi]  ds:0023:80552150={win32k!W32pServiceTable (bf997600)}
8053c7ee 8b1c87          mov     ebx,dword ptr [edi+eax*4] ds:0023:bf997d30={win32k!NtUserMessageCall (bf80f615)}

8053c7f1 2be1            sub     esp,ecx
8053c7f3 c1e902          shr     ecx,2
8053c7f6 8bfc            mov     edi,esp
8053c7f8 3b35b47b5580    cmp     esi,dword ptr [nt!MmUserProbeAddress (80557bb4)]
8053c7fe 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (8053c9ac)
8053c804 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8053c806 ffd3            call    ebx {win32k!NtUserMessageCall (bf80f615)}
8053c808 8be5            mov     esp,ebp
...

NTDLL.DLL 에서 제공되는 API 는 SystemServiceIndex 를 eax 에 설정하였지만,
USER32.DLL 에서 제공되는 API 는 eax 에 설정된 값에 0x0FFF 를 AND 연산하여 호출하는 것을 확인할 수 있습니다.

또한, NTDLL.DLL 은 KeServiceDescriptorTableShadow[0]을 참조하는데
USER32.DLL 은 KeServiceDescriptorTableShadow[1]을 참조하고 있습니다.

메모리에서 확인하면 다음과 같습니다.

kd> dd nt!KeServiceDescriptorTableShadow
80552140  80501030 00000000 0000011c 805014a4 ; KeServiceDescriptorTableShadow[0]
80552150  bf997600 00000000 0000029b bf998310 ; KeServiceDescriptorTableShadow[1]
80552160  00000000 00000000 00000000 00000000
80552170  00000000 00000000 00000000 00000000
80552180  80501030 00000000 0000011c 805014a4
80552190  00000000 00000000 00000000 00000000
805521a0  00000000 00000000 00000000 00000000
805521b0  00000000 00000000 00000000 00000000

이 때, KeServiceDescriptorTableShadow[1] 은 win32k!W32pServiceTable 입니다. 

kd> dds win32k!W32pServiceTable
bf997600  bf934ffe win32k!NtGdiAbortDoc
bf997604  bf946a92 win32k!NtGdiAbortPath
bf997608  bf8bf295 win32k!NtGdiAddFontResourceW
bf99760c  bf93e718 win32k!NtGdiAddRemoteFontToDC
bf997610  bf9480a9 win32k!NtGdiAddFontMemResourceEx
bf997614  bf935262 win32k!NtGdiRemoveMergeFont
bf997618  bf935307 win32k!NtGdiAddRemoteMMInstanceToDC
bf99761c  bf839cb5 win32k!NtGdiAlphaBlend


sysenter 호출시의 eax 값이 0x11cc 이고,
앞의 디어셈 결과에 따라 service index 는 0x1cc 이므로,

USER32.DLL 이 제공하는 API 의 System Service 는,
다음과 같이 확인할 수 있습니다.

kd> dds bf997600 + (0x1cc * 0x4)
bf997d30  bf80f615 win32k!NtUserMessageCall


하지만...
대충 풀어본거라서, 정답인지는 잘 모르겠네요 ㅎㅎ;;