시간이 나서, Timer DPC에 관해서 정리해보고자 글을 쓴다.

  먼저 Timer DPC 라는 제목을 살펴보자. Timer와 DPC 2개로 분리가 된다. Timer는 뭔가 시간이 흐른다는것을 예상해볼 수 있고, DPC는 Deferred Procedure Call 의 약자로써, 지연된 처리의 호출이라고 해석할 수 있다. 사실 Timer의 의미는 대략적으로 예상해볼 수 있는데,  DPC는 뭐가 도대체 지연된 호출인가? 지연된 호출이 왜 필요하단 말인가? 하고 궁금증이 생길 수 있다.

  DPC를 이해하기 위해서는 IRQ 우선순위와 커널의 인터럽트 처리 메커니즘을 알고있어야 하는데, 일반적으로 외부 하드웨어에서 CPU에 인터럽트를 보내면 커널은 이를 인지하여 , 해당 하드웨어에 맞는 ISR을 적절히 선택하여 인터럽트 처리를 수행한다. 그런데 보통 이러한 인터럽트 서비스 루틴은 해당 하드웨어의 요청에 정말 필요한 크리티컬 코드들로만 짧게 구성되어져 있는데, 이는 어느 특정 인터럽트 서비스 루틴으로 인해, 다른 인터럽트 처리의 방해가 없도록 하기 위해서이다. 여기서 갑자기 방해가 왜 나올까? 라고 생각해볼수가 있는데, 여기에서 인터럽트 우선순위 레벨(IRQL) 을 생각해볼 수 있다. 예를 들어 시스템에서 특정 하드웨어가 있는데 , 이 하드웨어는 매우 크리티컬한 요소라서 최우선적으로 처리되어야 할 놈이라고 가정해보자. 이 하드웨어는 IRQL26 우선순위를 부여받고, ISR을 처리하게된다고 가정했을때,  ISR에서 빨리 끝내주지 않고 , 질질 끌게되면 다른 우선순위 낮은 하드웨어(예 : 키보드) 가 인터럽트 처리를 요청했을때, 우선순위 높은 애가 아직 CPU를 부여잡고 있으므로, 기다릴수 밖에 없을것이다.  키보드 빨리빨리 입력해야하는데 , 키보드가 드득드득 지연되면서 입력되는것을 상상해본적이 있는가? 
  그럼 이 문제를 해결하려면 어떻게 해야할까? 참 이런거 생각한사람들은 머리가 좋은것 같다. (어찌보면 단순한가?)  해결법은 ISR을 최대한 단순하고 중요한 코드들로만 구성하고, 만약에 ISR에서 못다한 그리 중요하지않은 추가적인 작업이 필요할 경우, DPC라는 녀석을 요청하는것이다. DPC는  DIRQL이상의 하드웨어 인터럽트 처리가 완료되고나서 , PASSIVE_LEVEL로 떨어지기 전에 DISPATCH_LEVEL에서 작동하는 구간인데, SW Interrupt(PASSIVE_LEVEL, APC_LEVEL , DISPATCH_LEVEL) 중에서는 가장 높은 레벨에 속한다. 이렇게 되면 하드웨어 인터럽트 처리가 완료되고 난다음에 DPC가 처리되기 때문에 , 다른 하드웨어의 인터럽트를 처리하는데 별 무리가 없을것이다.


  이제 Timer DPC의 의미를 다시 한번 생각해보자. DPC는 HW ISR처리가 완료되고난다음, DISPATCH_LEVEL에서 작동하는 SW INT인데 , 이것이 Timer와 합쳐지면 어떤 관련이 있을까? 사실 필자도 이것에 관해서 딱히 책에서 찾아본적은 없는데, 가만히 생각해보면 DPC는 SW INT이므로 , "커널 Timer의 힘을 빌어서 주기적으로 발생되는 SW INT" 라고 생각해도 크게 나쁜것 같지는 않다.

본격적으로 윈도우 커널에서 제공하는 Timer 관련 함수들중 자주 사용되는 함수들을 알아보자.
 
 
1. NTKERNELAPI VOID KeInitializeTimer ( __out PKTIMER Timer );

2. NTKERNELAPI BOOLEAN KeSetTimerEx ( __inout PKTIMER Timer, __in LARGE_INTEGER DueTime, __in LONG Period, __in_opt PKDPC Dpc );

 3. NTKERNELAPI BOOLEAN KeCancelTimer ( __inout PKTIMER )
 

 
 1. 타이머를 사용하기에 앞서 , 초기화를 수행해주는 함수이다.  ExAllocatePool을 이용하여 SystemPool에 KTIMER만큼 할당한뒤, 사용하는것을 권장.

2. 타이머 설정&시작 함수인데, 옵션으로 DPC루틴도 파라메터로 전달할수 있어서, 주기적으로 처리해야할 작업이 있을경우 요긴하게 쓸수 있다. 참고로 , KeSetTimer 함수와 KeSetTimerEx 함수간의 차이가 존재하는데,  KeSetTimer 함수는 Signal이 한번만 발생해서 DPC루틴이 한번만 호출되고 끝나게 되고,  KeSetTimerEx는 LONG Period만큼 주기적으로 DPC루틴이 호출되게 된다.
 
 
3. 타이머를 취소하는 함수로써 , 본인 코드 구성에 따라, 필요에따라 UnloadRoutine 같은곳에 넣어준다.


필자는 따로 편리하게 쓰기 위해서 함수를 정의한뒤, 

PKTIMER
InitializeTimer(VOID)
{
PKTIMER Timer;

Timer = (PKTIMER)ExAllocatePool(NonPagedPool, sizeof(KTIMER));
if(Timer == NULL)
{
return NULL;
}

KeInitializeTimer(Timer);
return Timer;
}

PKDPC
SetTimer
(
IN PKTIMER Timer,
IN LONG Period, 
IN OPTIONAL KDEFERRED_ROUTINE TimerDpcRoutine, 
IN OPTIONAL PVOID DpcRoutineContext
)
{
LARGE_INTEGER TimePeriod;
PKDPC DpcObj;

DpcObj = NULL;

if(TimerDpcRoutine != NULL)
{
DpcObj = (PKDPC) ExAllocatePool(NonPagedPool, sizeof(KDPC));
if(DpcObj == NULL)
{
return NULL;
}
KeInitializeDpc(DpcObj, TimerDpcRoutine, DpcRoutineContext);
}

TimePeriod.QuadPart = -100;
KeSetTimerEx(Timer, TimePeriod, Period, DpcObj);

return DpcObj;
}
 
VOID
ReleaseTimer(IN PKTIMER Timer, IN OPTIONAL PKDPC DpcObj)
{
KeCancelTimer(Timer);
ExFreePool(Timer);

if(DpcObj != NULL)
{
ExFreePool(DpcObj);
}
}


DriverEntry에  다음과 같은 코드를 넣어주었다.


TimerObj = InitializeTimer();
if(TimerObj == NULL)
{
return STATUS_UNSUCCESSFUL;
}
TimerDpcObj = SetTimer(TimerObj, 1000, TimerDpcRoutine, NULL);
if(!TimerDpcObj)
{
return STATUS_UNSUCCESSFUL;
}


또한 UnloadRoutine에는 다음과 같은 코드를 넣어주었다.

ReleaseTimer(TimerObj, TimerDpcObj);
 


이제 TimerDpcRoutine을 살펴보자.

 VOID TimerDpcRoutine(
IN PKDPC Dpc,
IN OPTIONAL PVOID DeferredContext,
IN OPTIONAL PVOID SystemArgument1,
IN OPTIONAL PVOID SystemArgument2
)
{
DBG_PRINT1("Warning! This routine executes on DISPATCH_LEVEL!\n");

if(IsSSDTAlreadyHooked(
(PBYTE)ZwSetInformationThread, 
(PBYTE)NewZwSetInformationThread, 
(PDWORD)KeServiceDescriptorTable.KiServiceTable) == FALSE)
{
disableWP_CR0();
OldZwSetInformationThread = (ZwSetInformationThreadPtr) hookSSDT((PBYTE)ZwSetInformationThread, 
(PBYTE)NewZwSetInformationThread, 
(PDWORD)KeServiceDescriptorTable.KiServiceTable);
enableWP_CR0();
}
return;
}

