겸손한 개발을 위한 자양분

kd> !process 0140 0
Searching for Process with Cid == 140
Cid Handle table at e1003000 with 256 Entries in use
PROCESS 81f32be0  SessionId: none  Cid: 0140    Peb: 7ffd9000  ParentCid: 0004
    DirBase: 037d0000  ObjectTable: e100f658  HandleCount:  18.
    Image: smss.exe
 
kd> dt nt!_HANDLE_TABLE e100f658
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe1293000
   +0x004 QuotaProcess     : 0x81f32be0 _EPROCESS
   +0x008 UniqueProcessId  : 0x00000140
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0xe1374a4c - 0xe1000d84 ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null)
   +0x02c ExtraInfoPages   : 0
   +0x030 FirstFree        : 0x50
   +0x034 LastFree         : 0
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount      : 18
   +0x040 Flags            : 0
   +0x040 StrictFIFO       : 0y0
 
kd> dd 0xe1293000
e1293000  00000000 fffffffe e1008591 000f0003
e1293010  81ecdd53 00100020 e1350639 001f0001
e1293020  e14a7bb1 001f0001 e1009e89 000f000f
e1293030  e1387dc1 000f000f 81ed2691 00100001
e1293040  e1004fc9 000f0001 e137c9f9 000f000f
e1293050  81efa4f1 001f0003 e13c5459 00020006
e1293060  81f827e9 001f0003 81f8c009 001f0fff
e1293070  81f8c009 00000400 e13fd539 001f0001
 
kd> dt nt!_HANDLE_TABLE_ENTRY
   +0x000 Object           : Ptr32 Void
   +0x000 ObAttributes     : Uint4B
   +0x000 InfoTable        : Ptr32 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : Uint4B
   +0x004 GrantedAccess    : Uint4B
   +0x004 GrantedAccessIndex : Uint2B
   +0x006 CreatorBackTraceIndex : Uint2B
   +0x004 NextFreeTableEntry : Int4B
 
kd> dt nt!_OBJECT_HEADER e1008591&0xfffffffc
   +0x000 PointerCount     : 17
   +0x004 HandleCount      : 16
   +0x004 NextToFree       : 0x00000010
   +0x008 Type             : 0x81fb5040 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0x10 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x32 '2'
   +0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x00000001
   +0x014 SecurityDescriptor : 0xe100a77a
   +0x018 Body             : _QUAD
 
kd> dt nt!_OBJECT_TYPE 0x81fb5040
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY [ 0x81fb5078 - 0x81fb5078 ]
   +0x040 Name             : _UNICODE_STRING "KeyedEvent"
   +0x048 DefaultObject    : 0x80561b40
   +0x04c Index            : 0x10
   +0x050 TotalNumberOfObjects : 1
   +0x054 TotalNumberOfHandles : 0x10
   +0x058 HighWaterNumberOfObjects : 1
   +0x05c HighWaterNumberOfHandles : 0x11
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : 0x6579654b
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

Win32K.sys 는, GDI 관련된 처리를 하나보군요...

KeServiceDescriptorTableShadow를 통해, 보면,
kd> dds KeServiceDescriptorTableShadow
80552140  80501030 nt!KiServiceTable
80552144  00000000
80552148  0000011c
8055214c  805014a4 nt!KiArgumentTable
80552150  bf997600 win32k!W32pServiceTable
80552154  00000000
80552158  0000029b
8055215c  bf998310 win32k!W32pArgumentTable


win32k!W32pServiceTable 이 보이고~ 내용을 확인해봅니다.
kd> dds win32k!W32pServiceTable
bf997600  ????????
bf997604  ????????
bf997608  ????????
bf99760c  ????????



orz;;; 젠장.. 안그래도 요새 되는일도 없는데, 내용까지 없네요...
페이징 문제인건가...

일단 GDI 관련된 프로세스를 만들어서 붙혀봅니다.

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
...
PROCESS 81d45bd0  SessionId: 0  Cid: 0430    Peb: 7ffdb000  ParentCid: 05a8
    DirBase: 072c0260  ObjectTable: e10e1d18  HandleCount:  13.
    Image: S****ge2.exe

kd> .process /i 81d45bd0 
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.

kd> g
Break instruction exception - code 80000003 (first chance)
nt!RtlpBreakWithStatusInstruction:
80526da8 cc              int     3


다시 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
bf997620  bf9479d0 win32k!NtGdiAngleArc
bf997624  bf933a9d win32k!NtGdiAnyLinkedFonts
bf997628  bf947fc8 win32k!NtGdiFontIsLinked
bf99762c  bf90e7e0 win32k!NtGdiArcInternal
bf997630  bf88e5fe win32k!NtGdiBeginPath

