관리 메뉴

kisoo

커널디버거를 동작하지 못하도록 하는 방법 과 스핀락에 대하여 본문

01.About Programming /12.Default knowledge

커널디버거를 동작하지 못하도록 하는 방법 과 스핀락에 대하여

JamesK78 2010. 1. 5. 13:33

Endless Creation이라는 서울산업대학교 컴퓨터공학과내 과소모임 홈페이지에서

 

이봉석님께서 작성하신 글을 옮김.

 

(홈페이지에 방명록이 없어서...

양해를 구하지 못하고 옮겼습니다...)

=================================================

 

커널디버거를 동작하지 못하도록 하는 방법

보안제품을 개발하여, 시중에 제공하게 되면, 간간히 특별한 하나님의 지혜를 받은 분들이 커널디버거를 동원하여 이 보안제품의 보안성을 가차없이 파해쳐버리는 경우를 종종보게 된다. 완벽한 보안은 사실 사내직원들의 인성교육밖에 없다는 것은 모두 다 아는 사실이며, 완벽한 보안은 불가능하다고 보는 견해가 일반적인 만큼 이번호에서는 간단한 트릭을 사용하여 커널디버거의 동작을 무용지물로 만드는 방법을 소개하고자 한다.

커널디버거로 많이 사용되는 제품은 “소프트아이스”와 “윈디버거”정도로 볼 수 있다. 이런 제품들은 CPU가 제공하는 디버그레지스터에 의존하는 대표적인 디버거라고 볼 수 있다. 이런 점에서 디버그레지스터에 관한 간단한 조작으로 커널디버거를 사용하는 유저에게 뼈아쁜 불루스크린의 영광을 가져다 줄수 있을것이다.

인텔호환의 CPU안에는 다음과 같은 디버그레지스터가 있다.
DR0 : 중단점설정에 사용되는 레지스터0
DR1 : 중단점설정에 사용되는 레지스터1
DR2 : 중단점설정에 사용되는 레지스터2
DR3 : 중단점설정에 사용되는 레지스터3
DR6 : 중단점설정에 반응한 어떤 동작이 발생되었을때, DR0-DR4중에 어떤 요인으로 중단점반응이 발생하였는가를 확인하는 레지스터
DR7 : 중단점설정을 제어하는 컨트롤레지스터

레지스터외에 또 다른 중요한 포인트가 바로 INT 1(Exception 1)핸들러이다. 이 핸들러는 중단점설정을 한뒤, 이 중단점조건에 부합되는 사건이 발생될때마다 호출되는 핸들러이기 때문이다.

가장간단하고 무식하게 커널디버거의 동작을 무용지물로 만들기위해서는 INT 1핸들러를 설치하는 방법을 들 수 있는데, 문제는 핸들러를 설치한뒤, 이 핸들러가 호출되는 상황은 비단 커널디버거만 해당하는 것이 아니라, 많이 사용하는 응용프로그램레벨의 디버거들도 INT 1핸들러가 호출된다는 것이다. 따라서, 단지 INT 1핸들러를 간단하게 만드는 일보다는, INT 1핸들러가 호출되는 이유가 어디에 있는가를 먼저 확인한뒤, 커널레벨에서 발생시킨 조건인지 아니면 응용프로그램레벨에서 발생시킨 조건인지를 파악하여, 커널레벨에서 발생시킨 조건인경우에만 불루스크린의 영광을 처절하게 사용자에게 보여주는 것이 당연히 좋은 방법이라고 볼 수 있다.

이시간에는 이런 기능을 수행하는 INT 1핸들러의 코드와, 핸들러를 설치하는 방법에 대해서 배우도록 하겠다.

레지스터를 사용하는 방법은 마이크로프로세서레퍼런스를 참고하도록 한다.




ULONG *OldESP;
void __declspec(naked) MyISR()
{
        _asm cli;
        _asm mov OldESP, ESP;
        _asm pushad;

        _asm mov eax, dr6;
        _asm mov esi, g_OldIsr;
        _asm and eax, 0x0000000f; // 디버그레지스터를 사용한경우라면, 무조건 커널디
                                        // 버거가 발생시킨 조건이다
        _asm jz Client_Debug

ByeBye:
        _asm hlt; // 시스템을 맛동산 가도록 한다....
                
Client_Debug: // 디버그레지스터를 사용하지 않은 경우라면, 일단, 현재 쓰래드가 커널레벨
                // 인지 아니면 유저레벨인지를 파악한다
        _asm mov EDX, OldESP;
        _asm mov EAX, [EDX+4];
        _asm and al, 3;
        _asm cmp al, 3; // CS's CPL이 3인가?(UserLevel인가?)
        _asm jz App_Debug
        _asm jmp ByeBye;

App_Debug:
        _asm popad; // 이런경우라면, 일반 응용프로그램수준의 디버거와 관련있다
                        // 따라서, 원래의 INT 1핸들러를 호출하도록 유도한다
        _asm push g_OldIsr;
        _asm ret;
}


