티스토리 뷰

원문(OSR)의 글을 요약, 이해 정리한 글이다.


 

아래의 4가지 시나리오를 통해 NtXxx 함수와 ZwXxx함수에 대해 알아보자.

UserMode에서 NtXxx(NTDLL.DLL) 함수 호출하기

UserMode에서 ZwXxx(NTDLL.DLL) 함수 호출하기

KernelMode에서 NtXxx(NTOSKRNL.EXE) 함수 호출하기

KernelMode에서 ZwXxx(NTOSKRNL.EXE) 함수 호출하기


 

Calling From User Mode

NTDLL.DLL에서 NtXxx의 함수 NtReadFile을 Windbg에서 U 명령어를 통해 disassemble 한 결과는 아래와 같다.


 

  1. 0: kd> u ntdll!NtReadFile
    ntdll!NtReadFile:
    77f761e8 b8b7000000       mov     eax,0xb7
    77f761ed ba0003fe7f       mov     edx,0x7ffe0300
    77f761f2 ffd2             call    edx
    77f761f4 c22400           ret     0x24


 

  1. 0: kd> u ntdll!ZwReadFile
    ntdll!NtReadFile:
    77f761e8 b8b7000000       mov     eax,0xb7
    77f761ed ba0003fe7f       mov     edx,0x7ffe0300
    77f761f2 ffd2             call    edx
    77f761f4 c22400           ret     0x24

 

NtReadFile과 ZwReadFile은 0x77f761e8의 동일주소를 가르킨다.

CALL EDX의 0x7ffe0300의 주소를 ln(list nearest symblos) 명령을 통해 심볼을 본다.


 

  1. 0: kd> ln 0x7ffe0300(7ffe0300)   SharedUserData!SystemCallStub  
    Exact matches:
        SharedUserData!SystemCallStub
  2. 0: kd> u SharedUserData!SystemCallStub
    SharedUserData!SystemCallStub:
    7ffe0300 8bd4             mov     edx,esp
    7ffe0302 0f34             sysenter
    7ffe0304 c3               ret


 

SystemCallStub이란 함수에서 SYSENTER(윈도우2000에서는 INT 2Eh)를 호출한다.


 

SYSENTER(INTEL document)란?

 thread into Kernel Mode and executes the routine pointed to by the SYSENTER_EIP_MSR, which is MSR 0x176.

커널로 전환하며 SYSENTER_EIP_MSR 레지스터가 가르키는 포인터 번지를 실행한다.


 

rdmsr(read msr, msr은 model specific register) 명령어로 SYSENTER_EIP_MSR를 살펴보면

  1. - 0: kd> rdmsr 176
    msr[176] = 00000000:8053a270


 

  1. 0: kd> ln 8053a270
    (8053a270)   nt!KiFastCallEntry   |  (8053a2fb)   nt!KiSystemService
    Exact matches:
        nt!KiFastCallEntry


 

결국 SYSENTER KiFastCallEntry를 호출한다는걸 알수있다.


 

  1. 053a2f9 eb5c jmp     nt!KiSystemService+0x5c (8053a357) 


 

KiFastCallEntry의 마지막(?)을 보면 KiSystemService를 호출한다.


 

아래는 OSR에서 제공한 windbg용 확장 DLL을 설치(windbg내 winext폴더에 복사)하여 !osrexts.sst를 사용하여 System Service Descriptor Table의 내용을 살펴보고 실제 주소를 disassemble한 예제이다.

  1. 0: kd> !osrexts.sst
    0: 0x805912c2  (nt!NtAcceptConnectPort)
    1: 0x805d87b0  (nt!NtAccessCheck)
    2: 0x805dc3e4  (nt!NtAccessCheckAndAuditAlarm)
    ...
    b7: 0x8056b2ec  (nt!NtReadFile)
    ...
  2. 0: kd> u nt!NtReadFile
    nt!NtReadFile:
    8056b2ec 6a58             push    0x58
    8056b2ee 6858044e80       push    0x804e0458
    8056b2f3 e8e09ffcff       call    nt!_SEH_prolog (805352d8)
    8056b2f8 33ff             xor     edi,edi
    8056b2fa 897de4           mov     [ebp-0x1c],edi
    8056b2fd 897de0           mov     [ebp-0x20],edi
    8056b300 897dd8           mov     [ebp-0x28],edi


 

