본문 바로가기

My Study/Programming&Theory

Windows x64 시스템 콜 User Area

항상 x86에서만 놀다가 시대에 뒤쳐졌지만 이제서야 서서히 x64에 발을 넣어보고 있다.

무섭지만..! 하지 않으면 안되므로.. ㅜ ㅜ


인터넷에 찾아보면 x64에 대한 많은 정보들이 있는 것 같지만 공부를 위해 스스로 시스템 콜 추적을 해봐야겠다.

===================================================================

먼저 타겟으로 잡은 OS는 Windows 7 x64 (AMD64) 이고 당연히 드라이버 싸이닝은 disable 시켜놓았다.

( 이번 편에서는 User Area 만 볼 것이므로 드라이버 싸이닝과 관련은 없다. )

추적대상 함수는 CreateFile 함수로 해보겠다.


Windows x64 같은 경우 x86 호환성을 제공하기 때문에 32bit 응용 프로그램들을 수정 없이 바로 사용할 수 있다. 



위 그림을 보면 64bit 프로그램들은 프로세스 명 옆에 아무런 표시가 없고 32bit 프로그램들은 옆에 *32 가 붙어있는 것을 알 수 있다. 이처럼 32bit 프로그램이 바로 실행 가능한 이유는 Windows가 32bit 에뮬레이션 레이어(WOW64)를 운영하고 있기 때문이다. 



Windows 폴더에 들어가면 32bit OS의 경우에는 System32 폴더만 있지만 64bit OS에선 SysWOW64라는 폴더가 하나 더 생긴 것을 알 수 있다. 처음에 보면 System32에 32bit 프로그램들이 들어가 있고 SysWOW64에 64bit 프로그램들이 들어가 있는 것으로 착각할 수 있는데 그 반대이다. 32bit 프로그램이 전부 SysWOW64 에 들어있다.


하지만 커널에서 작동하는 드라이버는 다르다. 커널 자체는 전부 64bit이고 에뮬레이션 레이어도 없기 때문에 드라이버는 무조건 64bit로 만들어져야한다. 즉.. 64bit OS에선 두 가지 형태의 조합이 나올 수 있다.


32bit user program + 64bit kernel driver

64bit user program + 64bit kernel driver


여기서 시스템 콜 추적할 때에는 위 두가지 경우를 다 살펴보도록 하겠다.

다음 편에서 커널 추적을 할 것이므로 여기선 USER만 살펴보도록 하겠다.


==================================

32bit USER

==================================


테스트를 위해 사용된 코드는 위와 같다. ( x86 & x64 동일 )


디버거는 WinDbg를 사용하였다. pdb가 없다면 EntryPoint에 BP를 걸고 시작해야되지만 여기선 pdb가 있기 때문에 그러한 과정은 생략한다. 또한 x86과 다르게 x64는 PE32+ 라는 구조를 가지며 기존의 IMAGE_OPTIONAL_HEADER 가 아닌 IMAGE_OPTIONAL_HEADER64 구조체를 가지고 자료형들이 조금씩(4Byte->8Byte) 바뀌었다. 이 점을 고려하여 ImageBase 있는 위치를 찾으면된다.


아무튼 CreateFile에 BP를 걸고 멈추고 쭉쭉 들어가 보면 아래와 같은 CALL을 한다.


CreateFile -> CreateFileW -> NtCreateFile


여기까진 32bit OS에서 실행하는 것과 같지만 NtCreateFile 내부는 32bit OS와는 다르다.

NtCreateFile 내부는 다음과 같이 생겼다.


ntdll32!NtCreateFile:

77300094 b852000000      mov     eax,52h

77300099 33c9            xor     ecx,ecx

7730009b 8d542404        lea     edx,[esp+4]

7730009f 64ff15c0000000  call    dword ptr fs:[0C0h]

773000a6 83c404          add     esp,4

773000a9 c22c00          ret     2Ch


32bit OS에서 32bit 프로그램은 KiFastSystemCall 함수를 호출하지만 

64bit OS에서 32bit 프로그램에선  fs:[0C0h]  를 호출하고 있다.


일단 위 코드에서 fs:[0C0h] 는 WOW32Reserved 를 나타내고 있다.


ntdll!_TEB

   +0x000 NtTib            : _NT_TIB

   +0x01c EnvironmentPointer : Ptr32 Void

   +0x020 ClientId         : _CLIENT_ID

   +0x028 ActiveRpcHandle  : Ptr32 Void

   +0x02c ThreadLocalStoragePointer : Ptr32 Void

   +0x030 ProcessEnvironmentBlock : Ptr32 _PEB

   +0x034 LastErrorValue   : Uint4B

   +0x038 CountOfOwnedCriticalSections : Uint4B

   +0x03c CsrClientThread  : Ptr32 Void

   +0x040 Win32ThreadInfo  : Ptr32 Void

   +0x044 User32Reserved   : [26] Uint4B

   +0x0ac UserReserved     : [5] Uint4B

   +0x0c0 WOW32Reserved    : Ptr32 Void

   +0x0c4 CurrentLocale    : Uint4B

   +0x0c8 FpSoftwareStatusRegister : Uint4B


