之前曝光了一个利用MmMapViewInSystemSpace往驱动空间所在区域分配内存的外挂
传送门:https://bbs.pediy.com/thread-230129.htm
闲的蛋疼就研究了一下怎么枚举这种方法分配出来的内存。
首先明确一下虚拟内存的分配流程:
vista以上:
MmMapViewInSystemSpace ->MiMapViewInSystemSpace->MiInsertInSystemSpace->MiReserveSystemPtes(&MiSystemPteInfo, PteCount)
(不同系统可能不同,但是总之是预留PTE)
xp:
MmMapViewInSystemSpace ->MiMapViewInSystemSpace->MiInsertInSystemSpace->
从SystemSpaceBitMap找一块满足sizein64k大小的空的bitmap出来,对应到预先分配好的SystemSpaceViewStart里,直接作为分配出的地址
物理内存的分配流程:
刚才分配的PTE present位为0,访问时触发pagefault再分配物理内存,原理和NtMapViewOfSection差不多,都是只管分配VA,访问时#pf才给分配物理内存
这种情况下分配物理内存时不会往MmPfnDatabase中写入pfn对应的pte信息(毕竟一个section可以被多个PTE映射不是么),导致MmGetVirtualForPhysical无法从物理地址反查虚拟地址
我们可以找个有符号的win7看一下:
发现和wrk差不多,直接看wrk。
关键代码
Entry = (ULONG_PTR) MI_64K_ALIGN(Base) + SizeIn64k;
Hash = (ULONG) ((Entry >> 16) % Session->SystemSpaceHashKey);
while (Session->SystemSpaceViewTable[Hash].Entry != 0) {
Hash += 1;
if (Hash >= Session->SystemSpaceHashSize) {
Hash = 0;
}
}
Session->SystemSpaceHashEntries += 1;
Session->SystemSpaceViewTable[Hash].Entry = Entry;
Session->SystemSpaceViewTable[Hash].ControlArea = ControlArea;
那么只要遍历MmSession的SystemSpaceViewTable就能找到所有的va。
entry的算法是这样的:
entry=64k对齐(虚拟地址) + 多少个64k
也就是说entry的高48位是虚拟地址,低16位是大小(指有几个64k)
从分配前的检查也可以看出,不能分配超过(65535=64k-1)个64k,否则超过低16位能保存的大小了。
算出来的entry会简单计算一个hash放到 SystemSpaceViewTable 里。
那么我们遍历这个表就能拿到所有的SystemSpaceView VA和大小了,遍历前记得加锁SystemSpaceViewLockPointer。
PMMSESSION_WIN7 Session = (PMMSESSION_WIN7)MmSession;
for (ULONG i = 0; i < Session->SystemSpaceHashSize; i ++) {
if (Session->SystemSpaceViewTable[i].Entry != 0) {
PVOID BaseAddress = (PVOID)(((Session->SystemSpaceViewTable[i].Entry >> 16) << 16) | 0xFFFF000000000000ull);//Higher 48 bit of Entry is BaseAddress aligned to 64k
SIZE_T ViewSize = (Session->SystemSpaceViewTable[i].Entry & 0xFFFFull) * 0x10000;//lowest 16 bit of Entry is SizeIn64k
//Now you get BaseAddress and ViewSize, ViewSize is count in bytes.
}
}
}
32位系统的区别仅在于高16位是虚拟地址,低16位是大小,其他和64位没有任何区别,因此以上代码甚至只需要把PMMESSION结构换成32位版本就行了。
至于PMMSESSION结构,自己找个有符号的win7自提。
MMSESSION MmSession变量在MmMapViewInSystemSpace入口一个lea rdx就有
以上遍历兼容xp~win8.1
遍历结果:
然后看一下win10的 MiInsertInSystemSpace ,貌似变化有点大,不用hash表而是用红黑树了。
typedef struct _MMSESSION_WIN10
{
EX_PUSH_LOCK SystemSpaceViewLock;
PEX_PUSH_LOCK SystemSpaceViewLockPointer;
PRTL_AVL_TREE ViewRoot;
ULONG ViewCount;
ULONG BitmapFailures;
}MMSESSION_WIN10, *PMMSESSION_WIN10;
ViewRoot这个 RTL_BALANCED_NODE 其实是一个侵入式数据结构,他的大小远不止sizeof( RTL_AVL_NODE )这么一点,而是:
而是有0x60这么大。
遍历这个红黑树需要你有 RTL_BALANCED_NODE 所在完整的结构,我随便逆了一下,只拿了几个重要的成员:
typedef struct __declspec(align(8)) _MMVIEW_WIN10
{
RTL_BALANCED_NODE SectionNode;
ULONG64 Unkown1;
ULONG_PTR ViewSize;
ULONG_PTR Unkown2;
PVOID ControlArea;
PVOID FileObject;
ULONG_PTR Unknown3;
ULONG_PTR Unknown4;
PVOID SessionViewVa;
ULONG Unknown5;
ULONG Unknown6;
}MMVIEW_WIN10, *PMMVIEW_WIN10;
那我们遍历红黑树岂不是写个递归就完事了。
要注意写入VA的时候会遇上一些奇怪的bit,我们要去掉:
void EnumSystemSpaceViewWin10(PMMVIEW_WIN10 view)
{
PVOID BaseAddress = (PVOID)((ULONG_PTR)view->SessionViewVa & (~3));
//now you have
//BaseAddress
//view->ViewSize
PMMVIEW_WIN10 right = (PMMVIEW_WIN10)view->SectionNode.Right;
if (right)
{
EnumSystemSpaceViewWin10(right);
}
PMMVIEW_WIN10 left = (PMMVIEW_WIN10)view->SectionNode.Left;
if (left)
{
EnumSystemSpaceViewWin10(left);
}
}
EnumSystemSpaceViewWin10((PMMVIEW_WIN10)Session->ViewRoot);
记得win10也要加锁,而且锁换成了PUSH_LOCK。
然后你拿这份代码试了一下,发现在systemspaceview非常多的情况下会栈溢出爆炸。
因为递归层数太多了,你需要把递归算法改成循环。
修改过的循环遍历代码由于过于丑陋这里就不放了,类似于这种:(以下代码是网上抄的)
void xunhuanzhongxubianli(BinaryTreeNode * root)
{
stack<BinaryTreeNode*> ss;
if (root == NULL)
{
return;
}
BinaryTreeNode * pRoot = root;
while (pRoot || ss.size() > 0)
{
while (pRoot)
{
ss.push(pRoot);
pRoot = pRoot->m_pLeft;
}
BinaryTreeNode * temp = ss.top();
ss.pop();
cout << temp->m_nValue << " ";
pRoot = temp->m_pRight;
}
}
win10下遍历结果确实有丶多,递归很容易爆栈:
以上,完。
原文作者:hzqst
原文链接:https://bbs.pediy.com/thread-246843.htm
转载请注明:转自看雪学院
看雪阅读推荐:
网友评论