Timer DPC를 이용해서 간단하게 주기적으로 SSDT훅을 거는 코드를 구성해보았다. 이렇게 코드를 구성하면 , Timer DPC Detect 기능이 없는 루트킷 디텍터에서 SSDT를 Restore 한다고 해도, 1초마다 훅이 걸려있는지 검사해서 다시 훅을 걸어버리기 때문에, 악성 루트킷에서 요긴하게 사용될법 하다.
주의할점은, DPC Routine에서 Zw Routine은 호출하면 안된다는것이다. (Zw Routine은  PASSIVE_LEVEL에서만 호출이 가능하다!)

 

테스트 결과 잘 작동이 되었다.
아래는 스크린 샷.


드라이버를 시작한 모습.  DriverEntry에서 훅을 거는 코드를 작성하진 않았고 , TimerDpcRoutine에 훅을 거는 코드를 삽입했다. 따라서 , 훅은 TimerDpcRoutine에 의해서 걸린것이다.


SSDT Restore를 수행하려고 시도중인 모습.


SSDT Restore를 수행해도 바로 훅이 다시 걸린다. Period를 적절히 조절해주면 Performance도 어느정도 확보할 수 있을것 같다.


 
저작자 표시 비영리 변경 금지
신고
by Sone 2011.05.05 11:30
처음 SSDT 훅을 접해보는 사람은, 그냥 아무 생각없이 서비스넘버를 하드코딩 하는 경우가 다반사일 것이다. 물론 필자도 그랬었다. 
(아 그냥 , 하드코딩 개념이랑 비슷하구나, 단순히 구분을 위한 숫자이구나 라고 생각했었다.)
  그래서 서비스넘버에 관해서 자세히 알아볼 필요성이 있을것 같아서, 생각 정리도 할겸 포스팅을 해본다.


아래는 Windows Internal Fifth Edition 영문판의 128페이지에 나오는 내용이다.

2011:04:09 00:45:00

 
 Windows Internal에 따르면 , System Service Number는  Compile time때 Automatic Generate된 번호로써, 마이크로소프트 서비스팩 버젼에 따라서, OS버젼에 따라서 바뀔 가능성이 언제든지 존재한다. (이래서, 하드코딩은 금물이다)
 그 번호는 총 14비트로 구성되며, MSB 2Bit는 Service Table Index, LSB 12Bit는 System Service Number 이다.
System Service Number는 말 그대로,  시스템콜 디스패칭 식별을 위한 식별자이며,
Service Table Index를 살펴보면 , 2Bit이므로 총 4개까지의 서비스 테이블이 시스템에 존재할 수 있게된다.
ServiceDescriptorTable 구조체를 살펴보면 쉽게 알수있다.

#pragma pack(1)
typedef struct ServiceDescriptorEntry
{
UINT* ServiceTableBase;
UINT* ServiceCounterTableBase;
UINT NumberOfServices;
UCHAR* ParamTableBase;
} SDE, *PSDE;
#pragma pack()

typedef struct ServiceDescriptorTable
{
SDE ntoskrnl;           //00b
SDE win32k;             //01b
SDE Reserved0;        //10b
SDE Reserved1;        //11b
} SDT, *PSDT;

 ntoskrnl와 , win32k, Reserved0, Reserved1  총 4개의 서비스 테이블이 존재할수가 있다.
WinDBG로 살펴보면 알수있다.

 

2011:04:09 01:06:19

위 스샷에는 Reserved0와 Reserved1 항목은 서비스 디스패칭 용도가 아닌, 아예 다른 용도로 쓰여지고 있는것 같았다.
(확실치는 않다. 아시는분 알려주세요)
결과적으로 MSB 2Bit는 테이블 인덱스 식별자이다.

NTDLL.DLL에 포함되어져 있는, 서비스 디스패칭 루틴들의 서비스번호는
0x0XXX형태를 띄고있는데,
이것을 Binary로 바꿔보면,

00 1111 1111 1111 의 형태이다.

USER/GDI 서비스가 호출될때의 서비스 디스패칭 루틴들의 서비스번호는
0x1XXX형태를 띄고 있고, 이것을 바이너리로 바꿔보면

01 1111 1111 1111 의 형태이다.
(참고로, win32k subsystem driver는  Current Thread가 GUI Thread일때 , KeAddSystemServiceTable에 의해서 추가되어져서, NonPagedPool로 바뀌면서 ,, 서비스 호출이 이루어진다고 알고있다.)


나중에 혹시나 서비스 테이블이 더 추가된다면,
0x2XXX (10 1111 1111 1111) , 0x3XXX( 11 1111 1111 1111) 의 형태로 추가될 것이다.

저작자 표시 비영리 변경 금지
신고
by Sone 2011.04.09 14:29
 요즘에는 SSDT후킹에 관한 정보들이 온라인상에 매우 많아졌고, 예제 코드또한 많아서 , 이에 대해서 잘 모른다고해도 , 몇번 쳐보고 실습해보면 금방 알수있게 되었습니다. SSDT후킹이라 함은,  System Service Descriptor Table 을 후킹한다는말인데, 좀더 구체적으로 살펴보면, 우선 커널에서 Export 하고있는 KeServiceDescriptorTable (또는 KeServiceDescriptorTableShadow)의 구조체를 살펴보면 다음과 같습니다.


typedef struct ServiceDescriptorEntry
{
UINT* ServiceTableBase;
UINT* ServiceCounterTableBase;
UINT NumberOfServices;
UCHAR* ParamTableBase;
} SSDT_Entry, *PSSDT_Entry;