ULONG g_OldIsr;
typedef struct
{
        unsigned short wLowAddress;
        unsigned short wSegment;
        unsigned char cReserved[2];
        unsigned short wHighAddress;
}IDTSTRUCT;

IDTSTRUCT * GetInterruptDescriptorPointer( unsigned short selector )
{
        unsigned char IDTTable[6];
        IDTSTRUCT * IDTBase;
        unsigned char *pTempAddress;

        _asm sidt fword ptr IDTTable;
        IDTBase = (IDTSTRUCT *)(*((ULONG *)(IDTTable+2)));
        pTempAddress = (unsigned char *)IDTBase;
        pTempAddress += selector << 3;
        IDTBase = (IDTSTRUCT * )pTempAddress;
        return IDTBase;
}

void InstallISR()
{
        IDTSTRUCT * pIdtDesc;
        unsigned short wIsrAddressL;
        unsigned short wIsrAddressH;
        ULONG dwIsrAddress;

        pIdtDesc = GetInterruptDescriptorPointer( 0x1 ); // IDT로부터 1게이트의 포인
                                                        // 터를 구한다
        dwAddress = (ULONG)MyISR;
        wAddress = (unsigned short)(dwAddress >> 16);
        wIsrAddressH = (unsigned short)pIdtDesc->wHighAddress;
        pIdtDesc->wHighAddress = wAddress; // MyISR의 주소(상위)를 기록한다
        dwAddress = (ULONG)MyISR;
        wAddress = (unsigned short)(dwAddress & 0x0000FFFF);
        wIsrAddressL = (unsigned short)pIdtDesc->wLowAddress;
        pIdtDesc->wLowAddress = wAddress; // MyISR의 주소(하위)를 기록한다
        pIdtDesc->cReserved[1] |= 0xE0;
        dwIsrAddress = (ULONG)wIsrAddressH;
        dwIsrAddress <<= 16;
        
        g_OldIsr = dwIsrAddress + wIsrAddressL; // 원래의 INT 1주소를 보관한다
}

 

스핀락에 대해서

스핀락은 동기화 객체이므로, 우선 동기화 객체에 대해서 설명하면 다음과 같습니다.

동기화 객체의 사용 목적은 여러 커널 루틴이 공유된 자원(ex. 데이터가 일렬로 저장되는 큐)에 접근 할 때, 서로 간섭을 받지 않도록 하는데 있다고 할수 있습니다.
예를 들면, 두개의 시스템 스레드가 있는데 한 스레드(입력 스레드)는 큐에 데이터를 저장하고, 다른 스레드(출력 스레드)는 큐에서 데이터를 꺼내는 역할을 한다고 해봅시다. 이 스레드들이 동시에 실행이 되고 있을 때, 동시에 큐에 접근해서 데이터를 꺼내고 데이터를 읽는다고 하면 문제가 발생하게 됩니다. 왜냐하면, 입력 스레드는 큐 입력을 위해서 메모리 복사를 하게 되고 출력 스레드는 큐에서 데이터를 출력하기 위해 역시 메모리 복사를 하는 프로세스를 각각 수행하게 되는데, 이런 프로세스가 동시에 진행된다고 하면 큐 입력 프로세스(메모리 복사)가 채 끝나기도 전에 큐 출력 프로세스(역시 메모리 복사)를 진행하게 되는 상황이 종종 생기게 되어서 큐의 출력데이터에 대한 확실성을 보장받지 못할것입니다.
이런 상황을 방지하기 위해서 MS에서는 동기화 객체들을 제공한다는 것을 잘 알고 계실줄 압니다. 이벤트, 세마포어, 뮤덱스, 스핀락 등이지요. 이 중에서도 스핀락은 멀티 프로세서 시스템을 위한 동기화 객체입니다. 실제 코드에서 꼭 그런 목적으로 쓰이는 건 아니지만, 나온 배경은 멀티 프로세서들의 동기화 문제에서 비롯되었습니다.

