IDT 후킹을 해 PS/2 키보드 키로거를 만드는 도중... 한가지 문제 때문에 엄청난 시간을 허비하게 됬습니다.
MP 환경에서는 전혀 문제가 되지 않았지만 SMP환경에서 테스트를 하던 도중 키보드를 빨리 치면 BSOD화면이 뜨는 것입니다. 일단 오류가 났던 코드를 가지고 이야기 해보겠습니다.
일단 IDT Hooking하는 부분을 보시겠습니다. ( 불안정 코드 )
IDT_HOOK의 함수 형태는 CustomDpc루틴입니다.
SMP환경에서는 모든 CPU에 있는 IDT를 후킹해야 하므로
CustomDpc루틴을 사용해 각 CPU의 DPC큐에 DPC루틴을 넣어주었습니다.
후킹 내부를 보시면 먼저 sidt명령어를 사용해 IDTR에 있는 값을 가져옵니다.
IDTR 내부에는 IDT 처음 주소가 들어있습니다.
그리고 IDT에서 Interrupt 벡터 값에 대응하는 ISR 주소를 가져옵니다.
위 실험은 XP에서 테스트 됬기 때문에 키보드를 눌렀을 때 발생하는 Interrupt 벡터 값은 0x93이었습니다.
그리고 해당 주소에는 MyInterruptFunc라는 제가 만든 함수의 주소를 덮어씌워버립니다.
그림으로 어떻게 표현이 되는지 봐보겠습니다.
(위 그림은 몇 가지 부분을 생략 했으며 정확한 정보는 http://coffeenix.net/doc/develop/ia-interrupt/ch1-1.html 서 보세요.)
원래는 0x93에 들어있는 주소가 Old_ISR_Address입니다. 하지만 후킹을 해놓음으로써 MyInterruptFunc에 먼저 가게되고 그 후에 Old_ISR_Address로 점프하게 되는 것입니다.
이제 MyInterruptFunc에서는 CustomDPC루틴이 됬든 DpcForIsr루틴이 됬든 아무튼 후킹을 해서 원하는 작업을 할 코드를 DPC큐에 넣어주어야 합니다. 이렇게 구현하지 않으면 하드웨어 인터럽트 처리가 늦어지게 되며 컴퓨터가 버벅대는 현상을 경험하실 수 있습니다.
이렇게 구현하는게 틀린걸까요?? 후킹을해서 어떠한 작업을 하는지에 따라 달라지겠지만 내부에서 DPC루틴을 사용하는 경우에는 잘못된 방법입니다. 저는 처음에 위와 같이 구현을 했다가 엄청난 삽질의 결과를 맛보았습니다. 계속계속 BSOD화면이 뜨는 것입니다. 덤프 파일을 분석해도 IRQL이 잘못됬다고 나오더군요. MyInterruptFunc에서 IRQL값을 찍어본 결과 DISPATCH_LEVEL인 2 값이 나왔습니다. 보통 하드웨어 인터럽트의 IRQL값은 3~26인 DIRQL로 알고 있는데 뭔가 이상하군요. 그래서 후킹하는 방식을 위와 같이 하지 않고 더욱 깊숙한 곳을 후킹해보기로 했습니다.
일단 Old_ISR_Address루틴을 분석해보도록 하겠습니다.
먼저 WinDbg에서 IDT를 봐보겠습니다.
인터럽트 벡터 0x93 을 보니 그에 대응하는 ISR 주소는 0x82b06dd4 이고, 이 인터럽트를 관리하는 인터럽트 오브젝트(KINTERRUPT 구조체)의 주소는 0x82b06d98 입니다.
먼저 ISR 주소로 가보겠습니다.
모든 인터럽트 벡터의 ISR을 가보면 전부 위와 같이 생겼습니다.
빨간색 네모친 부분에서 edi에 0x82B06D98을 넣고 있는데 이 값이 Interrupt Object주소입니다.
해당 구조체에는 어떠한 내용이 들어있는지 봐보겠습니다.
먼저 4번째 구조체 멤버인 ServiceRoutine이 최종적으로 이 인터럽트가 발생했을 때 호출되는 루틴입니다.
KiInterruptDispatch 내부로 들어가보겠습니다.
세 군대에 빨간색 네모를 쳤습니다.
첫번째를 보시면 HalBeginSystemInterrupt를 호출하고 있습니다.
해당 함수는 하드웨어 인터럽트가 발생했을 때 커널이 호출하는 함수로서 첫 번째 파라미터에 사용하는 IRQL 환경으로 현재의 CPU 환경을 바꾸도록 요구하는 함수입니다. 함수를 호출하면 그 즉시 현재의 IRQL은 변경됩니다.
테스트 결과 Interrupt 벡터 0x93이 왔을 때는 IRQL이 9로 변경되었습니다.
그 다음을 보시면 [edi+0xc]를 call하고 있습니다. edi는 Interrupt Object를 가리키고 있었고 0xc에는 ServiceRoutine이 있었습니다. 바로 ServiceRoutine을 호출하는 코드입니다. 우리가 후킹해야할 부분입니다.
PKINTERRUPT->ServiceRoutine 에 MyInterruptFunc를 넣는 것입니다.
그러면 IRQL도 정상적으로 다 올라가 있고 그 전에 해야할 루틴들을 다 수행한 후 이기 때문에 에러가 뜨지 않습니다.
정확한 이유는 몰라도 후킹 코드 실행 시 ServiceRoutine이 호출되기 전에 코드들은 전부 실행이 되어 있는게 좋습니다.
가장 큰 이유는 IRQL...!!
그리고 마지막은 HalEndSystemInterrupt 함수 입니다.
커널이 인터럽트를 마치고, 원래 IRQL로 복원하려는 경우에 호출하는 함수로서, HalBeginSystemInterrupt() 함수와 항상 같이 사용되게 됩니다. 그 사이엔 ServiceRoutine이 있구요.
이제 어떤식으로 구성되어 있는지는 알아봤으니 후킹을 해보겠습니다. 먼저 ServiceRoutine의 값을 바꾸려면 KINTERRUPT 구조체 주소를 알아야 하는데 그 주소는 0x82B06D98 에 들어있었습니다.잘 보시면 ISR 주소에서 0x3c를 빼면 이 값이 나옵니다.
0x82b06dd4 - 0x3c = 0x82b06d98
이렇게 구해주면 됩니다. 한번 바뀐 후킹 코드를 보시겠습니다.
뭐 이런식으로 해놓을 경우 제가 생각하는 또 다른 장점으로는 IDT 테이블만 검사해 IDT가 후킹됬나 안됬나를 확인할 때는 안걸린다는 점입니다. 이렇게 후킹된 것을 발견하려면 해당 인터럽트 오브젝트까지 다 검사를 해야겠지요.
결론은 인터럽트 후킹 시 IDT에 있는 내용을 바로 후킹하기보다 해당 인터럽트에 대한 인터럽트 오브젝트 안에 있는 값을 바꿔주는게 훨씬 안정적으로 돌아간다는 것입니다.
'My Study > Programming&Theory' 카테고리의 다른 글
WM_QUERYENDSESSION Message (2) | 2010.12.26 |
---|---|
동적으로 Interrupt Vector값 얻기 ( APIC 설명.. ) (0) | 2010.12.26 |
Named Pipe 간단한 예제 (4) | 2010.12.24 |
WorkItem Routine (0) | 2010.12.18 |
2가지 DPC 루틴 (2) | 2010.12.17 |