typedef struct _SERVICE_DESCRIPTOR_TABLE {
SSDT_Entry ntoskrnl;          // Native API
SSDT_Entry win32k;           // GUI Sub System
SSDT_Entry reserved3; // Reserved.
SSDT_Entry reserved4; // Reserved.
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE; 


 대부분 SSDT후킹이라함은 , ntoskrnl(또는 win32k) . ServiceTableBase[INDEX]  를 따내는것이 대부분일것이고, 이것을 따내는목적은 시스템콜을 후킹함으로써 , 전역적으로 모든 쓰레드에 대하여 자신이 원하는 작업을 수행하게하려는 목적일것입니다. 그런데 이미 오래전부터 잘 알려진 사실이지만 ,  엄밀히 말하면  SSDT를 후킹하는것이   모든 쓰레드에 관해서 전역적으로 후킹한다는것은 틀린말입니다.
 윈도우에서 제공해주는 시스템API를 이용하여 쓰레드를 만들면 (또는 Main Thread 포함)  윈도우에서는 생성된 쓰레드를 효율적으로 관리하기위하여 , 커널레벨 내부적으로 ETHREAD , KTHREAD라는 Struct를 생성하여 관리합니다. 이중,  KTHREAD라는 구조체 내부를 살펴보면 다음과 같은 멤버가 있습니다.

(Windows XP Service Pack 3 기준)
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER

 즉 쓰레드마다  ServiceTable이라는 멤버를 가지고있고 , 특정 쓰레드에서 커널 시스템콜을 호출할 경우, 그 쓰레드는 자신의 KTHREAD의 ServiceTable 멤버를 참조하여 , 서비스 테이블을 찾아감을 알수 있습니다. 여기서 생각해보면 , 위 ServiceTable 멤버의 값을 임의로 바꿔버리게 되면 커널이 Export 하고있는 서비스테이블이 아닌, 다른 위치를 참조하게 된다는 말입니다.  먼저 ExAllocatePoolWithTag를 이용해서 메모리를 할당한뒤 , RtlCopyMemory 등을 이용하여, 원래의 서비스테이블 내용을 복사해주고 , ServiceTable멤버의 값을 새로 할당한 메모리주소로 바꿔주면 , 그때부터 그 쓰레드는 커널에서 Export 하고있는 서비스테이블을 참조하는것이 아니라 , 유저가 임의로 생성한 서비스테이블을 참조하게 되는것입니다. 흔히 SSDT를 따내면 전역적으로 후킹이 된다라고 하는것은 , 어플리케이션단에서 작동하는 쓰레드에 대해서 KTHREAD의 ServiceTable 멤버가 조작되지 않았을 경우에 한해서만 성립됩니다.  쓰레드마다 ServiceTable 멤버를 가지고있고 이 값이 변경되버리면 커널에서 Export 하고있는 SSDT를 후킹한다고 해도 , 그 후킹이 먹히지가 않을것이기 때문에 , 전역적인 후킹이라는것이 성립되지 않게 되는것이죠. 이것이 일명 Service Table Relocation이라고 하는것입니다. 이 Service Table Relocation은  SSDT후킹으로부터 보호하고자 하는 쓰레드를 대상으로해서 , 효율적으로 코드를 작성할수가 있어서 현업에서도 종종 이용되는것으로 알고있습니다. (1위 온라인 FPS 총게임에 채택된 보안솔루션도 이 방법을 쓰고있죠.)

 그런데 여기서 머리를 조금만 굴려보면 , 이 Service Table Relocation 도 무력화 시킬수 있는 방법이 있습니다. 즉 , 모든 쓰레드에 대하여 전역적인 SSDT후킹이 가능해지는것이죠. 그 방법은 KiFastCallEntry를 인라인 후킹하는 방법입니다. (뭐 잘 아시는분들이 수두룩할겁니다. 전혀 새로운 방법이 아니죠) KiFastCallEntry는  유저레벨에서  시스템콜을 호출해서  Ring Transaction이 이루어져서 커널레벨로 진입한뒤 , 제일 처음 호출되는 함수라는 의미에서 Entry라는 이름이 붙여진것같네요. 질질 끌지않고 바로 핵심내용을 살펴보면 KiFastCallEntry 함수 내부의 코드를 살펴보면 아래와 같은 내용이 있습니다.


805435bf 8bf8            mov     edi,eax
805435c1 c1ef08          shr     edi,8
805435c4 83e730          and     edi,30h
805435c7 8bcf            mov     ecx,edi
805435c9 03bee0000000    add     edi,dword ptr [esi+0E0h]
805435cf 8bd8            mov     ebx,eax
805435d1 25ff0f0000      and     eax,0FFFh
805435d6 3b4708          cmp     eax,dword ptr [edi+8]
805435d9 0f8333fdffff    jae     nt!KiBBTUnexpectedRange (80543312)
805435df 83f910          cmp     ecx,10h
805435e2 751b            jne     nt!KiFastCallEntry+0xcf (805435ff)
805435e4 648b0d18000000  mov     ecx,dword ptr fs:[18h]
805435eb 33db            xor     ebx,ebx
805435ed 0b99700f0000    or      ebx,dword ptr [ecx+0F70h]
805435f3 740a            je      nt!KiFastCallEntry+0xcf (805435ff)
805435f5 52              push    edx
805435f6 50              push    eax
805435f7 ff1548e75580    call    dword ptr [nt!KeGdiFlushUserBatch (8055e748)]
805435fd 58              pop     eax
805435fe 5a              pop     edx
805435ff 64ff0538060000  inc     dword ptr fs:[638h]
80543606 8bf2            mov     esi,edx
80543608 8b5f0c          mov     ebx,dword ptr [edi+0Ch]
8054360b 33c9            xor     ecx,ecx
8054360d 8a0c18          mov     cl,byte ptr [eax+ebx]
80543610 8b3f            mov     edi,dword ptr [edi]
80543612 8b1c87          mov     ebx,dword ptr [edi+eax*4]
80543615 2be1            sub     esp,ecx
80543617 c1e902          shr     ecx,2
8054361a 8bfc            mov     edi,esp
8054361c 3b3534415680    cmp     esi,dword ptr [nt!MmUserProbeAddress (80564134)]
80543622 0f83a8010000    jae     nt!KiSystemCallExit2+0x9f (805437d0)
80543628 f3a5            rep movs dword ptr es:[edi],dword ptr [esi]
8054362a ffd3            call    ebx

 제일 윗쪽의 진하게 표시된 부분을 보시면 ,  ESI + 0x0E0h 를 한다음 그것을 DWORD만큼 참조해서 연산을 수행하고 있습니다. 805435c9  에 EIP가 왔을때 , EDI의 값이 어떤지는 라이브 디버깅을 해보지않아서 현재로썬 모르겠지만, 여기서 ESI는 PKTHREAD일것이 분명할것이고 , 0x0E0는   ServiceTable 멤버의 Offset이라는거죠.   그다음 진한부분을 보시면  EDI 레지스터가 지니고있는 포인터를 DWORD만큼 참조하여 , EDI로 넣고 있습니다. EIP가 80543610에 왔을때 ,  EDI레지스터는  KeServiceDescriptorTable 또는 KeServiceDescriptorTableShadow의 값을 가지고 있을겁니다. 해당 라인이 수행되고나면 , EDI는 KiServiceTable 또는 W32pServiceTable 의 값을 가지게 될것입니다. 다음줄에서 EDI+EAX*4 연산을 수행하게되면, EBX는   KiServiceTable(또는 W32pServiceTable)  + (Service Number * 4) 의 값을 가지게 될것입니다. 즉 실제 Native 시스템콜이 구현되어져 있는부분의 주소값을 EBX가 가지게 된다는 말이죠. 그리고나서 맨 아랫줄에서 call ebx를 통하여 실제 시스템콜을 호출합니다. 그럼 여기서 핵심은 80543610 , 80543612인데, EIP가 80543610에 왔을때 EDI값은  KTHREAD의 ServiceTable 멤버를 참조하는것이 분명할것이기 때문에 , 이 부분을 인라인 후킹해주면 될것입니다. 후킹 함수는 다음과 같이 작성해볼 수 있겠죠.


__declspec(naked) VOID hookproc()
{
_asm
{
mov edi, dword ptr [edi]
cmp edi, OriginalSSDT
je RelocationMySSDT
cmp edi, OriginalSSDTShadow
je RelocationMySSDTShadow
jmp CalculateSystemCall
RelocationMySSDT:
mov edi, MyHookSSDT
jmp CalculateSystemCall
RelocationMySSDTShadow:
mov edi, MyHookSSDTShadow
jmp CalculateSystemCall
CalculateSystemCall:
mov ebx, dword ptr [edi+eax*4]
retn
}
}

위 naked 함수는 이미 특정 커널 드라이버에 의해서 Service Table Relocation 되버린 쓰레드를 걸러내는 기능을 하지는 못하고, 원본 SSDT, SSDTShadow 를 참조하는 쓰레드를 대상으로 전역적인 후킹이 가능하게끔 구현되어져 있지만,
PKTHREAD(KeGetCurrentThread)의  Win32Thread를 참조로하여 ,  그것의 TEST연산 결과를 이용하여(단순 GUI쓰레드 판별) , 그것이 Relocation되버린 쓰레드이건 , 커널의 서비스 테이블을 참조하고있는 쓰레드이건 상관없이, 강제적인 SSDT훅 , 또는 SDTShadow훅을 걸어버리게끔 코드 작성을 생각해볼 수 있습니다. 
 이렇게 하면 KTHREAD 단에서 ServiceTable의 멤버를 바꿔서 Relocation을 수행한다고 해도, KiFastCallEntry에서 Init을 수행하는 과정에서 후킹을 해버리기 때문에  , 모든 쓰레드에 대하여 전역적으로 후킹이 가능해질 것입니다. (사실 실제로 구현해보지는 않았습니다만, 될것으로 확신합니다.)  "KiFastCallEntry 후킹 + 시스템콜 (인라인)후킹"  을 써먹으면 매우 강력한 방법이 될것입니다.
저작자 표시 비영리 변경 금지
신고
by Sone 2010.09.12 07:00
win32k.sys는 기본적으로 Paged 영역이고,
Current Thread가 GUI Thread일때 올라오는것으로 알고있다.
그래서 SSDT Shadow를 후킹하기 위해서는 , 
Current Thread가  System Thread가 아닌,  GUI Thread일때만 가능한것으로 알고있다.

최근 SSDT Shadow를 후킹하기 위해서 여러가지 방법을 찾아보던 중,
Csrss의 PID를 얻어온 뒤, KeAttachProcess한 뒤에,  SSDT Shadow를 훅 하는 방법이 있길래 코드로 적용해보았다.

사실 Csrss가 어떤 역할을 하는지, 또 KeAttachProcess를 왜 하는지 이 2가지를 아직 잘 모르곘는데,
아마 KeAttachProcess는 Current Thread를 GUi Thread로 만들기 위해서 하는것으로 보여지는데...확실치는 않다.
일단 원리는 나중에 습득하기로 하고 , 코드부터 적용을 해보았다.

짱깨가 짜놓은 코드를 약간 변형해서 짰는데...

if(!strncmp(ImageFileName, "csrss", 5) )
{
CsrssPid = PsGetCurrentProcessId();
if(!CsrssPid)
{
DbgPrint("Get Pid Failed!\n");
return;
}

if(AttachCsrssProcess())
{
DbgPrint("Attach Process Success!\n");
SSDTShadowHook();
KeDetachProcess();
//DbgPrint("KeDetachProcess Success!\n");
return;
}
else
DbgPrint("Attach Process Failed!\n");
}


그런데 ,  아직 커널 후킹에는 미숙한터라, 
Native Service를 후킹해서 Csrss를 걸러내기에는 좀 애메한점이 많아서,
하는수없이 MyKiFastCallEntry에다가 심어놓기로 했다. 
물론 IA32_SYSENTER_EIP도 후킹해놓아야 한다. (SSDT Shadow 후킹하려고 Sysenter까지 후킹을....-_-;;)

__declspec(naked) void MyKiFastCallEntry()
_asm 
pushad 
pushfd 
mov ecx, 0x23 
push 0x30 
pop fs 
mov ds, cx 
mov es, cx 

//
push edx
push eax
call IsTargetProcess
//
popfd 
popad 
jmp [d_orgKiFastCallEntry] 


IsTargetProcess 함수 내부에다가  위의 Csrss를 걸러내는 코드를 작성하였는데, 

이상하게 윈도7에서는 잘되는 반면, 윈XP에서는  KeAttachProcess에서  ntstatus.h에 정의되지않은 알수없는  NTSTATUS를 뿜으면서 BSOD가 뜨는걸 확인하였다.
구글링을 해봤지만 , 답변을 찾지못하고 MSDN을 살펴보았는데....
KeAttachProcess를 보니  다음과 같은 문장만 달랑 있다.

The KeAttachProcess routine is exported to support existing driver binaries and is obsolete. Use KeStackAttachProcess instead.



그냥 핵심은.....
KeAttachProcess는 구식이다!  KeStackAttachProcess를 사용해라!
이말이다.


왜 구식인지는 잘 모르겠다.  (아시는분은 가르쳐주시면 캄사하겠습니다.)
어쨌든 구식이라니 , 뭔가 마음에 걸려서  KeAttachProcess와 KeDetachProcess를   
KeStackAttachProcess,  KeUnstackDetachProcess로  대체하였다.


그 결과는..........
매우 잘된다!

PS. 아, 뒤늦게 생각났는데 PsSetCreateThreadNotifyRoutine에다가 등록을 해두고 PKTHREAD의 Win32Thread를 체킹하게끔하면 될것같네요. 해보진 않았지만.....


PS2.  유저 영역의  특정 쓰레드에서  USER/GDI 관련 API를 한번 호출한다음(ex. FindWindow) ,  DeviceIoControl을 통해서 드라이버에 후킹 신호를 보내서,  SSDTShadow를 따내도 된다고 하는군요...USER/GDI API를 호출하면  win32k가 올라오기 때문에 그런듯하네요.
저작자 표시 비영리 변경 금지
신고
by Sone 2010.07.29 18:24
Arab Team 4 Reverse Engineering  에서 Kernel Detective라는 툴을 새롭게 내놓았습니다.
사실 배포된지는 꽤 됐죠.




기능을 살펴보면 , 

- 프로세스,쓰레드 정보 조회( 숨겨진 정보까지 탐지 가능 )
- KPROCESS , KTHREAD, 조회 가능
-  KeServiceDescriptorTable , KeServiceDescriptorTableShadow 변경 여부 조회 가능
- 쓰레드별로 , SSDT , SSDT SHADOW  Change , Restore 가능 ( SSDT Relocation 탐지 가능)
- IDT 조회 가능
- 핸들 조회&닫기 가능
- Kernel Modification 탐지 가능
- Driver 조회&언로드 가능
- 올리디버거 디스어셈블 엔진 내장 (모듈, 드라이버, 쓰레드, 프로세스,  특정 어드레스의 디스어셈블 가능, 실시간 Read/Write 지원)
- DebugView 지원


이건 뭐 딱 봐도 기능들이 초강력한것을 알수 있습니다.
커널모드로 작동하는 왠만한 보안프로그램은 저리가라 수준이네요.
역시나 창과 방패의 대결은 영원히 지속되는군요...(이번엔 방패측에서 조금 고생할수도....)




위 스샷은  카스퍼스키 안티 바이러스 2010  필터 드라이버에서  SSDT와  SSDT SHADOW 를 후킹하고있는것을 탐지한 모습입니다.
네이트온 원격제어로  카스퍼스키 화면을 컨트롤하려고 했을때 , 화면 조작이 되지않는 이유가 , 바로 SSDT Shadow를 후킹하고있기 때문이죠~

저작자 표시 비영리 변경 금지
신고
by Sone 2010.01.23 00:15
시험도 끝났고 , 본격적으로 삽질의 삽질 연구를 이리저리 거듭하고 공부해볼까~  하던 중
갑자기 생각나는 그 무언가가 있었으니.......

뭐 엄청난건 아니고 ,  간단한건데 그냥 개인적으로 매우 궁금했던터라.....
바로 시작해보았다.


모 온라인게임 보안프로그램에서 어떤 방식으로 SDT 함수들을 후킹해서 다른 악의적인 프로그램의 접근을 막을까, 궁금했던터라 한번 살펴보기로 했다.
결과적으로는 의외로 KiServiceTable 에서 후킹하고있는 함수의 목록은 그다지 많지 않았다.

먼저 서비스테이블을 한번 살펴보자.
이상하게도  눈으로만 일단 보면  보안프로그램에서 후킹을 하고있는 서비스는 아무것도 없어보인다.

0: kd> $$><myscript\kiservicetable.txt
80506460  805a6614 nt!NtAcceptConnectPort
80506464  805f2aea nt!NtAccessCheck
80506468  805f6320 nt!NtAccessCheckAndAuditAlarm
8050646c  805f2b1c nt!NtAccessCheckByType
80506470  805f635a nt!NtAccessCheckByTypeAndAuditAlarm
80506474  805f2b52 nt!NtAccessCheckByTypeResultList
80506478  805f639e nt!NtAccessCheckByTypeResultListAndAuditAlarm
8050647c  805f63e2 nt!NtAccessCheckByTypeResultListAndAuditAlarmByHandle
80506480  806173ce nt!NtAddAtom
80506484  80618110 nt!NtSetBootOptions
80506488  805edee8 nt!NtAdjustGroupsToken
8050648c  805edb40 nt!NtAdjustPrivilegesToken
80506490  805d6b48 nt!NtAlertResumeThread
80506494  805d6af8 nt!NtAlertThread
80506498  806179f4 nt!NtAllocateLocallyUniqueId
8050649c  805b7f80 nt!NtAllocateUserPhysicalPages
805064a0  80617010 nt!NtAllocateUuids
805064a4  805aaa9e nt!NtAllocateVirtualMemory
805064a8  805b2594 nt!NtAreMappedFilesTheSame
805064ac  805d860c nt!NtAssignProcessToJobObject
805064b0  8050389c nt!NtCallbackReturn
805064b4  80618102 nt!NtCancelDeviceWakeupRequest
805064b8  80578ae6 nt!NtCancelIoFile
805064bc  8053abe2 nt!NtCancelTimer
805064c0  806105de nt!NtClearEvent
805064c4  805be4fa nt!NtClose
805064c8  805f685a nt!NtCloseObjectAuditAlarm
805064cc  80625382 nt!NtCompactKeys
805064d0  805fad6a nt!NtCompareTokens
805064d4  805a6d02 nt!NtCompleteConnectPort
805064d8  806255d6 nt!NtCompressKey
805064dc  805a65b4 nt!NtConnectPort
805064e0  80546e7c nt!NtContinue
805064e4  80643ea8 nt!NtCreateDebugObject
805064e8  805c04aa nt!NtCreateDirectoryObject
805064ec  8061062e nt!NtCreateEvent
805064f0  80618986 nt!NtCreateEventPair
805064f4  8057b084 nt!NtCreateFile
805064f8  8057aa62 nt!NtCreateIoCompletion
805064fc  805d75d0 nt!NtCreateJobObject
80506500  805d7308 nt!NtCreateJobSet
80506504  806257b2 nt!NtCreateKey
80506508  8057b192 nt!NtCreateMailslotFile
8050650c  80618d7e nt!NtCreateMutant
80506510  8057b0be nt!NtCreateNamedPipeFile
80506514  805ad9d2 nt!NtCreatePagingFile
80506518  805a70d0 nt!NtCreatePort
8050651c  805d31fa nt!NtCreateProcess
80506520  805d3144 nt!NtCreateProcessEx
80506524  8061919e nt!NtCreateProfile
80506528  805ad3ac nt!NtCreateSection
8050652c  8061672e nt!NtCreateSemaphore
80506530  805c59c4 nt!NtCreateSymbolicLinkObject
80506534  805d2fe2 nt!NtCreateThread
80506538  8061864e nt!NtCreateTimer
8050653c  805fb112 nt!NtCreateToken
80506540  805a70f4 nt!NtCreateWaitablePort
80506544  80644f84 nt!NtDebugActiveProcess
80506548  806450d4 nt!NtDebugContinue
8050654c  80618052 nt!NtDelayExecution
80506550  80617884 nt!NtDeleteAtom
80506554  80618102 nt!NtCancelDeviceWakeupRequest
80506558  80578c2c nt!NtDeleteFile
8050655c  80625c42 nt!NtDeleteKey
80506560  805f6966 nt!NtDeleteObjectAuditAlarm
80506564  80625e12 nt!NtDeleteValueKey
80506568  8057b24a nt!NtDeviceIoControlFile
8050656c  806146ac nt!NtDisplayString
80506570  805bffd2 nt!NtDuplicateObject
80506574  805eed96 nt!NtDuplicateToken
80506578  80618110 nt!NtSetBootOptions
8050657c  80625ff2 nt!NtEnumerateKey
80506580  806180f4 nt!NtEnumerateSystemEnvironmentValuesEx
80506584  8062625c nt!NtEnumerateValueKey
80506588  805b5ca0 nt!NtExtendSection
8050658c  805eef42 nt!NtFilterToken
80506590  80617638 nt!NtFindAtom
80506594  80578cf8 nt!NtFlushBuffersFile
80506598  805b8814 nt!NtFlushInstructionCache
8050659c  806264c6 nt!NtFlushKey
805065a0  805ae6e6 nt!NtFlushVirtualMemory
805065a4  805b87b6 nt!NtFlushWriteBuffer
805065a8  805b8322 nt!NtFreeUserPhysicalPages
805065ac  805b4f7c nt!NtFreeVirtualMemory
805065b0  8057b27e nt!NtFsControlFile
805065b4  805d34f4 nt!NtGetContextThread
805065b8  805ca64e nt!NtGetDevicePowerState
805065bc  8059b116 nt!NtGetPlugPlayEvent
805065c0  8052318a nt!NtGetWriteWatch
805065c4  805faa5e nt!NtImpersonateAnonymousToken
805065c8  805a715e nt!NtImpersonateClientOfPort
805065cc  805d97cc nt!NtImpersonateThread
805065d0  80623908 nt!NtInitializeRegistry
805065d4  805ca434 nt!NtInitiatePowerAction
805065d8  805d71cc nt!NtIsProcessInJob
805065dc  805ca63a nt!NtIsSystemResumeAutomatic
805065e0  805a736a nt!NtListenPort
805065e4  8058613a nt!NtLoadDriver
805065e8  806279ae nt!NtLoadKey
805065ec  806275ba nt!NtLoadKey2
805065f0  8057b2b2 nt!NtLockFile
805065f4  80614c9e nt!NtLockProductActivationKeys
805065f8  80625682 nt!NtLockRegistryKey
805065fc  805b891c nt!NtLockVirtualMemory
80506600  805c02a0 nt!NtMakePermanentObject
80506604  805be59e nt!NtMakeTemporaryObject
80506608  805b73e0 nt!NtMapUserPhysicalPages
8050660c  805b7930 nt!NtMapUserPhysicalPagesScatter
80506610  805b4004 nt!NtMapViewOfSection
80506614  80618102 nt!NtCancelDeviceWakeupRequest
80506618  8057beca nt!NtNotifyChangeDirectoryFile
8050661c  80627978 nt!NtNotifyChangeKey
80506620  806265c8 nt!NtNotifyChangeMultipleKeys
80506624  805c057c nt!NtOpenDirectoryObject
80506628  8061072e nt!NtOpenEvent
8050662c  80618a5e nt!NtOpenEventPair
80506630  8057c182 nt!NtOpenFile
80506634  8057ab3a nt!NtOpenIoCompletion
80506638  805d7756 nt!NtOpenJobObject
8050663c  80626b84 nt!NtOpenKey
80506640  80618e56 nt!NtOpenMutant
80506644  805f6428 nt!NtOpenObjectAuditAlarm
80506648  805cd40a nt!NtOpenProcess
8050664c  805ef730 nt!NtOpenProcessToken
80506650  805ef394 nt!NtOpenProcessTokenEx
80506654  805ac3d0 nt!NtOpenSection
80506658  80616828 nt!NtOpenSemaphore
8050665c  805c5baa nt!NtOpenSymbolicLinkObject
80506660  805cd696 nt!NtOpenThread
80506664  805ef74e nt!NtOpenThreadToken
80506668  805ef504 nt!NtOpenThreadTokenEx
8050666c  80618770 nt!NtOpenTimer
80506670  80647176 nt!NtPlugPlayControl
80506674  805cb4bc nt!NtPowerInformation
80506678  805f9b10 nt!NtPrivilegeCheck
8050667c  805f573a nt!NtPrivilegeObjectAuditAlarm
80506680  805f5926 nt!NtPrivilegedServiceAuditAlarm
80506684  805ba3e8 nt!NtProtectVirtualMemory
80506688  806107e6 nt!NtPulseEvent
8050668c  80578ed6 nt!NtQueryAttributesFile
80506690  80618110 nt!NtSetBootOptions
80506694  80618110 nt!NtSetBootOptions
80506698  80541bc6 nt!NtQueryDebugFilterState
8050669c  806123d8 nt!NtQueryDefaultLocale
805066a0  80613038 nt!NtQueryDefaultUILanguage
805066a4  8057be64 nt!NtQueryDirectoryFile
805066a8  805c061c nt!NtQueryDirectoryObject
805066ac  8057c1b2 nt!NtQueryEaFile
805066b0  806108ae nt!NtQueryEvent
805066b4  8057902a nt!NtQueryFullAttributesFile
805066b8  806178ac nt!NtQueryInformationAtom
805066bc  8057ca1e nt!NtQueryInformationFile
805066c0  805d7c28 nt!NtQueryInformationJobObject
805066c4  805a73c8 nt!NtQueryInformationPort
805066c8  805cef5e nt!NtQueryInformationProcess
805066cc  805cdb8c nt!NtQueryInformationThread
805066d0  805ef82e nt!NtQueryInformationToken
805066d4  806127d6 nt!NtQueryInstallUILanguage
805066d8  80619620 nt!NtQueryIntervalProfile
805066dc  8057abe2 nt!NtQueryIoCompletion
805066e0  80626eaa nt!NtQueryKey
805066e4  80624900 nt!NtQueryMultipleValueKey
805066e8  80618efe nt!NtQueryMutant
805066ec  805c7296 nt!NtQueryObject
805066f0  80624fac nt!NtQueryOpenSubKeys
805066f4  806196ae nt!NtQueryPerformanceCounter
805066f8  8057d800 nt!NtQueryQuotaInformationFile
805066fc  805ba5aa nt!NtQuerySection
80506700  805c2064 nt!NtQuerySecurityObject
80506704  806168e0 nt!NtQuerySemaphore
80506708  805c5c4a nt!NtQuerySymbolicLinkObject
8050670c  8061812c nt!NtQuerySystemEnvironmentValue
80506710  806180e6 nt!NtSetSystemEnvironmentValueEx
80506714  806130b8 nt!NtQuerySystemInformation
80506718  80614878 nt!NtQuerySystemTime
8050671c  80618828 nt!NtQueryTimer
80506720  8061490a nt!NtQueryTimerResolution
80506724  806239ea nt!NtQueryValueKey
80506728  805bac38 nt!NtQueryVirtualMemory
8050672c  8057dcea nt!NtQueryVolumeInformationFile
80506730  805d3240 nt!NtQueueApcThread
80506734  80546ec4 nt!NtRaiseException
80506738  80616552 nt!NtRaiseHardError
8050673c  8057e48a nt!NtReadFile
80506740  8057e9f4 nt!NtReadFileScatter
80506744  805a7e50 nt!NtReadRequestData
80506748  805b628c nt!NtReadVirtualMemory
8050674c  805d4762 nt!NtRegisterThreadTerminatePort
80506750  80619036 nt!NtReleaseMutant
80506754  80616a10 nt!NtReleaseSemaphore
80506758  8057aeda nt!NtRemoveIoCompletion
8050675c  80645054 nt!NtRemoveProcessDebug
80506760  806251d4 nt!NtRenameKey
80506764  8062785e nt!NtReplaceKey
80506768  805a74d0 nt!NtReplyPort
8050676c  805a8498 nt!NtReplyWaitReceivePort
80506770  805a7ea0 nt!NtReplyWaitReceivePortEx
80506774  805a77ba nt!NtReplyWaitReplyPort
80506778  805ca5cc nt!NtRequestDeviceWakeup
8050677c  805a4a2e nt!NtRequestPort
80506780  805a4d5a nt!NtRequestWaitReplyPort
80506784  805ca3da nt!NtRequestWakeupLatency
80506788  806109c0 nt!NtResetEvent
8050678c  80523672 nt!NtResetWriteWatch
80506790  8062716a nt!NtRestoreKey
80506794  805d6aa2 nt!NtResumeProcess
80506798  805d6984 nt!NtResumeThread
8050679c  80627266 nt!NtSaveKey
805067a0  8062734c nt!NtSaveKeyEx
805067a4  80627474 nt!NtSaveMergedKeys
805067a8  805a5d48 nt!NtSecureConnectPort
805067ac  80618110 nt!NtSetBootOptions
805067b0  80618110 nt!NtSetBootOptions
805067b4  805d3704 nt!NtSetContextThread
805067b8  80647d0c nt!NtSetDebugFilterState
805067bc  806163fc nt!NtSetDefaultHardErrorPort
805067c0  80612528 nt!NtSetDefaultLocale
805067c4  80612d9a nt!NtSetDefaultUILanguage
805067c8  8057c6c6 nt!NtSetEaFile
805067cc  80610a80 nt!NtSetEvent
805067d0  80610b4a nt!NtSetEventBoostPriority
805067d4  80618d1a nt!NtSetHighEventPair
805067d8  80618c4a nt!NtSetHighWaitLowEventPair
805067dc  80644a1e nt!NtSetInformationDebugObject
805067e0  8057d010 nt!NtSetInformationFile
805067e4  805d8936 nt!NtSetInformationJobObject
805067e8  806244cc nt!NtSetInformationKey
805067ec  805c680c nt!NtSetInformationObject
805067f0  805cfe54 nt!NtSetInformationProcess
805067f4  805ce0d8 nt!NtSetInformationThread
805067f8  805fbe8c nt!NtSetInformationToken
805067fc  80619182 nt!NtSetIntervalProfile
80506800  8057ae78 nt!NtSetIoCompletion
80506804  805d58ce nt!NtSetLdtEntries
80506808  80618cb6 nt!NtSetLowEventPair
8050680c  80618bde nt!NtSetLowWaitHighEventPair
80506810  8057d7de nt!NtSetQuotaInformationFile
80506814  805c25f8 nt!NtSetSecurityObject
80506818  806183b0 nt!NtSetSystemEnvironmentValue
8050681c  806180e6 nt!NtSetSystemEnvironmentValueEx
80506820  806113e6 nt!NtSetSystemInformation
80506824  80654e18 nt!NtSetSystemPowerState
80506828  80615b80 nt!NtSetSystemTime
8050682c  805ca2ee nt!NtSetThreadExecutionState
80506830  8053ad72 nt!NtSetTimer
80506834  80615052 nt!NtSetTimerResolution
80506838  80616ec6 nt!NtSetUuidSeed
8050683c  80623d38 nt!NtSetValueKey
80506840  8057e0f4 nt!NtSetVolumeInformationFile
80506844  80614670 nt!NtShutdownSystem
80506848  80528768 nt!NtSignalAndWaitForSingleObject
8050684c  806193cc nt!NtStartProfile
80506850  80619576 nt!NtStopProfile
80506854  805d6a4c nt!NtSuspendProcess
80506858  805d68be nt!NtSuspendThread
8050685c  8061979a nt!NtSystemDebugControl
80506860  805d94ca nt!NtTerminateJobObject
80506864  805d49ac nt!NtTerminateProcess
80506868  805d4ba6 nt!NtTerminateThread
8050686c  805d6c0c nt!NtTestAlert
80506870  80537108 nt!NtTraceEvent
80506874  8061811e nt!NtTranslateFilePath
80506878  805862ce nt!NtUnloadDriver
8050687c  80624062 nt!NtUnloadKey
80506880  8062427c nt!NtUnloadKeyEx
80506884  8057b656 nt!NtUnlockFile
80506888  805b8eaa nt!NtUnlockVirtualMemory
8050688c  805b4e12 nt!NtUnmapViewOfSection
80506890  805fd244 nt!NtVdmControl
80506894  80644786 nt!NtWaitForDebugEvent
80506898  805c27ae nt!NtWaitForMultipleObjects
8050689c  805c26c4 nt!NtWaitForSingleObject
805068a0  80618b7a nt!NtWaitHighEventPair
805068a4  80618b16 nt!NtWaitLowEventPair
805068a8  8057eef2 nt!NtWriteFile
805068ac  8057f4d6 nt!NtWriteFileGather
805068b0  805a7e78 nt!NtWriteRequestData
805068b4  805b6396 nt!NtWriteVirtualMemory
805068b8  80506ae8 nt!NtYieldExecution
805068bc  80619bf2 nt!NtCreateKeyedEvent
805068c0  80619cdc nt!NtOpenKeyedEvent
805068c4  80619d8e nt!NtReleaseKeyedEvent
805068c8  80619fea nt!NtWaitForKeyedEvent
805068cc  805cd90c nt!NtQueryPortInformationProcess

스크롤의 압박이 있지만 , 깨끗하다는걸 증명하기 위해서는....어쩔수가 없다
아주 깨끗하지 않은가?
이건 뭐  SSDT 후킹을 하고있나?  라고 의심할수밖에 없을정도로.....깨끗한 테이블~~

이때까지만 해도  아~   KTHREAD의  ServiceTable의 값을 바꿔서 새로만든 ServiceTable로 연결 시켜서
SSDT Relocation 기법을 쓰는구나  싶었다.
하지만 그것도 아니었다.

   +0x0d8 UserAffinity     : 3
   +0x0dc SystemAffinityActive : 0 ''
   +0x0dd PowerState       : 0 ''
   +0x0de NpxIrql          : 0 ''
   +0x0df InitialNode      : 0 ''
   +0x0e0 ServiceTable     : 0x8055e700 
   +0x0e4 Queue            : (null) 
   +0x0e8 ApcQueueLock     : 0
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY [ 0x0 - 0x0 ]


0: kd> dds KeServiceDescriptorTable
8055e700  80506460 nt!KiServiceTable
8055e704  00000000
8055e708  0000011c
8055e70c  805068d4 nt!KiArgumentTable


Relocation도 아니면 뭐지?  생각하다가
결국은  내부 함수 후킹일 것 같네?  라는 생각이 들어서...
그래서 보통 대부분의 온라인 게임 보안 프로그램에서는 NtOpenProcess는 무조건 따내겠지? 하는 생각으로
NtOpenProcess 내부에서 호출하고있는 함수들의 목록을 살펴보기로 했다.

0: kd> uf /c nt!NtOpenProcess
nt!NtOpenProcess (805cd40a)
  nt!NtOpenProcess+0xa (805cd414):
    call to EagleNT+0xa9e0 (b1f3d9e0)
  nt!NtOpenProcess+0x4e (805cd458):
    call to nt!ExRaiseDatatypeMisalignment (80616092)
  nt!NtOpenProcess+0x7c (805cd486):
    call to nt!ExRaiseDatatypeMisalignment (80616092)
  nt!NtOpenProcess+0x116 (805cd520):
    call to nt!SeCreateAccessState (805f2dc6)
  nt!NtOpenProcess+0x132 (805cd53c):
    call to nt!SeSinglePrivilegeCheck (805f9ce0)
  nt!NtOpenProcess+0x17d (805cd587):
    call to nt!ObOpenObjectByName (805bd8f4)
  nt!NtOpenProcess+0x18b (805cd595):
    call to nt!SeDeleteAccessState (805f2b88)
  nt!NtOpenProcess+0x1e2 (805cd5ec):
    call to nt!PsLookupProcessThreadByCid (805d5030)
  nt!NtOpenProcess+0x1f4 (805cd5fe):
    call to nt!SeDeleteAccessState (805f2b88)
  nt!NtOpenProcess+0x202 (805cd60c):
    call to nt!PsLookupProcessByProcessId (805d50ec)
  nt!NtOpenProcess+0x224 (805cd62e):
    call to nt!ObOpenObjectByPointer (805bdc7a)
  nt!NtOpenProcess+0x232 (805cd63c):
    call to nt!SeDeleteAccessState (805f2b88)
  nt!NtOpenProcess+0x23e (805cd648):
    call to nt!ObfDereferenceObject (8052868e)
  nt!NtOpenProcess+0x246 (805cd650):
    call to nt!ObfDereferenceObject (8052868e)
  nt!NtOpenProcess+0x27e (805cd688):
    call to nt!_SEH_epilog (8053dbcb)



뷁 ,  NtOpenProcess가 호출되자마자  바로  다른곳을 호출하고 있음을 볼수있다.

더 확실히 보기 위해서


0: kd> u nt!NtOpenProcess
nt!NtOpenProcess:
805cd40a 68c4000000      push    0C4h
805cd40f 68c0c44d80      push    offset nt!ObWatchHandles+0x25c (804dc4c0)
805cd414 e8c7059731      call    EagleNT+0xa9e0 (b1f3d9e0)
805cd419 33f6            xor     esi,esi
805cd41b 8975d4          mov     dword ptr [ebp-2Ch],esi
805cd41e 33c0            xor     eax,eax
805cd420 8d7dd8          lea     edi,[ebp-28h]
805cd423 ab              stos    dword ptr es:[edi]


NtOpenProcess가 호출되자마자 바로 보안 드라이버의 함수를 실행한다.
이런 방식으로 후킹 해버리면  SSDT Restore나 Hook을 해도 마찬가지가 되버리기 때문에
알려진 공격방법에 대해서는 자유롭게 되는것이다.

NtOpenProcess 앞부분을 살펴보면
0xC4 와    ObWatchHandles+0x25C 를 PUSH 하면서  후킹함수를 호출하고 있다.
ObWatchHandles는  dd  로 살펴보니 HandleList  개념일것 같은데 ,  자세히는 모르겠다.

아마도 다른  서비스함수들 내부에도 이런식으로 여러개 따냈을것이 분명하기 때문에,
무식한방법으로 스크립트를 동원해서 내부를 싸그리 뒤져보기로 했다.

스크립트는 아래와 같이 작성하였다. (0x470은   Windows XP SP3 기준)

r $t0 = 0
r $t1 = 0
.for(r $t0 = 0; (@$t0 <= 470); r $t0 = @$t0 + 0x04)
{
      r $t1 = @$t0 + nt!KiServiceTable
      uf -c poi(@$t1)
}

그래서 결과를 추려내었더니 , 후킹하는 함수들의 목록은 아래와 같았다.

nt!NtClose (805be4fa)
  nt!NtClose+0x18 (805be512):
    call to EagleNT+0xa910 (b1f3d910)

>NtClose를 자세히 살펴보면 다음과 같다.
0: kd> u nt!NtClose
nt!NtClose:
805be4fa 8bff            mov     edi,edi
805be4fc 55              push    ebp
805be4fd 8bec            mov     ebp,esp
805be4ff 64a124010000    mov     eax,dword ptr fs:[00000124h]
805be505 0fbe8040010000  movsx   eax,byte ptr [eax+140h]
805be50c 6a00            push    0
805be50e 50              push    eax
805be50f ff7508          push    dword ptr [ebp+8]
805be512 e8f9f39731      call    EagleNT+0xa910 (b1f3d910)
805be517 5d              pop     ebp
805be518 c20400          ret     4
엥? 이게 끝인가?  NtClose는 정말 간단하게 되있군.




nt!NtDeviceIoControlFile (8057b24a)
  nt!NtDeviceIoControlFile+0x25 (8057b26f):
    call to EagleNT+0xa7d0 (b1f3d7d0)

>NtDeviceIoControlFile을 자세히 살펴보면 다음과 같다.
0: kd> u nt!NtDeviceIoControlFile
nt!NtDeviceIoControlFile:
8057b24a 8bff            mov     edi,edi
8057b24c 55              push    ebp
8057b24d 8bec            mov     ebp,esp
8057b24f 6a01            push    1
8057b251 ff752c          push    dword ptr [ebp+2Ch]
8057b254 ff7528          push    dword ptr [ebp+28h]
8057b257 ff7524          push    dword ptr [ebp+24h]
8057b25a ff7520          push    dword ptr [ebp+20h]
8057b25d ff751c          push    dword ptr [ebp+1Ch]
8057b260 ff7518          push    dword ptr [ebp+18h]
8057b263 ff7514          push    dword ptr [ebp+14h]
8057b266 ff7510          push    dword ptr [ebp+10h]
8057b269 ff750c          push    dword ptr [ebp+0Ch]
8057b26c ff7508          push    dword ptr [ebp+8]
8057b26f e85c259c31      call    EagleNT+0xa7d0 (b1f3d7d0)
8057b274 5d              pop     ebp
8057b275 c22800          ret     28h



nt!NtOpenProcess (805cd40a)
  nt!NtOpenProcess+0xa (805cd414):
    call to EagleNT+0xa9e0 (b1f3d9e0)

>NtOpenProcess는 위에서 보았으니 생략



nt!NtReadVirtualMemory (805b628c)
  nt!NtReadVirtualMemory+0x7 (805b6293):
    call to EagleNT+0xac40 (b1f3dc40)

>NtReadVirtualMemory를 자세히 보자
0: kd> u nt!NtReadVirtualMemory
nt!NtReadVirtualMemory:
805b628c 6a1c            push    1Ch
805b628e 68f0be4d80      push    offset nt!MmClaimParameterAdjustDownTime+0x90 (804dbef0)
805b6293 e8a8799831      call    EagleNT+0xac40 (b1f3dc40)
805b6298 64a124010000    mov     eax,dword ptr fs:[00000124h]
805b629e 8bf8            mov     edi,eax
805b62a0 8a8740010000    mov     al,byte ptr [edi+140h]
805b62a6 8845e0          mov     byte ptr [ebp-20h],al
805b62a9 8b7514          mov     esi,dword ptr [ebp+14h]


nt!NtWriteVirtualMemory (805b6396)
  nt!NtWriteVirtualMemory+0x7 (805b639d):
    call to EagleNT+0xad90 (b1f3dd90)


>NtWriteVirtualMemory
0: kd> u nt!NtWriteVirtualMemory
nt!NtWriteVirtualMemory:
805b6396 6a1c            push    1Ch
805b6398 6808bf4d80      push    offset nt!MmClaimParameterAdjustDownTime+0xa8 (804dbf08)
805b639d e8ee799831      call    EagleNT+0xad90 (b1f3dd90)
805b63a2 64a124010000    mov     eax,dword ptr fs:[00000124h]
805b63a8 8bf8            mov     edi,eax
805b63aa 8a8740010000    mov     al,byte ptr [edi+140h]
805b63b0 8845e0          mov     byte ptr [ebp-20h],al
805b63b3 8b7514          mov     esi,dword ptr [ebp+14h]





덧글의  TaeHwa 님의 도움으로 아래 내용 추가



 KiServiceTable에 있지않은  함수중에서  KiAttachProcess  라는 녀석이 있다.
이녀석은 이래저래 많이 사용되고 아주 민감한 녀석인데  이 녀석까지도  따내버린걸 확인할 수 있었다.
0: kd> uf /c nt!KiAttachProcess
nt!KiAttachProcess (804faa08)
  nt!KiAttachProcess+0x19 (804faa21):
    call to EagleNT+0xa3f0 (b1f3d3f0)
  nt!KiAttachProcess+0x77 (804faa7f):
    call to nt!KiReadyThread (80505124)
  nt!KiAttachProcess+0x89 (804faa91):
    call to nt!KiSwapProcess (80547ca0)
  nt!KiAttachProcess+0x91 (804faa99):
    call to nt!KiUnlockDispatcherDatabase (805478a8)
  nt!KiAttachProcess+0xe0 (804faae8):
    call to nt!KiSetSwapEvent (804fa5de)
  nt!KiAttachProcess+0xe8 (804faaf0):
    call to hal!KeAcquireQueuedSpinLockRaiseToSynch (806e8a3c)
  nt!KiAttachProcess+0x10c (804fab14):
    call to hal!KeReleaseQueuedSpinLock (806e8aa8)
  nt!KiAttachProcess+0x118 (804fab20):
    call to nt!KiSwapThread (805057bc)

0: kd> u nt!KiAttachProcess
nt!KiAttachProcess:
804faa08 8bff            mov     edi,edi
804faa0a 55              push    ebp
804faa0b 8bec            mov     ebp,esp
804faa0d 53              push    ebx
804faa0e 56              push    esi
804faa0f 8b7508          mov     esi,dword ptr [ebp+8]
804faa12 57              push    edi
804faa13 ff7514          push    dword ptr [ebp+14h]
804faa16 8b7d0c          mov     edi,dword ptr [ebp+0Ch]
804faa19 66ff4760        inc     word ptr [edi+60h]
804faa1d 8d5e34          lea     ebx,[esi+34h]
804faa20 53              push    ebx
804faa21 e8ca29a431      call    EagleNT+0xa3f0 (b1f3d3f0)
804faa26 895b04          mov     dword ptr [ebx+4],ebx
804faa29 891b            mov     dword ptr [ebx],ebx
804faa2b 8d463c          lea     eax,[esi+3Ch]




또한 

0: kd> uf /c nt!PsSuspendThread
nt!PsSuspendThread (805d6790)
  nt!PsSuspendThread+0x7 (805d6797):
    call to EagleNT+0xaee0 (b1f3dee0)
  nt!PsSuspendThread+0x22 (805d67b2):
    call to nt!KeSuspendThread (804ff4f8)
  nt!PsSuspendThread+0x52 (805d67e2):
    call to nt!ExAcquireRundownProtection (8060e3fa)
  nt!PsSuspendThread+0x6a (805d67fa):
    call to nt!KeSuspendThread (804ff4f8)
  nt!PsSuspendThread+0xa0 (805d6830):
    call to nt!KeForceResumeThread (804ff010)
  nt!PsSuspendThread+0xb4 (805d6844):
    call to nt!ExReleaseRundownProtection (8060e454)
  nt!PsSuspendThread+0xce (805d685e):
    call to nt!_SEH_epilog (8053dbcb)


0: kd> u nt!PsSuspendThread
nt!PsSuspendThread:
805d6790 6a18            push    18h
805d6792 68e8c94d80      push    offset nt!ObWatchHandles+0x784 (804dc9e8)
805d6797 e844779631      call    EagleNT+0xaee0 (b1f3dee0)
805d679c 33f6            xor     esi,esi
805d679e 8975e4          mov     dword ptr [ebp-1Ch],esi
805d67a1 64a124010000    mov     eax,dword ptr fs:[00000124h]
805d67a7 8b7d08          mov     edi,dword ptr [ebp+8]
805d67aa 3bf8            cmp     edi,eax

SuspendThread를 후킹하는것은  아마 유저모드 보안모듈의 실행흐름을 Suspend 시키는걸 방지하기 위해서 따내는것 같다.




결과적으로  현재까지 확인된 결과는
NtClose
NtDeviceIoControlFile
NtOpenProcess
NtWriteVirtualMemory
NtReadVirtualMemory
KiAttachProcess
PsSuspendThread




모듈 이름이 다 나와있긴하지만 , 대놓고 공개적으로 이름을 거론하긴 좀 뭐해서 , 모 보안 프로그램이라고 지칭하였다.
시간을 두고 살펴볼 필요가 있을것 같다.
저작자 표시 비영리 변경 금지
신고
by Sone 2009.12.21 03:39


#include <ntddk.h>

VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
 __asm{
  cli           //Clear Interrupt
  push eax                  //save eax
  mov eax, cr0
  and eax,0xfffeffff  //11111111111111101111111111111111  즉 , WP 비트를 의도적으로 0으로 세트함.
  mov cr0, eax
  pop eax                   //restore eax
 }
 DbgPrint("Control Reg 0 WP Clear");



//  훅을  원래대로 돌려놓는 코드를 작성



 __asm{
  push eax           //save eax
  mov eax, cr0
  or eax, NOT 0xfffeffff     //00000000000000010000000000000000 WP 비트를 1로 세팅함.
  mov cr0, eax
  pop eax             //restore eax
  sti               //Set Interrupt
 }
 DbgPrint("Control Reg 0  WP Set");
}



NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
      IN PUNICODE_STRING RegistryPath)
{
 DriverObject->DriverUnload=Unload;
 __asm{
  cli
  push eax                  //save eax
  mov eax, cr0
  and eax,0xfffeffff  //11111111111111101111111111111111  즉 , WP 비트를 의도적으로 0으로 세트함.
  mov cr0, eax
  pop eax                   //restore eax
 }
 DbgPrint("Control Reg 0 WP Clear");



//원하는 Hooking Code 작성



 __asm{
  push eax           //save eax
  mov eax, cr0
  or eax, NOT 0xfffeffff     //00000000000000010000000000000000 WP 비트를 1로 세팅함.
  mov cr0, eax
  pop eax             //restore eax
  sti
 }
DbgPrint("Control Reg 0  WP Set");
 return STATUS_SUCCESS;
}


위 코드는 각종 블로그  , 인터넷 사이트 등에 알려진지 꽤 오래 된 코드이다.


보통 SSDT, SSDTShadow, KiFastCallEntry 등 에 접근하여 쓰기작업을 수행하고자 할때 ,  (후킹할 때)
먼저 CR0 레지스터의 Write Protection 비트에 접근하여, WP를 해제시켜주어야 한다.
해제시켜주지 않으면 , 공포의 Blue Screen Of Death 를 맛보게 될것이니...
왜냐면 SSDT 영역은 기본적으로 읽기전용 영역으로 지정되어져 있다.
그런데  WP비트를 해제시켜주지않고  , 거기에 쓰기 작업을 시도하게 되면 , 당연히 예외가 발생하는것이다.

cr0에 값을 쓰기전에 인터럽트를 해제시켜주는 이유는 ,   인터럽트 서비스 루틴에 의해서 CR0값이 변경될 가능성이 있기 때문이다.

보통 인터럽트의 IRQL은  일반 커널 쓰레드의 PASSIVE_LEVEL보다 높은 값을 지니기때문에 , 언제든지 CPU를 선점할수 있게된다.

저작자 표시 비영리 변경 금지
신고
by Sone 2009.08.22 16:17
| 1 |