0:000> dd fs:[0C0h]

0053:000000c0  748f2320 00000412 00000000 00000000

0053:000000d0  00000000 00000000 00000000 00000000

0053:000000e0  00000000 00000000 00000000 00000000

0053:000000f0  00000000 00000000 00000000 00000000

0053:00000100  00000000 00000000 00000000 00000000

0053:00000110  00000000 00000000 00000000 00000000

0053:00000120  00000000 00000000 00000000 00000000

0053:00000130  00000000 00000000 00000000 00000000


0:000> !address 748f2320


Usage:                  Image

Base Address:           748f1000

End Address:            748f4000

Region Size:            00003000

State:                  00001000 MEM_COMMIT

Protect:                00000020 PAGE_EXECUTE_READ

Type:                   01000000 MEM_IMAGE

Allocation Base:        748f0000

Allocation Protect:     00000080 PAGE_EXECUTE_WRITECOPY

Image Path:             C:\Windows\SYSTEM32\wow64cpu.dll

Module Name:            wow64cpu

Loaded Image Name:      

Mapped Image Name:      

More info:              lmv m wow64cpu

More info:              !lmi wow64cpu

More info:              ln 0x748f2320

More info:              !dh 0x748f0000


0:000:x86> lm t n

start             end                 module name

01310000 01322000   Console_Study2 Console_Study2.exe Wed Nov 07 11:07:35 2012 (5099C267)

748f0000 748f8000   wow64cpu wow64cpu.dll Tue Aug 21 03:48:10 2012 (5032866A)

74900000 7495c000   wow64win wow64win.dll Tue Aug 21 03:48:12 2012 (5032866C)

74960000 7499f000   wow64    wow64.dll    Tue Aug 21 03:48:09 2012 (50328669)

75010000 75120000   kernel32 kernel32.dll Tue Aug 21 02:40:01 2012 (50327671)

76770000 767b7000   KERNELBASE KERNELBASE.dll Tue Aug 21 02:40:02 2012 (50327672)

77100000 772a9000   ntdll    ntdll.dll    Thu Nov 17 15:32:46 2011 (4EC4AA8E)

772e0000 77460000   ntdll32  ntdll32.dll  Thu Nov 17 14:28:47 2011 (4EC49B8F)


일단 fs:[0C0h] 를 따라가면 0x748f1000 을 베이스로 올라온 wow64cpu.dll 이미지 내부의 주소인 것은 확인했고 주소를 따라가보자.

wow64cpu!X86SwitchTo64BitMode:
748f2320 ea1e278f743300  jmp     0033:748F271E
748f2327 0000            add     byte ptr [eax],al

해당 Call 내부를 들어오니 해당 위치는 wow64cpu.dll 내부이고 X86SwitchTo64BitMode 함수를 호출한다는 것을 알 수 있다.
wow64cpu.dll은 64bit 모듈이다. 또한 점프되는 값을 보면 64bit 주소 체계인 것을 알 수 있다. 점프를 따라가보자.

wow64cpu!CpupReturnFromSimulatedCode:
00000000`748f271e 67448b0424      mov     r8d,dword ptr [esp] ds:00000000`0044f720=a6003077
00000000`748f2723 458985bc000000  mov     dword ptr [r13+0BCh],r8d
00000000`748f272a 4189a5c8000000  mov     dword ptr [r13+0C8h],esp
00000000`748f2731 498ba42480140000 mov     rsp,qword ptr [r12+1480h]
00000000`748f2739 4983a4248014000000 and   qword ptr [r12+1480h],0
00000000`748f2742 448bda          mov     r11d,edx
wow64cpu!TurboDispatchJumpAddressStart:
00000000`748f2745 41ff24cf        jmp     qword ptr [r15+rcx*8]

점프를 따라가보면 CpupReturnFromSimulatedCode 함수로 이동했다.

아래 있는 점프문도 따라가면

