저번 포스팅 때 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 |