基于SSDT的注册表主防系统
注:这个主防只支持x86系统。。本人技术略渣,x64还没搞出来。。。。
注注:这篇文章是很久以前发在旧博客里的,无意间翻到了觉得还不错,复制过来了,毕竟也是用心做的东西。
这个SSDT的内容我用了之前的一个框架,网上也有很多的SSDT库。关于SSDT还不太清楚的童鞋可以看看我以前的一篇文章。
http://lightlesshome.diandian.com/post/2014-01-29/40060870666
主要就是一个主防的思路问题。我的思路比较简单,很多方面都没有考虑到,用ZwSetKeyValue这个函数来开刀。
首先肯定是要HOOK掉这个函数。之后就要考虑ring0和ring3交互的问题。这里采用的是DeviceIoControl和IOCTL的方法来进行交互的。
首先ring0进行监听,然后得到监听到事件后向ring3发出一个信号(暂且用信号这个词,其实是通知ring3),然后ring3收到通知后,向ring0请求信息,ring0会把一系列的东西传递给ring3,ring3进行处理后弹框并询问用户是否允许修改。
我参考了下这篇文章的方法,在数据量不大的时候并且不要求效率的时候可以考虑采用下面这个模型。
http://www.cnblogs.com/dflower/archive/2009/04/17/1438066.html
IOCTL控制码的话主要定义了以下几个。
// 设置事件
#define IOCTL_SETEVENT \
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x831, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 向ring0 请求数据
#define IOCTL_GETINFO \
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x832, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 设置等待用户判断的事件
#define IOCTL_SETWAITEVENT \
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x833, METHOD_BUFFERED, FILE_ANY_ACCESS)
// 传递用户的判断结果
#define IOCTL_PASSUSERRES \
(ULONG)CTL_CODE(FILE_DEVICE_UNKNOWN, 0x834, METHOD_BUFFERED, FILE_ANY_ACCESS)
用途稍后会进行解释。
关于这个DeviceIoControl函数的用法可以参考下MSDN,比较简单,主要就是向ring0发送个控制码,然后获取数据之类的啥啥啥的。
在ring0中用这个函数。NTSTATUS DeviceIoControlDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP pIrp);
然后设置以下派遣函数
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControlDispatch;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceIoControlDispatch;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DeviceIoControlDispatch;
下面要在这个函数中获取IOCTL功能码
PIO_STACK_LOCATION irpStack;
irpStack = IoGetCurrentIrpStackLocation(pIrp);
irpStack->Parameters.DeviceIoControl.IoControlCode
那个IoControlCode就是功能码了。
下面就是继续switch case 来进行处理每一个功能码了。
基本上DeviceIoControlDispatch就是这样了。下面来考虑怎么在ring0和ring3中互相通知。
可以在ring3中创建一个事件,然后单独开一个线程来监视这个事件。开线程之前使用DeviceIoControl函数将这个事件的句柄传递到内核中,ring0收到这个事件后,转为句柄并保存起来,在以后需要使用的时候拿出来使用。
在ring3中:
先打开我们的驱动,其中的"\\.\RegDef"是驱动中的符号链接。
m_hDevice = CreateFile("\\\\.\\RegDef", GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL);
然后创建事件
m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
下面将事件句柄传递到ring0中。
DeviceIoControl(m_hDevice, IOCTL_SETEVENT, &m_hEvent, sizeof(HANDLE),
NULL, 0, &m_returnBytes, NULL);
然后开启一个新线程用来监视这个Event。我这里ring3使用的MFC,比较方便一点。
AfxBeginThread(EventThread, this);
UINT CRegDefDlg::EventThread(LPVOID pParam)
{
CRegDefDlg *pThis = (CRegDefDlg *)pParam;
DWORD dwReturned = 0;
int i = 0;
char szBuf[512];
char szName[128];
char szKey[128];
char szSubKey[128];
CString strTemp;
while (true)
{
WaitForSingleObject(pThis->m_hEvent, INFINITE);
DeviceIoControl(pThis->m_hDevice, IOCTL_GETINFO, NULL, 0, &pThis->m_OutBuffer, 1024, &dwReturned, NULL);
//AfxMessageBox(pThis->m_OutBuffer);
strTemp.Empty();
strTemp.Format("%s", pThis->m_OutBuffer);
AfxMessageBox(strTemp);
ZeroMemory(szBuf, 512);ZeroMemory(szName, 128);ZeroMemory(szKey, 128);ZeroMemory(szSubKey, 128);
// sscanf(pThis->m_OutBuffer, "%[]", szName, szKey, szSubKey);
int i = 0;
int j = 0;
while (pThis->m_OutBuffer[i] != '|')
{
szName[j] = pThis->m_OutBuffer[i];
i++;j++;
}
i += 1;j = 0;
while (pThis->m_OutBuffer[i] != '|')
{
szKey[j] = pThis->m_OutBuffer[i];
i++;j++;
}
i += 1;j = 0;
while (pThis->m_OutBuffer[i] != '|')
{
szSubKey[j] = pThis->m_OutBuffer[i];
i++;j++;
}
sprintf(szBuf, "Process Name: %s\r\nRegistry key: %s\r\nSubkey: %s\r\nWould you want this process to change the registyr?", szName, szKey, szSubKey);
int t = pThis->MessageBox(szBuf,"Warning!", MB_YESNO);
if (t == IDYES)
{
// 1表示允许修改;
pThis->UserRes = 1;
DeviceIoControl(pThis->m_hDevice, IOCTL_PASSUSERRES, &pThis->UserRes, 1, NULL, 0, &dwReturned, NULL);
}
else
{
// 0表示不允许修改
pThis->UserRes = 0;
DeviceIoControl(pThis->m_hDevice, IOCTL_PASSUSERRES, &pThis->UserRes, 1, NULL, 0, &dwReturned, NULL);
}
SetEvent(pThis->m_hWaitUserRequestEvent);
ResetEvent(pThis->m_hEvent);
}
return 0;
}
写完后考虑了下用一个线程来一直监听可能会有点蛋疼。可能会出现各种bug。
里面主要的东西就是那个while(true)循环,在这个循环里,首先要进行WatiForSingleObject来等待事件的发生。一旦等待完成,就要马上发送IOCTL_GETINFO来获取信息,(这里定义的IOCTL_GETINFO控制码是用来向ring0获取信息的),然后中间一大堆字符串处理的东西请忽视。pThis->UserRes用来表示用户是否允许修改,如果允许的话就置为1,不允许就置为0,然后将这个参数传递到ring0中,这里用到的控制码是IOCTL_PASSUSERRES。之后要ResetEvent(),不然的话会挂掉。
下面来看ring0中的处理,首先要获取ring3传下来的事件句柄,并保存下来。下面这段代码在DeviceIoControlDispatch中。
case IOCTL_SETEVENT:
KdPrint(("IOCTL_SETEVENT called."));
pInBuffer = pIrp->AssociatedIrp.SystemBuffer;
pInbufferSize = irpStack->Parameters.DeviceIoControl.InputBufferLength;
if (pInBuffer == NULL || pInbufferSize < sizeof(HANDLE))
{
KdPrint(("[DeviceIoControlDispatch] Set event error!\n"));
status = STATUS_INVALID_BUFFER_SIZE;
break;
}
hEvent = *(HANDLE*)pInBuffer;
KdPrint(("hEvent: %x\n", hEvent));
status = ObReferenceObjectByHandle(hEvent,
GENERIC_ALL,
NULL,
KernelMode,
(PVOID*)&gpEventObject,
objHandleInfo);
KdPrint(("gpEventObject: %x\n",gpEventObject));
break;
之前定义IOCTL的时候用的是BUFFER_IO方式,这里就用buffer来获取数据。关键就是这个ObReferenceObjectByHandle,之前一直是倒数第二个参数没加&符号,蓝屏了无数次。。。
hEvent是一个HANDLE类型。然后gpEventObject是PRKEVENT类型的全局变量,用于保存这个事件句柄,objHandleInfo是全局POBJECT_HANDLE_INFORMATION类型,不过好像在某些地方看到这个参数用NULL也可以。没有试验过,童鞋们可以自己尝试。这样这个句柄就保存下来了,在需要通知应用层事件的地方加上
KeSetEvent(gpEventObject, 0, FALSE);
这时ring3就会通过DeviceIoControl向驱动层请求数据。这个方法好像在竹林蹊径上也看到。
通信问题解决了之后就是考虑我们自己的HookZwSetValueKey的问题。
首先就是检测修改的注册表路径,如果是不允许修改的路径,比如启动项,那么就阻塞住ring0,并向ring3发送信息申请是否可以修改。因为同时有N多个进程会使用注册表,所以要先用一个互斥体来保证资源。随后调用KeSetEvent和KeClearEvent来通知上层应用,传递好进程的信息之后,就要KeWaitForSingleObject 来等待另一个事件,目的是等待上层应用的确认,基本上就是这样了。
完整代码请移步:https://github.com/LightIce/RegistryDefence/