When it comes to working with memory of process, it comes handy to have information about whole address space of process, to do not touch PAGE_GUARD, knowing exec and writable pages, etc.
For that purpose i already implemented VadWalker in my kernel common repo, and also use it in DbiFuzz frmwrk. But recently i come accross some ideas, how to improve my recent approach and do it more efficiently and kinda smarter.
VadRoot is in fact simple AVL-tree, and AVL struct is commonly used accross windows kernel. For working in AVL-style in kernel come msdn with some support :
- RtlInsertElementGenericTableAvl
- RtlDeleteElementGenericTableAvl
- RtlNumberGenericTableElementsAvl
- RtlGetElementGenericTableAvl
- RtlLookupFirstMatchingElementGenericTableAvl
- …
For iterating this RTL_AVL_TABLE struct, i implement simple bstree methods, which i then used in AVL.hpp (LockedContainers.hpp), and same time i use this methods for walking trough VadRoot itself. But this approach leads to implement more and more logic, for iterating through VadRoot, so i start thinking about another method to not over-engineering myself too much..
At first some words about VadRoot AVL-tree struct. It seems that it is some kind of intrusive mechanism, which insert { parent, left, right } links into MMVAD_SHORT. Same time it have to be sanitized parent link, because is used method for storing additional info about node in last unused part of pointer [ sanitizatoin mask = ~sizeof(void*) ].
And it sounds familiar, do not ? boost::intrusive::avltree do same job already!
Problem can be that per different m$ versions it can be node-pointers stored at different offsets,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
//new 7 type of VAD typedef struct _MMVAD_SHORT { union { LONG32 Balance : 2; struct _MMVAD* Parent; }u1; struct _MMVAD* LeftChild; struct _MMVAD* RightChild; ULONG32 StartingVpn; ULONG32 EndingVpn; union { ULONG32 LongFlags; struct _MMVAD_FLAGS VadFlags; }u; struct _EX_PUSH_LOCK PushLock; union { ULONG32 LongFlags3; struct _MMVAD_FLAGS3 VadFlags3; }u5; } MMVAD_SHORT, *PMMVAD_SHORT; //http://msdn.moonsols.com/win7rtm_x86/MMVAD_SHORT.html //old XP type typedef struct _MMVAD_SHORT { ULONG32 StartingVpn; ULONG32 EndingVpn; struct _MMVAD* Parent; struct _MMVAD* LeftChild; struct _MMVAD* RightChild; union { ULONG32 LongFlags; struct _MMVAD_FLAGS VadFlags; }u; } MMVAD_SHORT, *PMMVAD_SHORT; //http://msdn.moonsols.com/winxpsp2_x86/MMVAD_SHORT.html |
but ofc that can be handled by boost :
1 2 3 4 5 6 7 8 |
struct CMMVadShort : private MMVAD_SHORT { public: ... avl_set_member_hook<> MMADDRESS_NODE; ... } |
when it comes to sanitizing in VadRoot algo, then boost come with – optimize_size<true>, which means that algo take care less about performance and more about size of data :
1 2 3 4 5 6 7 8 9 10 11 |
struct CMMVadShort : private MMVAD_SHORT { public: ... avl_set_member_hook< optimize_size<true> > MMADDRESS_NODE; ... } typedef member_hook< CMMVadShort, avl_set_member_hook< optimize_size<true> >, &CMMVadShort::MMADDRESS_NODE > VadMemberOption; typedef avltree< CMMVadShort, VadMemberOption > VadTree; |
Now is *almost* solved problem with iterating trough VadRoot (getnext, getprev) and same time with classic functionality a.k.a. find, lower_bound…
Almost consist of two parts.
- 1. Still is problem, with creating header for avltree, as startpoint for iterating / finding algos
It can be solved, but not in clean way, in fact it is bit ohack. But on the other side, usage of VadRoot itself is ohack also … So key point is to create temporary boost::intrusive::avltree and insert inside dummy node, with NULL Vpn’s and redirect its pointer-links to VadRoot itself!
Here is bit of sample :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
class CDummyVad : public boost::noncopyable { public: CDummyVad() { CMMVadShort* dummy_vad = reinterpret_cast<CMMVadShort*>(&m_dummyStack); MMVAD_SHORT* vs = *dummy_vad; vs->StartingVpn() = NULL; vs->EndingVpn() = NULL; //set our decoy m_vadTree.insert_unique(*reinterpret_cast<CMMVadShort*>(m_dummyStack)); } //do not forget to null dummy links, to not clean VadRoot's one! ~CDummyVad() { CMMVadShort* dummy_vad = reinterpret_cast<CMMVadShort*>(&m_dummyStack); dummy_vad->MMADDRESS_NODE.right_ = nullptr; dummy_vad->MMADDRESS_NODE.parent_ = nullptr; } __checkReturn void RelinkToVadRoot( __in const CMMVadShort& vad_root ) { CMMVadShort* dummy_vad = reinterpret_cast<CMMVadShort*>(&m_dummyStack); dummy_vad->MMADDRESS_NODE.right_ = vad_root.MMADDRESS_NODE.right_; dummy_vad->MMADDRESS_NODE.parent_ = vad_root.MMADDRESS_NODE.parent_; } __checkReturn bool RelinkToVadRoot() { EPROCESS* eprocess = reinterpret_cast<EPROCESS*>(PsGetCurrentProcess()); CMMVadShort* vad_root = reinterpret_cast<CMMVadShort*>(eprocess->VadRoot()); if (!vad_root) return false; RelinkToVadRoot(*vad_root); return true; } private: void* m_dummyStack[0x10]; VadTree m_vadTree; }; |
- 2. Next issue is with locking. For safe accessing VadRoot itself is necessary to locks AddressSapce & WorkingSet of process, mark thread which holding locks. And in addition, also MMVAD_SHORT nodes itselfs use locking mechanism for working with them, so go deeper
In my PoC i provide simple locked wrapper around VadRoot, and wraps functions { Contains, GetMemory, Find }. Which provide all necessary locking mechanism.
In addition Find resolve iterator represent targeted MMVAD_SHORT, and holds locks during work with this object.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
class CVAdScanner : public boost::noncopyable { public: ... static __checkReturn bool Find( __in const CRange<void*>& mem, __inout std::shared_ptr<CVadNode>* out ) { CDummyVad dummy_vad; CVadNode* out_vadnode = nullptr; { CVADLock<CSharedLockWorker> lock; if (!lock.IsLocked()) return false; if (!dummy_vad.RelinkToVadRoot()) return false; VadTree::iterator it = dummy_vad->find(mem, CMMVadShortCmp()); if (dummy_vad->end() == it) return false; MMVAD_SHORT* c_mmvad = *it; out_vadnode = new CVadNode(*it); } //outside of spinlock playing with std::shared_ptr if (!out_vadnode) return false; *out = std::shared_ptr<CVadNode>(out_vadnode); return true; } ... } class CVadNode : public boost::noncopyable, private CSharedLockWorker { public: ... CVadNode* operator++() { //implement findNext, unlock current / lock new one, change m_vad to new one } ... } |
So thats it, boost::intrusive seems can help in a lot of cases to simplify designs and needs for reinveinting wheels (like i already did in my previous approach), so lets boosting bit of kernel
simple usage :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
CRange<void*> mem(nullptr); if (CVAdScanner::GetMemory((void*)0x77000000, &mem)) { DbgPrint("\nfound memory : %p %p\n", mem.Begin(), mem.End()); std::shared_ptr<CVadNode> sp_vad_node; if (!CVAdScanner::Find(mem, &sp_vad_node)) return; for (int i = 0; i < 0x10; i++) { ++(*sp_vad_node.get()); DbgPrint("\na. %p ITERATE : %p %p\n", sp_vad_node.get(), sp_vad_node->Begin(), sp_vad_node->End()); } } |
When i would have a time i will merge it in my kernel common repo, but meanwhile you can find some classes here
nice work