Here's a bit of code I wrote to stealth memory blocks thru VirtualQueryEx hooking (VirtualQuery calls VQE, so you can just detour VQE).
It ain't pretty, but it works. By "ain't pretty" I mean -- I ignore all sorts of return codes, I do a brute force search for the previous block, and right now it only stealths one block at a time; I assume anyone who's competent enough to understand the code is competent enough to make it pretty and safe.
It works by merging blocks; if the block being searched for is the block before your "stealth" block, and it's free, this returns the actual results plus the size of the stealth block. If the block being searched for is the stealth block, the fields are set to indicate that it's free. If the next block is free, its size is added to the size of the stealth block (and possibly also the previous block).
Note that because of the way VQE works, you can't search "backwards" in memory without a walk from zero to the address in question (otherwise region sizes/bases will be wrong). This makes this detour inefficient for frequent VQE's, but for occasional hits (like, say, Warden might do... which is why I was asking about what it does these days, which led to being called a retard
) it should be fine.
Note that this is far from uncounterable; I can count off at least two or three ways that Warden could counter this, in the future.
Still, imperfect as it is, it's a start. I searched the forums to see if this was prior art, but I didn't see it anywhere. It may be overkill, but if you coupled this with hooks on VirtualAlloc and the lovely PEB LDR DLL-hiding code floating around here on the forums, it should make it even less likely that Warden will see your code caves/injected DLL's.
Code:
MEMORY_BASIC_INFORMATION stealthBlock; // obviously you have to provide this
bool AddressIsIn(PMEMORY_BASIC_INFORMATION pMBI, DWORD dwAddx)
{
return (dwAddx >= (DWORD)pMBI->BaseAddress) && (dwAddx < (DWORD)pMBI->BaseAddress + pMBI->RegionSize);
}
bool GetPreviousRegion(PMEMORY_BASIC_INFORMATION pCurrentMBI, PMEMORY_BASIC_INFORMATION pPrev)
{
// HACKHACK: sanity check params
DWORD dwAddx = 0;
MEMORY_BASIC_INFORMATION mbi;
HANDLE hCurProc = GetCurrentProcess();
while (pVQE_Trampoline(hCurProc, (LPCVOID)dwAddx, &mbi, sizeof(MEMORY_BASIC_INFORMATION)))
{
dwAddx = (DWORD)mbi.BaseAddress + mbi.RegionSize;
if (dwAddx >= (DWORD)pCurrentMBI->BaseAddress) // HACKHACK: doesn't account for exceptional cases
{
memcpy(pPrev, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
return true;
}
}
return false;
}
void StealthMBI(LPCVOID lpAddress, PMEMORY_BASIC_INFORMATION pMBI)
{
if (pMBI)
{
MEMORY_BASIC_INFORMATION next;
HANDLE hCurProc = GetCurrentProcess();
//GetPreviousRegion(&stealthBlock, &prev); // HACKHACK: should check for failure
pVQE_Trampoline(hCurProc, (LPCVOID)((DWORD)stealthBlock.BaseAddress + stealthBlock.RegionSize), &next, sizeof(MEMORY_BASIC_INFORMATION));
if (AddressIsIn(&stealthBlock, (DWORD)pMBI->BaseAddress + pMBI->RegionSize) && (pMBI->State == MEM_FREE)) // if the next block is stealth block, we're in the block before stealth (duh)
{
// we're querying the block BEFORE the stealth block
pMBI->RegionSize += stealthBlock.RegionSize; // add the stealth block size to this (previous) block size
if (next.State == MEM_FREE) // now, if the NEXT block is free too...
{
pMBI->RegionSize += next.RegionSize; // add its size to the pool
}
}
else if (AddressIsIn(&stealthBlock, (DWORD)lpAddress))
{
// we're querying the stealthed block
pMBI->AllocationBase = 0;
pMBI->AllocationProtect = 0;
pMBI->State = MEM_FREE;
pMBI->Protect = PAGE_NOACCESS;
pMBI->Type = 0;
if (next.State == MEM_FREE)
{
// the next block is free too, so we should merge the stealthed block and the next block
pMBI->RegionSize += next.RegionSize;
}
}
}
}
SIZE_T WINAPI VirtualQueryEx_Detour(
__in HANDLE hProcess,
__in_opt LPCVOID lpAddress,
__out PMEMORY_BASIC_INFORMATION lpBuffer,
__in SIZE_T dwLength
)
{
SIZE_T result = pVQE_Trampoline(hProcess, lpAddress, lpBuffer, dwLength); // first call the real VQE
if (result && (GetProcessId(hProcess) == GetCurrentProcessId())) // only care about THIS process
{
StealthMBI(lpAddress, lpBuffer);
}
return result;
}
Here's it at work. This is a raw dump of a sequence of MEMORY_BASIC_INFORMATION structures, before and after activation. The target block is a page of code I injected at 0x20000 (splitting the free block at 0x20000).
Note that the block at 0x20000 shows a size of 0x1000 (4096 bytes), and a type of 0x20000 (MEM_PRIVATE).
Code:
ab: 00000000 ap: 00000000 ba: 00000000 pr: 00000001 rs: 00010000 st: 00010000 ty: 00000000
ab: 00010000 ap: 00000004 ba: 00010000 pr: 00000004 rs: 00010000 st: 00001000 ty: 00040000
ab: 00020000 ap: 00000004 ba: 00020000 pr: 00000004 rs: 00001000 st: 00001000 ty: 00020000
ab: 00000000 ap: 00000000 ba: 00021000 pr: 00000001 rs: 0000F000 st: 00010000 ty: 00000000
ab: 00030000 ap: 00000004 ba: 00030000 pr: 00000004 rs: 00003000 st: 00001000 ty: 00020000
And afterwards:
Code:
ab: 00000000 ap: 00000000 ba: 00000000 pr: 00000001 rs: 00010000 st: 00010000 ty: 00000000
ab: 00010000 ap: 00000004 ba: 00010000 pr: 00000004 rs: 00010000 st: 00001000 ty: 00040000
ab: 00000000 ap: 00000000 ba: 00020000 pr: 00000001 rs: 00010000 st: 00010000 ty: 00000000
ab: 00030000 ap: 00000004 ba: 00030000 pr: 00000004 rs: 00003000 st: 00001000 ty: 00020000
Note that now it looks like there's a single free block (type 0) at 0x20000, size 0x10000 -- absorbing the following free block.
Enjoy.