wow64cpu!ServiceNoTurbo:
00000000`748f2749 4189b5a4000000  mov     dword ptr [r13+0A4h],esi ds:00000000`0019fdc4=6a000000
00000000`748f2750 4189bda0000000  mov     dword ptr [r13+0A0h],edi
00000000`748f2757 41899da8000000  mov     dword ptr [r13+0A8h],ebx
00000000`748f275e 4189adb8000000  mov     dword ptr [r13+0B8h],ebp
00000000`748f2765 9c              pushfq
00000000`748f2766 5b              pop     rbx
00000000`748f2767 41899dc4000000  mov     dword ptr [r13+0C4h],ebx
00000000`748f276e 8bc8            mov     ecx,eax
00000000`748f2770 ff150ae9ffff    call    qword ptr [wow64cpu!_imp_Wow64SystemServiceEx (00000000`748f1080)]
00000000`748f2776 418985b4000000  mov     dword ptr [r13+0B4h],eax
00000000`748f277d e98ffeffff      jmp     wow64cpu!CpuSimulate+0x61 (00000000`748f2611)

ServiceNoTurbo 함수로 오게되고 내부적으로 Wow64SystemServiceEx 함수를 호출하고 있는 것을 알 수 있다.
이름만 딱 봐도 실제 시스템 콜을 호출할 것 같은 함수이다.

내부적으로 더 들어가보면 2개의 함수를 더 거쳐서 시스템 콜을 호출한다는 것을 알수 있다.

[출처] PE32와 PE32+의 차이점|작성자 좐심

[출처] PE32와 +의 차이PE32|작성자 좐심


call    r12 {wow64!whNtCreateFile (00000000`7497bee8)}

                                      ↓

call    qword ptr [wow64!_imp_NtCreateFile (00000000`74961a48)]


Wow64SystemServiceEx 내부적으로 wow64.dll 에 있는 whNtCreateFile 함수를 호출하고 또 들어가 wow64.dll에 있는 NtCreateFile 함수를 호출하게 된다. 위는 32bit 모듈인 ntdll32.dll에 있는 NtCreateFile 이었고 이번 NtCreateFile은 64bit 모듈인 wow64.dll 에서 import 하고 있는 ntdll!NtCreateFile 이다. 모든 여정은 끝이났다. ntdll!NtCreateFile 내부를 보자.


ntdll!NtCreateFile:

00000000`77151860 4c8bd1          mov     r10,rcx

00000000`77151863 b852000000      mov     eax,52h

00000000`77151868 0f05            syscall

00000000`7715186a c3              ret

00000000`7715186b 0f1f440000      nop     dword ptr [rax+rax]


syscall 이 발견되었다. 32bit OS에선 sysenter 였는데 왜 여긴 syscall 일까.. 이 부분은 아래서 설명하도록 하고 

지금까지 CreateFile을 따라왔던 경로를 쭉 나열해보자.


(32bit)

kernel32 ! CreateFile -> 

KERNELBASE ! CreateFileW -> 

ntdll32 ! NtCreateFile ->


(64bit)

wow64cpu ! X86SwitchTo64BitMode ->

wow64cpu ! CpupReturnFromSimulatedCode ->

wow64cpu ! ServiceNoTurbo ->

wow64 ! Wow64SystemServiceEx ->

wow64 ! whNtCreateFile ->

ntdll ! NtCreateFile ( 최종도착 )


64bit 시스템에서 32bit 응용프로그램은 시스템콜 한번 하기가 참 힘든 것 같다.

또한 IA32 구조에선 모든 인자가 스택에 들어가 있지만(fastcall 제외) AMD64에선 


RCX, RDX, R8, R9, 스택 순으로 들어가기 때문에 그에 맞춰서 인자도 위치도 전부 변경되었다.


그리고 위에서 syscall 에 대해 설명한다고 했는데 syscall에 대한 설명을 Intel 문서에서 찾아보았다.



SYSCALL은 기존의 RIP를 RCX에 저장하고 새로운 RIP를 로드하는데 그 위치는 IA32_LSTAR 라고 나와있다.

IA32_LSTAR 값은 Intel 문서에서 MSR 정보를 찾아보면 알 수 있다.



0xc0000082 값으로 나와있다.


kd> rdmsr C0000082H

msr[c0000082] = fffff800`02c7bec0

kd> ln fffff800`02c7bec0

(fffff800`02c7bec0)   nt!KiSystemCall64   |  (fffff800`02c7bfde)   nt!KiSystemServiceStart

Exact matches:

    nt!KiSystemCall64 (<no parameter info>)


그 위치는 nt 모듈에 있는 KiSystemCall64 라는 것을 알 수 있다.

이로서 커널 추적을 시작하면 된다. 


이번엔 64bit 프로그램일 때 CreateFile 을 추적해 보자.


==================================

64bit USER

==================================

64bit 응용 프로그램은 번거로운 과정없이 kernel32.dll -> KERNELBASE.dll -> ntdll.dll 로 들어오면

ntdll!NtCreateFile 함수가 호출되는데 내부를 보면 끝이다.


ntdll!NtCreateFile:

00000000`77151860 4c8bd1          mov     r10,rcx

00000000`77151863 b852000000      mov     eax,52h

00000000`77151868 0f05            syscall

00000000`7715186a c3              ret

00000000`7715186b 0f1f440000      nop     dword ptr [rax+rax]


바로 syscall이 보이며 앞 주소를 보면 32bit 응용 프로그램의 목적지와 같은 것을 알 수 있다.


이로서 이번 포스팅은 마치지만 뭔가 syscall 이 호출되는 과정으로 들어오는 시기에 이상한 부분이 있다.

ntdll32.dll은 존재하지 않은 모듈이다. ntdll.dll이 ntdll + ntdll32 인걸까?....


뭔가 혼동이온다. ㅡ ㅡ;


PS. x86 시스템에서 32bit 응용 프로그램 내부의 API 함수들의 가장 밑단을 후킹하려면 예전에는 KiFastSystemCall 함수를 인라인후킹했어야하는데 x64 시스템에서 32bit 응용 프로그램 내부의 API 함수의 가장 밑단을 후킹하려면 GS:[C0h] 에 있는 WOW32Reserved 의 값을 바꿔버리면 될 것같다.