본문 바로가기

My Study/Programming&Theory

동적으로 Interrupt Vector값 얻기 ( APIC 설명.. )

 PS/2 키로거를 만들 던 도중 또 하나의 문제에 부딪혔습니다.
OS마다 키보드를 눌렀을 때 발생하는 Interrupt Vector 값이 다르다는 것입니다.

뭐 XP, Vista, 7 ... 같은 OS버전 끼리는 Interrupt Vector 값이 같다면 간단하게 RtlGetVersion 함수를 사용해
OS버전을 읽어오고 각 OS버전에 맞게 Interrupt Vector 값을 셋팅해주면 됩니다. 하지만 제 테스트 결과..
같은 윈도우 버전 6.1(Windows 7).. 이라도 Interrupt Vector 값이 달랐습니다.

Ultimate K Enterprise K 2개를 비교해본 결과 같은 윈도우 버전 값을 갖지만 Interrupt Vector 값은 달랐습니다.
이럴 경우 참 난감합니다. 

그래서 이번엔 커널 함수를 사용해 실시간으로 Interrupt Vector 값을 얻어오는 방법에 대해 알아보겠습니다.
이에 대한 내용으로 여기저기 구글링을 해보고 있는데 마침 괜찮은 곳이 나왔습니다.
대충 위 사이트를 보고 따라서 해보았습니다.

위 사이트에서 보면 Interrupt Vector 값을 얻는 방법으로는 3가지가 있다고 나와있습니다.

첫번째, Using APIC
APIC는 SMP 환경을 위해 만들어진 Advanced PIC입니다.
APIC에 대해서 조금 더 설명해 보겠습니다.
원래 하나의 CPU만 사용하던 시절에는 i8259A PIC칩이 하드웨어 인터럽트 신호를 관리하였습니다.

i8259A PIC칩은 PCI 칩들이 사용하는 인터럽트 신호와 ISA 버스에서 사용하는 인터럽트 신호를 둘다 받아들여
인터럽트 벡터 값을 계산 후 INT 명령과 함께 오퍼랜드로 사용되어 CPU에 전달되게 됩니다.
이 때 이 벡터 값을 구하려면 i8259A PIC칩에 들어오는 IRQ 값에 +0x30을 해주면 됩니다.

하지만 요즘은 SMP 환경으로 거의 다 바뀌었습니다. 즉, 하드웨어 인터럽트가 발생했을 때 그 값을 계산해 Interrupt 벡터 값을
CPU에 전달해주는 PIC는 바뀔 수 밖에 없습니다. 여러 CPU에 Interrupt 벡터 값을 전달해 주어야 하기 때문이죠.
또한 ISA, PCI버스 만이 아닌 더 다양한 버스에서 발생되는 인터럽트 또한 CPU에 전달하는 방법까지 고려해야 합니다.
그래서 나온 것이 APIC입니다. APIC는 기존에 사용하던 i8259A PIC의 환경을 포함하고 있어서, 과거와의 호환성을 여전히 제공은 합니다.


보시면 APIC 핵심 칩인 LOCAL APIC가 있고 I/O APIC가 있습니다.
LOCAL APIC는 각각의 CPU마다 하나씩 존재하는 칩입니다. 그리고 I/O APIC는 메인 보드당 하나 또는 여러 개까지 존재할 수 칩이지만 대부분 하나의 I/O APIC를 사용합니다.

하드웨어들이 발생하는 인터럽트 신호를 LOCAL APIC로 전달해주는 것이 I/O APIC입니다. 그리고 LOCAL APIC는 자신에게 들어온 인터럽트 신호를 자신이 속한 CPU에게 전달하는 역할을 담당합니다.

위 그림은 APIC 환경에서 i8259A PIC의 사용 모드일 때의 그림이지만 윈도우는 부팅과정 때 APIC를 사용하도록 동작 모드를 바꿔버립니다. 그러면 더이상 i8259A 호환 칩은 사용되지 않습니다. 

기존에는 인터럽트 벡터 값이 16개만 사용됬지만 이제는 엄청나게 많은 값을 사용할 수 있게 됬습니다.

