'IDT'에 해당되는 글 1건

  1. 2009.01.04 IDT HOOK BASED ON JUMP TEMPLATE(PART-1)
2009. 1. 4. 00:06

IDT HOOK BASED ON JUMP TEMPLATE(PART-1)

IDT HOOK BASE ON JUMP TEMPLATE(PART-1)  Written by Sim-Hyeon, Choe

어떤 특정 인터럽트가 발생할 때마다 우리의 루트킷이 호출되게 할 수 있다면 나름대로 짜릿하고

흥미로운 일이 될 것입니다. 이 IDT를 후킹하였을 때 그 특성상 무수히 많은 호출을 바탕으로 우리가

원하는 제어를 할 수 있도 있지요.

IDT HOOK을 하기에 앞서 우선 IDT에 대해 알아보도록 하겠습니다.

IDT는 Interrupt Descriptor Table의 약자로 내부(트랩) 혹은 외부(하드웨어), 소프트웨어 인터럽트가

발생되었을 때, 이를 처리하기 위한 인터럽트 서비스 루틴의 실제 물리 주소의 서술자(Interrupt Gate

Descriptor)의 선두 번지들을 가지고 있는 테이블을 의미합니다.


인터럽트가 발생하면 프로세서는 EFLAGS 레지스터의 Interrupt Enable Flag를 Disable하고, IDTR

레지스터를 참조하여 해당 인터럽트의 벡터를 찾습니다. IDTR 레지스터는 IDT의 Base Address와

Limit를 포함하고 있는 48Bit 크기의 레지스터로, Base Address와 Interrupt Vector를 오프셋으로

태스크, 트랩 or 인터럽트 게이트 디스크립터를 지정합니다. Limit는 IDT 엔트리의 마지막 엔트리를

포함하는데 IDT를 참조시 이를 초과한 엔트리에 대한 접근의 경우에는 General Protection Fault가

발생하게 됩니다. 이 IDTR 레지스터의 정보를 저장하고 로드하기 위한 SIDT와 LIDT Instruction을

제공하기 때문에 RING 0에서 IDT에 대한 Read/Write가 가능합니다.


위의 그림과 같이 각 게이트의 디스크립터는 8 Byte로 이루어져 있으며, 그 구성을 살펴보면 먼저

Offset 31~16과 Offset 15~0은 실제 물리 메모리에서 인터럽트 서비스 핸들러 엔트리의 위치 오프셋을
 
의미하는데, 하위/상위 오프셋을 32비트 값으로 합치면 유효한 값으로 참조되어 질 수 있습니다.

P는 게이트의 디스크립터가 커널 코드 세그먼트의 물리 메모리에 활성화 상태를 나타내는 플래그이고,

Selector는 ISR이 위치하는 세그먼트의 Base Address를 지시합니다. DPL은 위의 그림과 같이 이 디

스크립터의 접근 권한을 나타내는 비트입니다. 나머지 비트는 중요하지 않기 때문에 생략하겠습니다.


IDT HOOK을 하기에 앞서, 다음 두가지 사항을 유의해야 합니다.

1) IDT는 프로세서마다 각각 따로 존재하기 때문에, HOOK하고자하는 ISR을 각 IDT마다 모두 후킹하여
    야 합니다.

2) 인터럽트 서비스 핸들러는 호출된 후, 실행 흐름의 제어가 이전 핸들러로 리턴되지 않기 때문에, 서비
    스 루틴에서 처리한 결과값을 필터링할 수 없습니다.


이 PART에서 시도할 후킹 기법은 JUMP TEMPLATE이라는 기법입니다.

JUMP TEMPLATE HOOK의 시나리오는 다음과 같습니다.

1) 후킹 타깃은 불특정 다수의 인터럽트 서비스 핸들러가 된다.

2) 불특정 다수의 인터럽트 핸들러로 분기할 하나의 템플릿 루틴을 만든다.
    (이것은 바이트 코드로 작성하면 된다. 이 루틴에서 우리의 HOOK ROUTINE으로 CALL하게 한다)

3) 템플릿 루틴을 실행할 수 있는 별도의 공간이 필요하기 때문에 Non-Paged Memory Pool을
   
메모리에 생성한다(당연히 템플릿 루틴이 페이징이 발생해서는 안된다)

4) 할당한 메모리 영역에 작성한 템플릿 코드를 복사한다.

5) IDT에서 ORIGINAL 인터럽트 서비스 루틴의 엔트리를 백업하고 IDT의 각 엔트리를 점프 템플릿
   
시작 엔트리로 변경한다.

6) 드라이버 언로드시, IDT를 원래의 ISR 엔트리로 복구한다.  


그럼 지금부터 직접 코딩하면서 하나하나씩 짚어가며 살펴보도록 하겠습니다.

우선 위의 스펙을 바탕으로 IDTR 레지스터와 각 게이트 디스크립터의 정보를 저장할 수 있는 구조체를

다음과 같이 정의합니다.

#pragma pack(1)

typedef struct IDTENTRY
{
      unsigned short LowOffset;
      unsigned short selector;
      unsigned char  unused_lo;
      unsigned char  segment_type:4;                // 0x0E is an interrupt gate
      unsigned char  system_segment_flag:1;
      unsigned char  DPL:2;                             // descriptor privilege level
      unsigned char  P:1; /* present */
      unsigned short HiOffset;

} IDTENTRY;


typedef struct IDTINFO
{
      unsigned short IDTLimit;
      unsigned short LowIDTbase;
      unsigned short HiIDTbase;

} IDTINFO;

#pragma pack()


NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject,
                                IN PUNICODE_STRING theRegistryPath )
{
     IDTINFO    IDT_Info;
     IDTENTRY *pIDT_Entries;
     
     __asm sidt IDT_info                   // IDTR 레지스터의 정보를 IDT_Info 구조체에 채워 넣는다.

     // 하위 어드레스와 상위 어드레스를 합쳐 IDT의 32비트 Base Address로 만든다.
     // 이렇게 구한 IDT의 Base Address를 pIDT_entries + Vector Offset으로 접근할 수 있다.
     pIDT_entries = (IDTENTRY *)MAKELONG( IDT_info.LowIDTbase, IDT_info.HiIDTbase );

    
     ...... ......
     ...... ......

     return STATUS_SUCCESS;
}

PART-1은 여기까지만 정리하고 PART-2에서 중요한 나머지 모두 서술하도록 하겠습니다.