나옵니다. 낄낄...

좀전에 걸려있던 쓰레드는, GDI 를 안쓰나 봅니다. 확인은 귀찮아서 패스하고...
nt 모듈에서 그랬던 것처럼, W32pServiceTable 이라는 변수를  파일에서 확인해보면,

.data:BF999B80 _W32pServiceTable dd offset _NtGdiAbortDoc@4
.data:BF999B80                                         ; DATA XREF: DriverEntry(x,x)+F6o
.data:BF999B80                                         ; NtGdiAbortDoc(x)
.data:BF999B84                 dd offset _NtGdiAbortPath@4 ; NtGdiAbortPath(x)
.data:BF999B88                 dd offset _NtGdiAddFontResourceW@24 ; NtGdiAddFontResourceW(x,x,x,x,x,x)
.data:BF999B8C                 dd offset _NtGdiAddRemoteFontToDC@16 ; NtGdiAddRemoteFontToDC(x,x,x,x)
.data:BF999B90                 dd offset _NtGdiAddFontMemResourceEx@20 ; NtGdiAddFontMemResourceEx(x,x,x,x,x)
.data:BF999B94                 dd offset _NtGdiRemoveMergeFont@8 ; NtGdiRemoveMergeFont(x,x)
.data:BF999B98                 dd offset _NtGdiAddRemoteMMInstanceToDC@12 ; NtGdiAddRemoteMMInstanceToDC(x,x,x)
.data:BF999B9C                 dd offset _NtGdiAlphaBlend@48 ; NtGdiAlphaBlend(x,x,x,x,x,x,x,x,x,x,x,x)
.data:BF999BA0                 dd offset _NtGdiAngleArc@24 ; NtGdiAngleArc(x,x,x,x,x,x)
.data:BF999BA4                 dd offset _NtGdiAnyLinkedFonts@0 ; NtGdiAnyLinkedFonts()
.data:BF999BA8                 dd offset _NtGdiFontIsLinked@4 ; NtGdiFontIsLinked(x)
.data:BF999BAC                 dd offset _NtGdiArcInternal@40 ; NtGdiArcInternal(x,x,x,x,x,x,x,x,x,x)
.data:BF999BB0                 dd offset _NtGdiBeginPath@4 ; NtGdiBeginPath(x)
.data:BF999BB4                 dd offset _NtGdiBitBlt@44 ; NtGdiBitBlt(x,x,x,x,x,x,x,x,x,x,x)
.data:BF999BB8                 dd offset _NtGdiCancelDC@4 ; NtGdiCancelDC(x)
.data:BF999BBC                 dd offset _NtGdiCheckBitmapBits@32 ; NtGdiCheckBitmapBits(x,x,x,x,x,x,x,x)
.data:BF999BC0                 dd offset _NtGdiCloseFigure@4 ; NtGdiCloseFigure(x)
.data:BF999BC4                 dd offset _NtGdiClearBitmapAttributes@8 ; NtGdiClearBitmapAttributes(x,x)
.data:BF999BC8                 dd offset _NtGdiClearBrushAttributes@8 ; NtGdiClearBrushAttributes(x,x)


우왕ㅋ 굿~
DriverEntry 에서 셋팅하나봅니다.

INIT:BF9AFD0F ; __stdcall DriverEntry(x, x)
INIT:BF9AFD0F _DriverEntry@8  proc near               ; DATA XREF: DriverEntry(x,x)+1CEo
INIT:BF9AFD0F
....
INIT:BF9AFDF2                 push    edi
INIT:BF9AFDF3                 push    offset _W32pArgumentTable
INIT:BF9AFDF8                 push    _W32pServiceLimit
INIT:BF9AFDFE                 mov     _countTable, esi
INIT:BF9AFE04                 push    esi
INIT:BF9AFE05                 push    offset _W32pServiceTable
INIT:BF9AFE0A                 call    ds:__imp__KeAddSystemServiceTable@20 ; KeAddSystemServiceTable(x,x,x,x,x)


코드를 보니, W32pServiceTable 변수를 인자로,
KeAddSystemServiceTable 함수를 호출하여, SST를 등록하는 것 같습니다.

뭐... 끝 났네요...
win32k.sys 의 DriverEntry 로부터,
KeAddSystemServiceTable 의 주소를 호출하는 CALL 문을 찾는,
소스 코드 작성하면 되겠군요.

orz;;;

