요즘에는 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

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력해주세요.

이전의 악성코드 포스팅에서 대부분의 악의적인 목적으로 작성되는 악성코드는 Binder종류의 프로그램을 이용해서
Fake 목적의 파일과 결합되어진 상태로 배포된다고 했었는데,

여기서 수많은 BInder종류의 프로그램 들 중, 한가지를 소개할까 합니다.

Arab Team 4 Reverse Engineering 에서 내놓은 JOINER라는 프로그램인데,
심플하면서도 강력한 기능들을 갖추고 있습니다.

기본적으로는 실행가능한(EXE)  파일 2개를 선택해서 Merge 시킬 수 있는 기능을 갖추고 있습니다.
(아쉽게도 그림파일을 첨부할 수 있는 기능은 없군요.)
여기에 부가 옵션으로 UPX패킹 옵션을 넣을 수 있고, Melt라는 옵션은 정확히 아직 어떠한 역할을 수행하는것인지 잘 모르겠네요.
결합된 파일의 아이콘을 지정할 수 있는 기능도 갖추고 있습니다.




이제 각종 고급옵션을 살펴보면,

* 파일 속성 지정 가능
* 압축이 풀리는 경로 지정 가능
* 실행 순서 지정가능
* 각종 보호 옵션 (Anti Debugger , Anti VMWare , Anti Virtual PC , Anti WireShark , Anti JoeBox , Anto SandBox , Anti ThreathExpert,  시스템 복원 중지 , 작업 관리자 실행 차단)

이건 뭐 프로그램 제작자가 , "어떠한 목적으로 사용하던간에 사용에 따른 결과는 너네들이 책임져라" 
라고 간접적으로 말하고 있네요.

저도 이쯤에서 다시한번 말합니다.
USE AT YOUR OWN RISK!!!

저작자 표시 비영리 변경 금지
신고

'Reverse Engineering' 카테고리의 다른 글

C-Decompiler v1.1 Demo  (1) 2010.04.04
flo`s Notepad2 4.1.24  (1) 2010.04.04
AT4RE JOINER 4.0  (5) 2010.04.04
넥슨 온라인게임 악성코드에 관한 분석  (89) 2010.03.25
W32Dasm 10.0  (0) 2010.02.04
Kernel Detective 1.3.1  (5) 2010.01.23
by Sone 2010.04.04 14:36
| 1 |