스핀락이 어떻게 구현되었는지 분석해 보겠습니다.

KfAcquireSpinLock 의 코드를 역어셈블 해봤습니다. 일반적으로 드라이버 코드에서는 KeAcquireSpinkLock 함수를 호출하게 될것입니다. Wdm.h 파일에서는 다음과 같이 정의되어있으므로, 어셈블 코드는 KfAcquireSpinLock 을 분석합니다.

표 0. wdm.h 헤더 파일의 스핀락 관련

<wdm.h> 헤더 파일 안의 정의

#define KeLowerIrql(a)      KfLowerIrql(a)
#define KeRaiseIrql(a,b)    *(b) = KfRaiseIrql(a)

#define KeAcquireSpinLock(a,b)  *(b) = KfAcquireSpinLock(a)
#define KeReleaseSpinLock(a,b)  KfReleaseSpinLock(a,b)








표 1. KfAcquireSpinLock 디스어셈블 코드

==========================================================================
                public KfRaiseIrql
KfRaiseIrql     proc near               ;
                                        ;
                movzx   edx, cl
                movzx   ecx, ds:byte_80012398[edx]
                mov     eax, ds:0FFFE0080h
                mov     ds:0FFFE0080h, ecx
                shr     eax, 4
                movzx   eax, ds:byte_8001D218[eax]
                retn
KfRaiseIrql     endp
==========================================================================
                public KfAcquireSpinLock
KfAcquireSpinLock proc near             ;
                                        ;
                mov     edx, ds:0FFFE0080h
                mov     dword ptr ds:0FFFE0080h, 41h
                shr     edx, 4                                                
                movzx   eax, ds:byte_8001D218[edx]        

loc_8001284A:                           ;
                lock bts     dword ptr [ecx], 0
                jb      short loc_80012854
                retn
;
                align 4

loc_80012854:                           ;
                                        ;
                test    dword ptr [ecx], 1
                jz      short loc_8001284A
                pause
                jmp     short loc_80012854
KfAcquireSpinLock endp



표 1을 보면, KfAcquireSpinLock 루틴의 앞부분이 KfRaiseIrql 루틴과 비슷함을 알수 있습니다. KfAcquireSpinLock 루틴에서는 우선 KfRaiseIrql 로 IRQL을 올리게 되는데, DISPATCH_LEVEL 까지 올려서 다른 스레드의 간섭을 우선 차단하게 되고,  
lock bts     dword ptr [ecx], 0 이하 코드부터는, 스핀락이 해제될때까지 루프를 돌게 됩니다. 그리고 루프를 계속 돌다가 스핀락이 해제되면, 스핀락을 얻고 리턴하게 됩니다. 그다음부터는 락이 걸린 상태에서 KfAcquireSpinLock 루틴 다음 부분부터의 코드가 여기서 얻은 동일한 스핀락을 취하려는 다른 CPU의 코드나 스레드로부터 간섭하지 않는 상태에서 수행을 하고나서 스핀락을 해제 하게 됩니다.
스핀락을 해제하는 루틴은 KfReleaseSpinLock 이고, 어셈블 코드는 다음과 같습니다. KfLowerIrql 루틴과 비슷함을 확인할수 있습니다.

표 2. KfReleaseSpinLock 디스어셈블 코드
==========================================================================
                public KfLowerIrql
KfLowerIrql     proc near               ;
                                        ;
                xor     eax, eax
                mov     al, cl
                xor     ecx, ecx
                mov     cl, ds:byte_80012398[eax]
                mov     ds:0FFFE0080h, ecx
                mov     eax, ds:0FFFE0080h
                retn
KfLowerIrql     endp
==========================================================================
                public KfReleaseSpinLock
KfReleaseSpinLock proc near             ;
                                        ;
                xor     eax, eax
                mov     al, dl
                mov     byte ptr [ecx], 0
                xor     ecx, ecx
                mov     cl, ds:byte_80012398[eax]
                mov     ds:0FFFE0080h, ecx
                mov     eax, ds:0FFFE0080h
                retn
KfReleaseSpinLock endp



여기까지는 멀티 CPU나 하이퍼스레딩이 돌아가는 윈도우에서의 KfAcquireSpinLock에 대해서 분석해봤습니다. 단일 CPU가 돌아가는 윈도우 시스템에서는 KfAqcuireSpinLock 은 단지 KfRaiseIrql(DISPATCH_LEVEL) 로 IRQL을 올리는 일만 수행합니다. 


Comments