겸손한 개발을 위한 자양분

SYSENTER

MYDN2009. 5. 12. 18:09

SYSENTER 는 level 0 의 시스템 프로시져를 호출하기 위한 명령어입니다.

쉽게 얘기한다면, 어플리케이션 레벨에서 커널 레벨로 진입하기 위한 명령이며
Windows XP 를 기준으로 했을 때, API 의 호출은 다음의 예와 같습니다.


예-1) OpenProcess 의 System Service 호출

Application Level

kernel32!OpenProcess

ntdll!ZwOpenProcess

ntdll!KiFastSystemCall
nt!KiFastCallEntry

nt!NtOpenProcess

Kernel Level


예-2) SendMessage 의 System Service 호출

Application Level

USER32!SendMessageA

USER32!SendMessageWorker

USER32!NtUserMessageCall

ntdll!KiFastSystemCall
nt!KiFastCallEntry

win32k!NtUserMessageCall

Kernel Level


두 가지 예를 보면, 어플리케이션 레벨에서 마지막으로 불리는 함수는 ntdll!KiFastSystemCall 이고, 커널 레벨에서 처음 불리는 함수는 nt!KiFastCallEntry 로 동일한 것을 볼 수 있습니다. 이 때, (XP 일 때) ntdll!KiFastSystemCall 에서 사용되는 명령이 바로 sysenter 입니다.


nt!KiFastCallEntry 에서, 어플리케이션이 원하는 시스템 서비스를 호출하는 과정은 다음과 같이 확인할 수 있습니다.


분석하고자 하는 API가 어떤 함수들을 호출하고 있는지 확인합니다.
OpenProcess 를 확인해보겠습니다.

kd> uf /c kernel32!OpenProcess
kernel32!OpenProcess (7c81e079)
  kernel32!OpenProcess+0x43 (7c81e0bc):
    call to ntdll!ZwOpenProcess (7c90dd7b)
  kernel32!OpenProcess+0x54 (7c838bc7):
    call to kernel32!BaseSetLastNTError (7c80937b)

windbg 의 명령어를 사용하여 OpenProcess 는, ntdll 의 ZwOpenProcess 를 호출하는 것을 알 수 있습니다.
(굳이 확인할 필요는 없지만) 이것을 실제 코드에서 확인해 보면,

kd> uf kernel32!OpenProcess
kernel32!OpenProcess:
7c81e079 8bff            mov     edi,edi
7c81e07b 55              push    ebp
7c81e07c 8bec            mov     ebp,esp
7c81e07e 83ec20          sub     esp,20h
7c81e081 8b4510          mov     eax,dword ptr [ebp+10h]
7c81e084 8945f8          mov     dword ptr [ebp-8],eax
7c81e087 8b450c          mov     eax,dword ptr [ebp+0Ch]
7c81e08a 56              push    esi
7c81e08b 33f6            xor     esi,esi
7c81e08d f7d8            neg     eax
7c81e08f 1bc0            sbb     eax,eax
7c81e091 83e002          and     eax,2
7c81e094 8945ec          mov     dword ptr [ebp-14h],eax
7c81e097 8d45f8          lea     eax,[ebp-8]
7c81e09a 50              push    eax
7c81e09b 8d45e0          lea     eax,[ebp-20h]
7c81e09e 50              push    eax
7c81e09f ff7508          push    dword ptr [ebp+8]
7c81e0a2 8d4510          lea     eax,[ebp+10h]
7c81e0a5 50              push    eax
7c81e0a6 8975fc          mov     dword ptr [ebp-4],esi
7c81e0a9 c745e018000000  mov     dword ptr [ebp-20h],18h
7c81e0b0 8975e4          mov     dword ptr [ebp-1Ch],esi
7c81e0b3 8975e8          mov     dword ptr [ebp-18h],esi
7c81e0b6 8975f0          mov     dword ptr [ebp-10h],esi
7c81e0b9 8975f4          mov     dword ptr [ebp-0Ch],esi
7c81e0bc ff150c11807c    call    dword ptr [kernel32!_imp__NtOpenProcess (7c80110c)]
7c81e0c2 3bc6            cmp     eax,esi
7c81e0c4 5e              pop     esi
7c81e0c5 0f8cfbaa0100    jl      kernel32!OpenProcess+0x53 (7c838bc6)
...

위와 같이 kernel32!_imp__NtOpenProcess 가 호출되는 것을 볼 수 있는데요,
해당 포인터의 값을 확인해보면

kd> dd 7c80110c
7c80110c  7c90dd7b 7c9505c7 7c95081a 7c950689
7c80111c  7c9507d9 7c90e5c4 7c9506de 7c90e030
7c80112c  7c95085c 7c95070d 7c950734 7c950759
7c80113c  7c9140fd 7c9135c0 7c92bff2 7c90d421
7c80114c  7c92be02 7c90d9b5 7c928436 7c90d865
7c80115c  7c92b4dd 7c90dfc7 7c92c095 7c926459
7c80116c  7c90f1cb 7c912f9b 7c929794 7c914210
7c80117c  7c9010ed 7c901005 7c90d976 7c929464

 다음과 같이 ntdll!ZwOpenProcess 인 것을 알 수 있습니다.

kd> uf 7c90dd7b
ntdll!ZwOpenProcess:
7c90dd7b b87a000000      mov     eax,7Ah
7c90dd80 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c90dd85 ff12            call    dword ptr [edx]
7c90dd87 c21000          ret     10h


ZwOpenProcess 는 다시 7ffe0300를 호출합니다.