SDT Hooking 은, Service Descriptor Table 에 등록된 각 함수들의 Addr 을 변경하여
... 하.. 설명 귀찮...

어쨋든,

시스템의 SSDT 주소는 KeServiceDescriptorTable 이라는 커널 변수로 export 되어있습니다.
이 커널 변수가 가진 내용을 보면,

kd> dds KeServiceDescriptorTable
80552180  80501030 nt!KiServiceTable
80552184  00000000
80552188  0000011c
8055218c  805014a4 nt!KiArgumentTable

위와 같이 nt 모듈의 KiServiceTable 을 보고 있습니다.

kd> dds nt!KiServiceTable
80501030  8059849a nt!NtAcceptConnectPort
80501034  805e5666 nt!NtAccessCheck
80501038  805e8ec4 nt!NtAccessCheckAndAuditAlarm
8050103c  805e5698 nt!NtAccessCheckByType
80501040  805e8efe nt!NtAccessCheckByTypeAndAuditAlarm
80501044  805e56ce nt!NtAccessCheckByTypeResultList
80501048  805e8f42 nt!NtAccessCheckByTypeResultListAndAuditAlarm



실제 테이블이 위치한 nt 모듈의 섹션을 확인하기 위해, PE 정보와 모듈의 베이스를 연산해보면

kd> dt /r nt!_IMAGE_NT_HEADERS 804d7000+0xe8
   +0x000 Signature        : 0x4550
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
      +0x000 Machine          : 0x14c
      +0x002 NumberOfSections : 0x19
      +0x004 TimeDateStamp    : 0x41107b0c
      +0x008 PointerToSymbolTable : 0
      +0x00c NumberOfSymbols  : 0
      +0x010 SizeOfOptionalHeader : 0xe0
      +0x012 Characteristics  : 0x12e
   +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER
      +0x000 Magic            : 0x10b
      +0x002 MajorLinkerVersion : 0x7 ''
      +0x003 MinorLinkerVersion : 0xa ''
      +0x004 SizeOfCode       : 0x19cd00
      +0x008 SizeOfInitializedData : 0x58f80
      +0x00c SizeOfUninitializedData : 0
      +0x010 AddressOfEntryPoint : 0x1b66dc
      +0x014 BaseOfCode       : 0x600
      +0x018 BaseOfData       : 0x6da00

 

kd> lm
start    end        module name
7c900000 7c9b0000   ntdll      (pdb symbols)          c:\symbols\ntdll.pdb\
804d7000 806cd280   nt         (pdb symbols)          c:\symbols\ntkrnlpa.pdb\

nt 모듈의 코드 영역에 들어있는 테이블임을 알 수 있고.
코드 영역에 들어있는 테이블 이므로, 파일에서도 같은 테이블을 갖고 있다고 예상할 수 있습니다.


다음은 IDA 로 nt 파일을 열어, 찾은 KiServiceTable 입니다.

.text:0040D8A0 _KiServiceTable dd offset _NtAcceptConnectPort@24
.text:0040D8A0                                         ; DATA XREF: KiInitSystem()+116o
.text:0040D8A0                                         ; NtAcceptConnectPort(x,x,x,x,x,x)
.text:0040D8A4                 dd offset _NtAccessCheck@32 ; NtAccessCheck(x,x,x,x,x,x,x,x)
.text:0040D8A8                 dd offset _NtAccessCheckAndAuditAlarm@44 ; NtAccessCheckAndAuditAlarm(x,x,x,x,x,x,x,x,x,x,x)
.text:0040D8AC                 dd offset _NtAccessCheckByType@44 ; NtAccessCheckByType(x,x,x,x,x,x,x,x,x,x,x)
.text:0040D8B0                 dd offset _NtAccessCheckByTypeAndAuditAlarm@64 ; NtAccessCheckByTypeAndAuditAlarm(x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x)
.text:0040D8B4                 dd offset _NtAccessCheckByTypeResultList@44 ; NtAccessCheckByTypeResultList(x,x,x,x,x,x,x,x,x,x,x)
.text:0040D8B8                 dd offset _NtAccessCheckByTypeResultListAndAuditAlarm@64 ; NtAccessCheckByTypeResultListAndAuditAlarm(x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x)
.text:0040D8BC                 dd offset _NtAccessCheckByTypeResultListAndAuditAlarmByHandle@68 ; NtAccessCheckByTypeResultListAndAuditAlarmByHandle(x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x)
.text:0040D8C0                 dd offset _NtAddAtom@12 ; NtAddAtom(x,x,x)
.text:0040D8C4                 dd offset _NtEnumerateBootEntries@8 ; NtEnumerateBootEntries(x,x)
.text:0040D8C8                 dd offset _NtAdjustGroupsToken@24 ; NtAdjustGroupsToken(x,x,x,x,x,x)
.text:0040D8CC                 dd offset _NtAdjustPrivilegesToken@24 ; NtAdjustPrivilegesToken(x,x,x,x,x,x)
.text:0040D8D0                 dd offset _NtAlertResumeThread@8 ; NtAlertResumeThread(x,x)
.text:0040D8D4                 dd offset _NtAlertThread@4 ; NtAlertThread(x)
.text:0040D8D8                 dd offset _NtAllocateLocallyUniqueId@4 ; NtAllocateLocallyUniqueId(x)
.text:0040D8DC                 dd offset _NtAllocateUserPhysicalPages@12 ; NtAllocateUserPhysicalPages(x,x,x)
.text:0040D8E0                 dd offset _NtAllocateUuids@16 ; NtAllocateUuids(x,x,x,x)
.text:0040D8E4                 dd offset _NtAllocateVirtualMemory@24 ; NtAllocateVirtualMemory(x,x,x,x,x,x)

