본문 바로가기

My Study/Programming&Theory

SYSENTER Hooking(Multi Processor)

SYSENTER Hooking에 대해서는 저번에 올렸던 글입니다.
하지만 저번에 올렸던 코드대로 하다보면 싱글 프로세서 기반에서는 잘 작동할지 몰라도
멀티 프로세서 기반에서는 정상적으로 작동을 하지 못합니다.

요새 노트북이건 데탑이건 하물며 핸드폰마저 듀얼코어까지 나오려는 세상에 
싱글코어 기반으로만 프로그래밍을 한다면 별 의미가 없는 것 같습니다.

그래서 이번에는 SYSENTER를 똑같이 후킹하되 멀티 프로세서 기반에서도 잘 작동하도록 해보겠습니다.
먼저 듀얼코어 환경에 싱글코어 기반으로 짠 sys파일을 커널에 로드시켜본 것그림으로 나타내보겠습니다.

간단히 그림을 설명해보겠습니다.
듀얼코어 환경에서는 위와 같이 cpu가 2개입니다.
( 실제로 2개가 아니라 OS,디바이스 드라이버,어플리케이션들이 cpu를 2개로 여기게 됨 )

그리고 각 cpu마다 msr레지스터, dpc루틴, idt등 전부 각각 있습니다.
그렇기 때문에 제가 커널에 로드 시켰던 sys가 cpu1에서 처리가 됬다면 cpu1에 있는 msr 0x176레지스터 값만
위 그림처럼 바뀌었을 것입니다.
아직 cpu2의 msr 0x176레지스터 값은 KiFastCallEntry를 가리키고 있지요.

이때 파일 삭제를 위해 ZwOpenFile이 호출이 됬는데 이 루틴이 cpu2에서 처리가 되었다면 해당 루틴은
MyKiFastCallEntry루틴으로 가지않고 바로 KiFastCallEntry로 가게 됩니다.
이렇게 되면 파일 삭제를 못하게 하는 MyRoutine을 안거치므로 그냥 정상적으로 파일 삭제가 이루어지게 됩니다.

그러면 항상 파일이 삭제가 되냐?? 아닙니다.
각 루틴들은 DISPATCH_LEVEL에서 작동하고 있는 스케쥴러에 의해 작동하는 cpu가 계속 바뀔 것입니다.
운 좋게도 ZwOpenFile을 처리하는 루틴이 cpu1에서 작동하고 있었다면 후킹이 성공될 것이며 파일 삭제 또한 안될 것입니다.

IRQL, 우선순위 이런거 다 빼고 본다면 이 후킹이 성공할 확율은 50%입니다. 우리는 100%를 원하는데 말이죠.
100%로 하기 위한 내용은 아래서 보기로 하고 이번엔 이렇게 로드된 드라이버를 언로드 해보겠는데
이 때, 블루스크린이 발생할 확율이 50%입니다. 그림으로 블루스크린이 발생하는 경우를 보겠습니다.

보시면 이번에는 드라이버 언로드 루틴이 cpu2에서 처리가 됬습니다.
그리고 해당 드라이버 언로드 되었기 때문에 MyKiFastCallEntry주소에는 아무 의미없는 값들이 있을 것입니다.

보통은 언로드 루틴이 cpu1에서 작동을해서 msr 0x176레지스터 값을 다시 KiFastCallEntry를 가리키게끔 만들어 주어야하는데 반대로 cpu2에서 작동을 해버려서 cpu1의 msr 0x176레지스터 값은 여전히 MyKiFastCallEntry를 가리키고 있습니다.

이때 cpu1에서 sysenter명령어가 실행이되어 msr 0x176레지스터에 있는 주소로 딱 간 순간! 블루스크린이 발생하게 됩니다.
(직접 겪었습니다. 크래쉬 파일 분석해보니 바로 그림이 그려지더군요..ㅠㅠ)