요약

User mode에서  NtXxx , ZwXxx 호출은 결국 같은 루틴이다.

EAX에 index, EDX에 argument pointer 설정

SystemCallStub 를 호출하여 SYSENTER 호출

SYSENTER 는 인터럽트 방지하고 커널모드로 thread를 변경  SYSENTER_EIP_MSR (XP SP1에서는 KiFastCallEntry)를 호출

KiFastCallEntry 는 새로운 trap frame 형성, 인터럽트 사용가능후 KiSystemService 호출

KiSystemService 는 EDX를 통해 파라미터를 전달받고 EAX의 인덱스에 따라 KiServiceTable[EAX] 함수 호출


 

Calling From Kernel Mode 

NTOSKRNL.EXE에서 NtXxx의 함수 NtReadFile을 Windbg에서 U 명령어를 통해 disassemble 한 결과는 아래와 같다.

(NTOSKRNL.EXE 모듈의 심볼명은 nt 이다. 이는 CPU 에 따라 NTOSKRNL.EXE의 이름이 변경되지만 심볼명은 nt 하나로 통일하기 위한것이 아닐까 라고 추정 - 이재홍님)


 

  1. 0: kd> u nt!NtReadFile
    nt!NtReadFile:
    8056b2ec 6a58             push    0x58
    8056b2ee 6858044e80       push    0x804e0458
    8056b2f3 e8e09ffcff       call    nt!_SEH_prolog (805352d8)
    8056b2f8 33ff             xor     edi,edi 

NtReadFile은 원래 함수라고 보여진다.

  1. 0: kd> u nt!ZwReadFile
    nt!ZwReadFile:
    80504d4c b8b7000000       mov     eax,0xb7
    80504d51 8d542404         lea     edx,[esp+0x4]
    80504d55 9c               pushfd
    80504d56 6a08             push    0x8
    80504d58 e89e550300       call    nt!KiSystemService (8053a2fb)
    80504d5d c22400           ret     0x24

ZwReadFile은 이전에 보았던 코드와 유사하다.

EAX에 index 번호 0xb7을 넣고 EDX에 argument pointer를 설정하고 PUSHFD(PUSH EFLAGS)후 KiSystemService 바로 호출

KernelMode이기 때문에 KernelMode로 전환하기 위한 SYSENTER와 KiFastCallEntry 생략되었다.

아래그림은 EFLAGS

eflags.jpg


 

요약

Case A:

Kernel Mode 에서 NtXxx 함수 호출

PreviousMode 변경이 없다.

Case B:

Kernel Mode 에서  ZwXxxx 함수 호출

EAX에 index 번호 0xb7을 넣고 EDX에 argument pointer를 설정하고 PUSHFD(PUSH EFLAGS)후 KiSystemService 바로 호출

PreviousMode KernelMode로 설정

CALL KiServiceTable[EAX]


 

중요한 차이는 KiSysstemService를 통한 과정은 previous mode가 커널모드로 설정하는것이다.

Nt를 바로 호출하는게 overhead가 없지만 Zw호출은 previousmode를 변경한다.


 

Previous Mode

Previous mode란 시스템이 시스템 서비스를 호출하는 곳의 모드를 결정하기 위한 indicator로 사용된다.

Previous mode가  User mode로 설정되면 시스템 서비스 처리 루틴은 호출은 User mode에서 호출되는것으로 인식하고 루틴으로 전달된 파라미터를 사용하기전에 유효성 검사를 필요로 한다.

하지만 커널모드는 그러한 세밀한 조사를 하지 않고 유효하다고 가정한다.


 

 Previous Mode를 구하는 API

  1.  KPROCESSOR_MODE  ExGetPreviousMode( VOID );


 

ZwXxx, NtXxx 호출시 실패하는 경우  

NtXxx 함수를 직접 호출하면 previous mode를 변경되지 않기때문에 호출된 NtXxx 커널 시스템 서비스 루틴이 previous mode가 user mode로 설정된채 임의 user stack상에서 실행될수 있다. 이러한 호출은