일단 APIC에 대한 것을 설명해 보았고 다시 벡터 값을 구하는 것으로 촛점을 바꿔보겠습니다.
WinDbg 명령어로 !ioapic를 치면 IoApic 주소가 나오는데 이 주소는 IDT 벡터 정보를 포함하고 있다고 나왔습니다.
한번 제 컴퓨터에서 쳐보았습니다.


딱 이것만 보고는 뭘 해야될지 모르겠네요 ㅠㅠ 더 자세한 정보
여기서 한번 읽어보시길 바랍니다. 저도 공부를 해야겠군요.

두번째 방법은 Scanning kernel memory 입니다.
이 방법의 메인 기술은 hal.dll의 메모리 공간이 IDTIRQ 정보를 포함하고 있다는 것입니다. 그리고 우리는 그 메모리 공간을 스케닝 함으로써 그 값을 얻을 수 있다는 군요.
해당 문서의 아래쪽을 보면 어셈블리 코드까지 자세히 나와있습니다.

세번째 방법은 Kernel API를 사용하는 방법입니다.
사용하는 함수는 HalGetInterruptVector입니다. 해당 함수를 구글에서 찾아봤더니..!! =_=
지금은 무용지물이 된 함수랍니다. 지금도 잘만 되는데 왜 무용지물이 됬는지.. 
아무튼 인자에 대한 설명이 다 없어져버렸습니다 ㅠㅠ
일단 사용해보겠습니다.

첫번째  인자로는 Bus Data Type을 넣는데 
typedef enum _INTERFACE_TYPE
{
         InterfaceTypeUndefined = -1,
         Internal = 0,
         Isa = 1,
         Eisa = 2,
         MicroChannel = 3,
         TurboChannel = 4,
         PCIBus = 5,
         VMEBus = 6,
         NuBus = 7,
         PCMCIABus = 8,
         CBus = 9,
         MPIBus = 10,
         MPSABus = 11,
         ProcessorInternal = 12,
         InternalPowerBus = 13,
         PNPISABus = 14,
         PNPBus = 15,
         MaximumInterfaceType = 16
} INTERFACE_TYPE;
위 값들 중 하나를 선택에서 너으면 됩니다. 키보드 interrupt hooking을 하는데 ISA를 할까 PCIBus를 할까 했는데 둘다 나오는 결과 값은 같았습니다.


사용은 이렇게...
그러면 Interrupt_Num에는 벡터 값이 들어오는데 XP같은 경우는 0x93 값이 들어왔고
Windows 7 Ultimate K는 0x61 Windows 7 Enterprise K는 0x71이 들어왔습니다.

이 값은 Windbg에서 !idt 명령어로 IDT를 보았을 때 Keyboard Interrupt 값과 같습니다.


먼저 제 VMware에는 XP SP3 MP환경과 SMP 환경 모두 구축이 되어 있으므로 둘다 테스트를 해보았습니다.
그 결과 IDT 후킹은 성공 하였습니다. 키를 눌렀을 때 제가 만든 핸들러로 넘어오는게 성공했죠.

Windows7 또한 성공했습니다. SMP 환경에서 말이죠.

팁으로.. 제가 키로거를 만드는데 있어서 잠깐의 삽질 과정 중 하나를 말하겠습니다.
IDT 후킹을 해서 키로거를 만들 땐 IDT 테이블에 있는 주소를 바로 후킹하는 것이 아닌 KINTERRUPT 구조체 
즉, interrupt object 안에있는 ServiceRoutine에 있는 값을 바꿔치기 해야합니다.
그 이유는 http://ezbeat.tistory.com/301 여기에 대충 썼었습니다.

이 때 ISR주소에서 KINTERRUPT 주소를 가지고 있는 구조체 포인터를 구해야 하는데 Windows XP 같은 경우는 0x3C를 빼주면 됬습니다. 하지만 Windows 7에서는 0x58을 빼주어야 합니다. 이것때문에 거진....5~6시간은 삽질한거 같습니다..ㅠㅠ

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

Inline Hooking  (2) 2011.01.10
WM_QUERYENDSESSION Message  (2) 2010.12.26
IDT Hooking 시 주의해야 할 점  (0) 2010.12.25
Named Pipe 간단한 예제  (4) 2010.12.24
WorkItem Routine  (0) 2010.12.18