본문 바로가기

My Study/Programming&Theory

디바이스로 파일 생성, 쓰기, 제거 ( Zw와 Nt 함수 차이점 )

 항상 Windows 유저레벨에서 파일을 생성, 쓰기, 제거.. 할 때 CreateFile, WriteFile, DeleteFile..을 사용했습니다.
이번엔 커널 함수 사용법을 쫌 익혀보기 위해 커널레벨에서 파일 생성, 쓰기, 제거를 해보았습니다.

일반적으로 유저레벨에서 파일을 열 때 CreateFile을 사용했다면 커널에선 그대로 사용할 수 없습니다.
Zw 혹은 Nt 함수를 써야합니다. 아마 유저레벨에서 CreateFile을 사용한 후 kernel32.dll로 들어와보면
내부적으로 ZwCreateFile함수를 호출하고 있는 것을 볼 수 있을 것입니다. 이건 유저레벨에서 확인가능한 내용입니다.
ZwCreateFile은 ntdll.dll에 존재하지요.

커널에서는 이런 함수를 사용하기 위해선 저렇게 Zw를 사용해야 한다는 것입니다.
여기서 의문점이 드는 부분이 계실탠데 Zw는 언제쓰고 Nt는 언제 쓰느냐 입니다. 
일단 제가 만든 코드를 보신 후 계속 이야기를 해보겠습니다.
일단 드라이버가 로드되면 ZwCreateFile로 파일 생성과 동시에 핸들을 얻어오고
그 핸들 값을 가지고 해당 파일에 값을 넣습니다. "Ezbeat Fighing!!"
그리고 핸들을 닫아주지요.
 이 때 ZwCreateFile로 파일을 생성할 때는 파일 이름을 "C:\\test.txt" 이런식으로 해주면 안되고
"\\??\\C:\\test.txt" 로 지정을 해야합니다.
보통 응용프로그램은 오브젝트 관리자가 전달한 파일 볼륨을 분석하여 Symbolic Link를 생성하지만 
드라이버 내부에서는 이러한 과정을 거치지 않습니다. 
그렇기 때문에 저장하고사 하는 파일의 Symbolic Link를 같이 추가해야합니다.

파일의 속성은 따로 InitializeObjectAttributes() 함수를 사용해야합니다.

이제 언로드를 시킬 때 해당 파일을 제거하기 위해 ZwOpenFile을 사용해서 핸들을 얻어옵니다.
그리고 ZwSetInformationFile을 수행하면 파일이 제거됩니다. 당연히 제거에 알맞는 인자들을 전달해줘야합니다.

사실 삭제하는 루틴이 ZwDeleteFile인 줄 알았었습니다.
하지만 유저레벨에서 DeleteFile 함수를 사용해서 쭉 트레이스 해본결과
kernel32.dll에서는 

DeleteFile -> ZwOpenFile -> ZwSetInformationFile

이렇게 호출이 되더군요. ( 파일 삭제에 필수적인 함수만 나열 )
그리고 파일을 생성하느냐 삭제하느냐에 따라 인자들이 달라질 뿐이었습니다.

저번 포스팅 내용에서 SSDT후킹을 하였는데 그 때 사용한 방법이 ZwOpenFile에서 세번째 인자 구조체 변수에
삭제할 파일 경로가 들어가 있는 것을 보고 같으면 ZwOpenFile 실패를 일으키면서 핸들 값 얻기를 실패하게 해버리는 것입니다. http://ezbeat.tistory.com/276

그리고 각 함수들에 별의별 정의된 값이 들어가는데 그건 직접 올디로 인자를 보면서 적은 것입니다.

코드는 크게 어려운게 없으니 천천히 보시면 이해하실 수 있을 것입니다.

코드에 대한 설명은 끝났고 이제 Zw와 Nt에 관한 것을 조금 말해보겠습니다.
궁금한 점은 Zw는 언제쓰고 Nt는 쓰느냐입니다.
Zw함수를 써도 어짜피 내부적으론 Nt함수를 가져오므로 그냥 커널에서는 Zw를 쓰지말고 바로 Nt를 쓰면 안되냐?
라는 질문도 있을 수 있습니다.

결론은 써도 됩니다. 하지만 아주 위험하다는 것입니다.
일단 Zw함수를 썼을 때는 바로 해당 함수 처리 루틴으로 가는게 아니라 KeSystemService를 거친 후 
KiFastCallEntry로 오게됩니다. KiFastCallEntry에서는 KiServiceDescriptorTable에서 Nt함수를 가져오겠지요.
Zw 함수를 썼을 때 내부로 들어가보겠습니다.

이와 같이 작동을 하게됩니다.

그러면 KeSystemService에서는 뭘하느냐..
해당 함수는 Zw함수 파라미터로 전달된 인자들의 유효성을 검사하는 일을 합니다.
또 모드가 정확히 커널 레벨이라는 것으로 바뀌게 됩니다.

하지만 Zw를 쓰지 않고 바로 Nt를 써버리면 위와 같은 과정은 전부 생략이 되게 됩니다.
파라미터 유효성 검사도 하지 않고 현재 모드가 커널레벨이라는 것도 모르므로 에러를 뱉을 수도 있습니다.

즉, 결론을 말하면 Nt함수를 쓰지말고 Zw함수를 사용하는게 안전하고 정상적으로 작동한다는 것입니다.
( Nt를 쓴다고 무조건 뻑나는건 아닙니다.. )

유저모드에서 Zw호출한 것과 커널모드에서 Zw호출한 것의 다른점은 유저모드에선 커널레벨로 내려와야하므로
sysenter명령어를 사용해야 한다는 것입니다.

커널로 내려와서 작동하는 것은 똑같습니다. ( 함수는 조금 다르지만 코드는 일치 )
그림으로 보시겠습니다.

그림 설명을 조금만 해보겠습니다.
일단 노란 부분은 코드가 같습니다.

유저레벨에선 ntdll.dll까지 내려와 KiFastSystemCall에서는 sysenter를 합니다. 그렇게 커널로 내려오면
KiFastCallEntry로 오게됩니다. 아에 +0x0 부분부터 시작하게 되지요.

하지만 커널레벨에서는 KeSystemService함수로 와서 노란부분만 실행 후 KiFastSystemCall함수로 뛰게됩니다.
정확히 이런 명령어가 있습니다.
jmp     nt!KiFastCallEntry+0x8d 
뭐 KiFastCallEntry 아래코드는 Nt함수를 가져오는 루틴이겠지요. (SSDT후킹 때 전부 설명 했음)

결론은 Zw는 이렇게 작동한다는 것입니다.
그리고 Zw를 사용함으로써 좋은 점은
파라미터 유효성검사, 모드를 커널레벨로 정확히 변경

Nt는 그런거 없음.


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

SYSENTER Hooking(Multi Processor)  (2) 2010.10.26
SYSENTER Hooking  (3) 2010.10.24
SSDT Hooking  (7) 2010.10.20
Driver Load, Unload  (0) 2010.10.17
Intel CPU의 영리함??  (5) 2010.10.09