Parameters의 유효성 확인 시도를 알지 못하기 때문에 호출실패가 발생할수 있다.

또다른 문제점은 ProbeForRead, ProbeForWrite를 통해 메모리가 Usermode의 메모리인지를 검사할때 Previous mode가 user mode로 설정된 상태에서 시스템 서비스 NtXxx 함수 호출시 커널모드 버퍼를 인자로 넘겨준다면 STATUS_ACCESS_VIOLATION이 발생할수 있다.


 

Sample Code

Sample code는 드라이버에서 c: 드라이버에 파일로 로그를 남기는 코드


 

InitializeObjectAttribute함수를 통해 c: 드라이버에 파일에 대한 핸들생성

  1. switch (operation)
  2. {
       case IOCTL_OSR_ENABLE_LOGGING:
          if (!devExt->LoggingEnabled)
          {
             RtlInitUnicodeString(&logFileName, OSRNTAPI_LOGFILE);
    #ifdef USER_HANDLE
             InitializeObjectAttributes(&oa,
  3. &logFileName, OBJ_CASE_INSENSITIVE,
    NULL, NULL);
  4. #else
             InitializeObjectAttributes(&oa, &logFileName,
                OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE, NULL, NULL);
    #endif
             code = ZwCreateFile(&devExt->LogFileHandle,
                GENERIC_WRITE | SYNCHRONIZE,
                &oa, &iosb, …


 

위의 예제에서 볼수 있듯이 핸들의 2가지가 생성될수 있다.

첫번째는 User mode 상에서 생성된 핸들(같은 프로세스의 context상에서만 접근가능, 다른 프로세스 접근 불가능)과 OBJ_KERNEL_HANDLE의 옵션을 주고 생성한 커널핸들(모든 프로세스, 드라이버에서 접근가능)이 있다.


 

응용프로그램에서 ReadFile 호출

드라이버 IRP_MJ_READ dispatch 함수, 드라이버에서 log write 시도

  1. PreviousMode = UserMode
    code = NtWriteFile(devExt->LogFileHandle,
          NULL,
          NULL,
          NULL,
          &iosb,
          (PVOID)"OsrRead: NtWriteFile\r\n",
          strlen("OsrRead: NtWriteFile\r\n"),
          NULL,
          NULL);
    #ifdef USER_HANDLE
       ASSERT(code == STATUS_ACCESS_VIOLATION);
    #else
    // OBJ_KERNEL_HANDLE
       ASSERT(code == STATUS_INVALID_HANDLE);
    #endif


 

NtWriteFile 호출의 경우

#ifdef USER_HANDLE 정의시 (OBJ_KERNEL_HANDLE를 사용하지 않았을경우)

STATUS_ACCESS_VIOLATION 에러발생

넘겨진 파라미터는 커널버퍼이지만 PreviousMode가 User 이므로 ProbeForRead/Write 를 통해 유효성 검사에서 실패하여 발생

#ifdef USER_HANDLE 미정의시(OBJ_KERNEL_HANDLE를 사용했을경우)

STATUS_INVALID_HANDLE 에러발생

핸들생성시 커널핸들로 생성했지만 PreviousMode가 User 이므로 User모드 핸들에서는 커널핸들을 참조(찾을수 없다)할수없어 invalid 한 핸들에러 발생


 

  1. PreviousMode = KernelMode (KiSystemService에서 change)
    code = ZwWriteFile(devExt->LogFileHandle,
             NULL, NULL, NULL,
             &iosb,
             (PVOID)"OsrRead: ZwWriteFile\r\n",
             strlen("OsrRead: ZwWriteFile\r\n"),
             NULL, NULL);
    ASSERT(code == STATUS_SUCCESS);


 

ZwWriteFile 호출의 경우

어떤 경우에서도 성공


 

Conclusion  

User mode에서는 어떤것을 사용하여도 무방하나 kernel mode에서는 ZwXxx를 사용하여 previous mode가 커널로 설정되어 유효성 검사를 거쳐 시스템 서비스 함수들이 호출되도록 하여야 한다.


 

ETC

MSDN에 따르면 Zw로 시작하는 해당함수를 호출한 프로세스의 대한 접근권한 체크를 하지 않습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함