본문 바로가기

My Study/Programming&Theory

Windows x64 시스템 콜 Kernel Area

저번 포스팅 때 Windows x64 시스템콜의 User Area 부분을 봐보았는데 이번엔 Kernel 부분을 봐보도록 하겠습니다.

잠깐 복습을 하자면 32bit 프로그램이나 64bit 프로그램이나 전부 64bit ntdll.dll의 syscall 을 호출하게되고 syscall이 호출되면 

MSR 0xC0000082 에 있는 IA32_LSTAR 값으로 RIP가 바뀐다고 했습니다.

또한 IA32_LSTAR 값은 nt 이미지의 KiSystemCall64 함수를 가리키고 있습니다.


그러면 이번엔 KiSystemCall64 함수부터 보도록 하겠습니다.


첫 부분 명령어


fffff800`02c70ec0 0f01f8          swapgs


동적 디버깅을 하면서 분석을 하기 위해선 KiSystemCall64 함수 첫부분인 위 swapgs 명령어에 BP를 걸고 실행해야되는데 WinDbg에서 BP를 걸고 F5를 누르면 Windows가 멈춰버리더군요. 이게 패치가드 때문에 그런가? 생각을 했지만 패치가드는 WinDbg 붙어있으면 Disable 되는 걸로 알고 있는데 뭔가 이상하더군요. 아무튼 이 방법은 안되므로 SYSCALL Hooking을 이용해 nop 하나 주고 KiSystemCall64 로 jmp하는 루틴을 넣고 nop에 BP를 걸어도..! 멈추더군요. 하드웨어 BP도 멈춤..;;


그래서 swapgs 명령어가 아닌 그 다음 명령어에 BP를 걸어보았는데 Windows가 멈추지 않고 BP가 잘 먹히더군요.;;;; 이건 왜이럴까요?? WinDbg의 버그일까요?? 아니면 x64 OS의 보호매커니즘 때문일까요?

일단 BP는 잘 먹혔으니 분석을 해보도록 합시당~


;GS 바꿈( MSR C0000101H )

swapgs


;유저 스택 포인터 저장 후 커널 스택 포인터로 변경

mov   qword ptr gs:[10h],rsp ; KPCR.UserRsp

mov   rsp,qword ptr gs:[1A8h] ; KPCR.KPRCB.RspBase


; 현재 쓰레드 구조체 가져옴

mov   rbx,qword ptr gs:[188h] ; KPCR.KPRCB.CurrentThread


; 트렙프래임 꺼냄

prefetchw [rbx+1D8h] ; KTHREAD.TrapFrame


; mxcsr 상태 저장 후 Prcb 값으로 mxcsr 셋팅

stmxcsr dword ptr [rbp-54h]

ldmxcsr dword ptr gs:[180h] ; KPCR.Prcb


; 첫번째 인자와 시스템 콜 넘버를 현재 스레드 구조체에 저장

mov     qword ptr [rbx+1E0h],rcx ; KTHREAD.FirstArgument

mov     dword ptr [rbx+1F8h],eax ; KTHREAD.SystemCallNumber


nt!KiSystemServiceStart:

; 현재 스택 상태를 스레드의 트랩 프래임 상태로 저장

mov     qword ptr [rbx+1D8h],rsp ; KTHREAD.TrapFrame


; 시스템 콜 넘버에서 GUI 시스템 콜인지 아닌지 확인

mov     edi,eax

shr     edi,7

and     edi,20h

and     eax,0FFFh


nt!KiSystemServiceRepeat:

; r10, r11에 KeServiceDescriptorTable, KeServiceDescriptorTableShadow 주소 값 넣음

lea     r10,[nt!KeServiceDescriptorTable (fffff800`02eb1840)]

lea     r11,[nt!KeServiceDescriptorTableShadow (fffff800`02eb1880)]


; GUI 시스템 콜인지 확인 후 GUI 시스템 콜이면 r10도 KeServiceDescriptorTableShadow 값으로 셋팅

test dword ptr [rbx+100h],80h ; KTHREAD.GuiThread

cmovne  r10,r11


; 시스템 콜 넘버가 현재 사용될 서비스 테이블 개수보다 큰지 확인

cmp     eax,dword ptr [rdi+r10+10h] ; r10.NumberOfService

jae     nt!KiSystemServiceExit+0x1a7 (fffff800`02c7a302)


; KiServiceTable 주소 가져옴 ( GUI인 경우엔 W32pServiceTable )

mov     r10,qword ptr [rdi+r10] ; r10.ServiceTableBase


; KiServiceTable 에서 rax를 offset 으로 접근해 특정 값 가져옴(4Byte) => E ( 인코딩 된 값 )

movsxd  r11,dword ptr [r10+rax*4]


; E 값을 rax에 저장하고, r10 = KiServiceTable + E >> 4 수행, r10이 실제 서비스 루틴

mov     rax,r11

sar     r11,4

add     r10,r11


; 서비스 루틴 호출

call    r10


중간중간 생략하면서 진행하였습니다.


결론적으로 시스템 콜 호출 루틴은

실제 서비스 루틴 = KiServiceTable + (KiServiceTable[rax] >> 4);

실제 서비스 루틴 = W32pServiceTable+ (W32pServiceTable[rax] >> 4);


끝.. 중요한건 KiServiceTable에서 offset으로 접근하면 바로 함수 주소가 있는게 아닌 한번 인코딩 되어있다는 점이네요.

그 외에는 32bit 시스템하고 크게 달라진건 없는 것 같습니다.


그리고 KiSystemCall64 함수를 빠져나갈땐 sysret 를 호출하게 되는데 그 전에도 gs를 바꾸기 위해 swapgs를 수행합니다.

하지만 저기에 BP 를 걸면 또 멈춥니다.


찾아보니 어떤분도 그냥 WinDbg가 멍청해서 그렇다고 하네요. 뭐 정확한 이유는 모르겠습니다.

'My Study > Programming&Theory' 카테고리의 다른 글

NTFS 메타데이터파일 정보  (0) 2012.11.22
NTFS : MFT 요소 값 구하기 2가지 방법  (15) 2012.11.20
Windows x64 시스템 콜 User Area  (4) 2012.11.07
Context 는 매우 중요하다!!  (0) 2012.11.06
x86 자가보호 DLL  (1) 2012.11.06