KiServiceTable 을 KiInitSystem() 이라는 함수에서 사용하고 있습니다.


이 함수의 내용을 확인해보면,

INIT:005EF1DE ; __stdcall KiInitSystem()
INIT:005EF1DE _KiInitSystem@0 proc near               ; CODE XREF: KiInitializeKernel(x,x,x,x,x,x)+230p
INIT:005EF1DE                 push    20h
INIT:005EF1E0                 mov     eax, offset _KiDispatcherReadyListHead
...
INIT:005EF2E0                 mov     ds:byte_48B46C, 1
INIT:005EF2E7                 mov     ds:byte_48B46E, 4
INIT:005EF2EE                 mov     ds:dword_48B470, esi
INIT:005EF2F4                 mov     ds:_KeServiceDescriptorTable, offset _KiServiceTable
INIT:005EF2FE                 mov     ds:dword_48B524, esi
INIT:005EF304                 mov     ds:dword_48B52C, offset _KiArgumentTable
...
INIT:005EF32A                 pop     esi
INIT:005EF32B                 retn
INIT:005EF32B _KiInitSystem@0 endp

KeServiceDescriptorTable 커널 변수를 설정하는 본체 함수임을 알 수 있습니다.

위의 내용을 종합했을 때,
nt 파일을 열어, KiServiceTable 의 Offset 을 알아내면, 실제 SystemService 들의 Original RVA 를 알 수 있을것 같습니다.

다음은 이러한 내용을 바탕으로 SDT 를 복원하는 방법입니다.
단, 이 소스는 파일을 직접 열어 OFFSET 연산을 한 것은 아니고,
LoadLibraryEx API 를 사용하여, 파일을 메모리에 올리는 것으로 되어있습니다.
( http://rootkit.com/newsread.php?newsid=176 )


코드의 주 내용은, KiInitSystem() 함수 내부에서

INIT:005EF2F4                 mov     ds:_KeServiceDescriptorTable, offset _KiServiceTable

부분을 찾아 _KiServiceTable 의 offset 을 알아내는 것 입니다.

실제 사용된 코드는 다음과 같습니다.

if (dwPointsToRva==dwKSDT) {
 // check for mov [mem32],imm32. we are trying to find
 // "mov ds:_KeServiceDescriptorTable.Base, offset _KiServiceTable"
 // from the KiInitSystem.
 if (*(PWORD)((DWORD)hModule+dwPointerRva-2)==0x05c7) {
  // should check for a reloc presence on KiServiceTable here
  // but forget it
  dwKiServiceTable=*(PDWORD)((DWORD)hModule+dwPointerRva+4)-poh->ImageBase;
  return dwKiServiceTable;
 }
}

export 된, KeServiceDescriptorTable 의 주소는 이미 알고 있으므로,
해당 주소에 특정값을 대입하는 Asm OPCODE 를 검색하고
그 명령줄에서 KiServiceTable(dwKiServiceTable) 의 offset 을 반환합니다.

위의 offset 에서 알아낸 RVA를 연산하여 확인한다면,
SDT Hooking 을 풀 수... 있을지는 사실 미지수입니다.
국내외 보안 제품들의 경우, SDT Hooking 을 풀어버리면, 다시 걸고 걸고 반복하면서 BSOD 를 내는 경우가 많은듯합니다.

... orz;;; 좌절~

참고 : 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


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