kd> dd 7ffe0300
7ffe0300  7c90eb8b 7c90eb94 00000000 00000000
7ffe0310  00000000 00000000 00000000 00000000
7ffe0320  00000000 00000000 00000000 00000000
7ffe0330  ac7348b3 00000000 00000000 00000000
7ffe0340  00000000 00000000 00000000 00000000
7ffe0350  00000000 00000000 00000000 00000000
7ffe0360  00000000 00000000 00000000 00000000
7ffe0370  00000000 00000000 00000000 00000000

이 위치를 확인해보면  KiFastSystemCall 인 것을 알 수 있습니다.

kd> uf 7c90eb8b
ntdll!KiFastSystemCall:
7c90eb8b 8bd4            mov     edx,esp
7c90eb8d 0f34            sysenter
7c90eb8f 90              nop
7c90eb90 90              nop
7c90eb91 90              nop
7c90eb92 90              nop
7c90eb93 90              nop
7c90eb94 c3              ret

그리고, KiFastSystemCall 에서 드디어, sysenter 명령이 등장합니다.

Intel 에서 제공하는 INSTRUCTION SET REFERENCE 를 보면,
sysenter 에 대하여 다음과 같은 내용을 확인할 수 있습니다.

SYSENTER - Fast System Call

MSRs Used By the SYSENTER and SYSEXIT Instructions
IA32_SYSENTER_CS   0x174  code segment selector
IA32_SYSENTER_ESP  0x175  stack
IA32_SYSENTER_EIP  0x176  code segment to the first instruction of the selected oparating procedure or routine.

대충 보면, IA32_SYSENTER_EIP 에, 수행할 프로시져의 첫 주소가 있다는 얘기 같습니다.

IA32_SYSENTER_EIP는 0x176의 Address 를 갖고 있으므로 다음과 같이 확인합니다.

kd> rdmsr 0x176
msr[176] = 00000000`8053c710

해당 주소가 가리키는 함수가 무엇인지 확인해보면,

kd> ln 8053c710
(8053c710)   nt!KiFastCallEntry   |  (8053c819)   nt!KiServiceExit
Exact matches:
    nt!KiFastCallEntry = <no type information>

위와 같이 nt!KiFastCallEntry 임을 알 수 있습니다.
정리하면, sysenter 에 의해서 MSR 0x176 에 등록된 함수가 실행되고, 이 함수가 KiFastCall Entry 인것 같습니다.

MSR 0x176 에 등록된 주소가 커널 주소이기 때문에 이 부분에서 특권 전환이 나타날 것 같습니다.


실제 OpenProcess의 호출을 따라가기 위해 ZwOpenProcess 에 브레이크 포인트를 걸고
(주. 실제로 커널에서 호출을 따라갈 때에는 특정 프로세스를 타겟으로 하는 것이 좋을 것 같습니다.)

kd> bp ntdll!ZwOpenProcess

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

kd> r
eax=0000007a ebx=00b10388 ecx=00357290 edx=00abfc10 esi=00000000 edi=00b109e8
eip=7c90eb8d esp=00abfc10 ebp=00abfc4c 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


이제 특권 전환이 일어나고,
KiFastCallEntry 가 수행됩니다.

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
8053c79f c1ef08          shr     edi,8
8053c7a2 83e730          and     edi,30h
8053c7a5 8bcf            mov     ecx,edi
8053c7a7 03bee0000000    add     edi,dword ptr [esi+0E0h] ds:0023:820dfc10={nt!KeServiceDescriptorTableShadow (80552140)}
8053c7ad 8bd8            mov     ebx,eax
8053c7af 25ff0f0000      and     eax,0FFFh
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]
8053c7ee 8b1c87          mov     ebx,dword ptr [edi+eax*4] ds:0023:80501218={nt!NtOpenProcess (805bfb78)}
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 {nt!NtOpenProcess (805bfb78)}
8053c808 8be5            mov     esp,ebp
...

KeServiceDescriptorTableShadow 를 베이스 주소로 하여, eax*4만큼 떨어져있는 포인터의 함수를 호출하는것을 확인할 수 있습니다.

이 과정을 연산으로 확인해본다면,

kd> dd nt!KeServiceDescriptorTableShadow
80552140  80501030 00000000 0000011c 805014a4
80552150  bf997600 00000000 0000029b bf998310
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
kd> dds 80501030
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
8050104c  805e8f86 nt!NtAccessCheckByTypeResultListAndAuditAlarmByHandle

KeServiceDescriptorTableShadow 의 주소가 80501030 이고,
앞서 sysenter 호출 직전에 확인했던 레지스터에서 eax가 0x7a 이므로,

다음과 같이 나타낼 수 있을겁니다.

kd> dds 80501030 + (0x7a * 0x4)
80501218  805bfb78 nt!NtOpenProcess

확인해 보면, 해당 주소는 nt!NtOpenProcess 의 함수 포인터인것을 알 수 있습니다.

정리하면,
sysenter 직전의 eax 값을 확인하여 해당 API 가 호출하는 System Service 를 확인 할 수 있고,
이로서 eax 의 값이 System Service 의 Index 라는 것을 알 수 있습니다.

단, 이 내용은 모든 API 에 해당하는 것은 아니며,
우선, ntoskrnl 에 연결되는 시스템 서비스일 경우에 해당하는 것 같습니다.

win32k 에 연결되는 API 의 경우, eax 값의 설정이 약간 다른 모양을 볼 수 있습니다.

to be continued...