在前面的文章《驱动开发:运用MDL映射实现多次通信》
LyShark教大家使用MDL
的方式灵活的实现了内核态多次输出结构体的效果,但是此种方法并不推荐大家使用原因很简单首先内核空间比较宝贵,其次内核里面不能分配太大且每次传出的结构体最大不能超过1024
个,而最终这些内存由于无法得到更好的释放从而导致坏堆的产生,这样的程序显然是无法在生产环境中使用的,如下LyShark
将教大家通过在应用层申请空间来实现同等效果,此类传递方式也是多数ARK反内核工具中最常采用的一种。
与MDL映射相反,MDL多数处理流程在内核代码中,而应用层开堆复杂代码则在应用层,但内核层中同样还是需要使用指针,只是这里的指针仅仅只是保留基本要素即可,通过EnumProcess()
模拟枚举进程操作,传入的是PPROCESS_INFO
进程指针转换,将数据传入到PPROCESS_INFO
直接返回进程计数器即可。
// -------------------------------------------------
// R3传输结构体
// -------------------------------------------------
// 进程指针转换
typedef struct
{
DWORD PID;
DWORD PPID;
}PROCESS_INFO, *PPROCESS_INFO;
// 数据存储指针
typedef struct
{
ULONG_PTR nSize;
PVOID BufferPtr;
}BufferPointer, *pBufferPointer;
// 模拟进程枚举
ULONG EnumProcess(PPROCESS_INFO pBuffer)
{
ULONG nCount = 0;
for (size_t i = 0; i < 10; i++)
{
pBuffer[i].PID = nCount * 2;
pBuffer[i].PPID = nCount * 4;
nCount = nCount + 1;
}
return nCount;
}
内核层核心代码: 内核代码中是如何通信的,首先从用户态接收pIoBuffer
到分配的缓冲区数据,并转换为pBufferPointer
结构,ProbeForWrite
用于检查地址是否可写入,接着会调用EnumProcess()
注意传入的其实是应用层的指针,枚举进程结束后,将进程数量nCount
通过*(PULONG)pIrp->AssociatedIrp.SystemBuffer = (ULONG)nCount
回传给应用层,至此内核中仅仅回传了一个长度,其他的都写入到了应用层中。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
pBufferPointer pinp = (pBufferPointer)pIoBuffer;
__try
{
DbgPrint("缓冲区长度: %d \n", pinp->nSize);
DbgPrint("缓冲区基地址: %p \n", pinp->BufferPtr);
// 检查地址是否可写入
ProbeForWrite(pinp->BufferPtr, pinp->nSize, 1);
ULONG nCount = EnumProcess((PPROCESS_INFO)pinp->BufferPtr);
DbgPrint("进程计数 = %d \n", nCount);
if (nCount > 0)
{
// 将进程数返回给用户
*(PULONG)pIrp->AssociatedIrp.SystemBuffer = (ULONG)nCount;
status = STATUS_SUCCESS;
}
}
__except (1)
{
status = GetExceptionCode();
DbgPrint("IOCTL_GET_EPROCESS %x \n", status);
}
// 返回通信状态
status = STATUS_SUCCESS;
break;
应用层核心代码: 通信的重点在于应用层,首先定义BufferPointer
用于存放缓冲区头部指针,定义PPROCESS_INFO
则是用于后期将数据放入该容器内,函数HeapAlloc
分配一段堆空间,并HEAP_ZERO_MEMORY
将该堆空间全部填空,将这一段初始化后的空间放入到pInput.BufferPtr
缓冲区内,并计算出长度放入到pInput.nSize
缓冲区内,一切准备就绪之后,再通过DriveControl.IoControl
将BufferPointer
结构传输至内核中,而bRet
则是用于接收返回长度的变量。
当收到数据后,通过(PPROCESS_INFO)pInput.BufferPtr
强制转换为指针类型,并依次pProcessInfo[i]
读出每一个节点的元素,最后是调用HeapFree
释放掉这段堆空间。至于输出就很简单了vectorProcess[x].PID
循环容器元素即可。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
// 应用层数据结构体数据
BOOL bRet = FALSE;
BufferPointer pInput = { 0 };
PPROCESS_INFO pProcessInfo = NULL;
// 分配堆空间
pInput.BufferPtr = (PVOID)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PROCESS_INFO) * 1000);
pInput.nSize = sizeof(PROCESS_INFO) * 1000;
ULONG nRet = 0;
if (pInput.BufferPtr)
{
bRet = DriveControl.IoControl(IOCTL_IO_R3StructAll, &pInput, sizeof(BufferPointer), &nRet, sizeof(ULONG), 0);
}
std::cout << "返回结构体数量: " << nRet << std::endl;
if (bRet && nRet > 0)
{
pProcessInfo = (PPROCESS_INFO)pInput.BufferPtr;
std::vector<PROCESS_INFO> vectorProcess;
for (ULONG i = 0; i < nRet; i++)
{
vectorProcess.push_back(pProcessInfo[i]);
}
// 释放空间
bRet = HeapFree(GetProcessHeap(), 0, pInput.BufferPtr);
std::cout << "释放状态: " <<