이렇게 멀티 프로세서 기반에서는 항상 모든 cpu를 생각해야 하며 각 cpu간 동기화 처리 또한 잘해주어야 합니다.
아직 저도 심도있는 디바이스를 짜보지 않아서 걱정이 앞서군요 ^^;

이제 위 SYSENTER 후킹을 멀티 프로세서 기반에서 잘 작동하도록 하는 코드를 짜보겠습니다.
일단 해결 방법은 간단합니다. 후킹할 땐 모든 cpu의 msr 0x176레지스터를 MyKiFastCallEntry를 가리키도록 하고
언로드 하면서 언훅 할 때는 다시 모든 cpu의 msr 0x176레지스터를 KiFastCallEntry를 가리키도록 바꿔주면 됩니다.

이번에는 DirverEntry함수를 보기전에 MyKiFastCallEntry함수부터 보겠습니다.

드라이버가 로드되면 두 cpu가 동시에 작동하면서 동시에 MyKiFastCallEntry함수를 접근할 수도 있는데
동시에 접근해도 각각의 스택이 존재하고 각각의 레지스터 존재하기 때문에 위 경우는 동기화를 시켜줄 필요가 없습니다.
동기화를 시켜주어야 하는 경우는 하나밖에 존재하지 곳에 동시접근이 발생할 때 해주어야 합니다.

위 경우를 만약 스핀락 같은 것으로 동기화를 시켜버린다면... 
sysenter가 호출될때마다 MyKiFastCallEntry로 들어올탠데 엄청난 속도 저하가 생길 것입니다.

이제 DriverEntry함수를 보도록 하겠습니다.
뭔가 코드가 더 생긴 것을 볼 수 있습니다.

위에서 모든 cpu에 존재하는 msr 0x176레지스터를 전부 수정시켜 주어야 한다고 했습니다.
그러므로 change_msr 루틴이 모든 cpu에서 모두 동작을 해야합니다. 즉, 듀얼코어 환경이라면 두번 실행이 되어야겠지요.
바로 그 코드입니다.

각 cpu에는 DPC루틴이라는 것이 있습니다. DPC큐에 루틴을 넣어두면 DPC가 처리하게 되는 것이지요.
DPC는 IRQL:DISPATCH_LEVEL에서 작동을 합니다.

각 cpu를 돌면서 change_msr실행 방법
KeSetTargetProcessorDpc함수로 DPC루틴이 실행되어질 cpu를 선택 후 
KeInsertQueueDpc로 선택되어진 cpu의 DPC큐에 
KeInitializeDpc함수에서 두번째 인자로 전달 된 함수를 넣게됩니다.

이 과정을 cpu개수 만큼 돌면 됩니다. 현재 환경의 프로세서 개수는 KeNumberProcessors로 구할 수 있습니다.

그리고 KeDelayExecutionThread로 1초의 텀을 주었는데 위 DPC큐에 넣는 과정을 텀 없이 바로 실행해버리면 
가끔가다 한번밖에 돌지 못하고 나와버리는 상황이 발생했습니다. 즉, 하나의 cpu만 훅이 걸리는 것이지요.
왜 그런지 이유는 잘 모르겠지만 일단 함수를 정상적으로 실행할 시간을 약간 줬더니 그런 문제는 쉽게 해결 됬습니다.

그리고 KeInitializeDpc함수의 두번째 인자로 함수 주소가 전달되는데 해당 함수는 PKDEFERRED_ROUTINE형태를 맞춰주어야합니다. msdn에 보면 다 나와있는 내용이긴 합니다. change_msr, restore_msr 함수를 봐보겠습니다.
그냥 이렇게 해주시면 됩니다.

이렇게 말고는 전에 올렸던 싱글코어 기반 드라이버와 큰 차이는 없습니다.
그리고 Unload루틴에서도 마찬가지로 restore_msr루틴을 각 프로세서 전부 작동하도록 해주어야합니다.

커널에 로드 후 실행시키면 똑같이 C:\test.txt 파일 삭제가 안되는 것을 확인할 수 있습니다.