Üye
Giriş
Battleye, EAC gibi güçlü Anti-Cheat sistemleri manual maplenen DLLleri tespit etmek için query memory protection kullanırlar. Whitelist içersine alınmış modüller dışında allocate edilmiş Read/Write pageleri varsa bu process içerisinde manual maplenmiş bir DLL olduğunu gösterir. Bunu geçmenin kolay yolları vardır. Bu RWX pagelerini gizlemek için bizim izleyeceğimiz yollar şunlardır: VAD manipülasyonu ve NX bit değişimi.
NOT: DLL'inizi manual maplemek için bir Driver'a ihtiyacınız var.
RWX detection neden var?
RWX detection var çünkü DLL kodunu çalıştırabilmek için execute iznine ve memory ile birlikte çalışması için read ve write iznine ihtiyacınız var. Execute izinleri zaten kendi başlarına şüphelilerdir çünkü execute izni mevcut olan kodu gösterir.
1) VAD gizlemesi
VAD yani virtual address descriptor (Türkçesi sanal adres tanımlayıcısı) bir processin memory aralıklarının özelliklerini açıklayan Tree Data Structure'dır. VAD'ın kökü EPROCESS->vadRoot içerisinde bulunur. VirtualQuery Virtual Pageler için korumayı belirlemek için VAD'dan bilgi alır. VAD Tree'den geçerseniz ve allocate edilmiş sayfalarınızı tanımlayan VAD node'unun koruma değerini değiştirirseniz VirtualQuery Result ile uğraşabilirsiniz.
2) NX bitini değiştirme
Manual maplenen pageleri gizlemenin başka bir yolu pageleri sadece Read/Write izni ile açmak, allocate ettiğiniz pagelerin PTE'lerini (Page Table Entry) almak ve pageinize coverların altında execute izni eklemektir. Karmaşık gelebilir ama aslında kolay.
Sadece bir Page'in PTE'SİNİ yakalamak için bir fonksiyon yazmanız gerekir. Unexported MiGetPteAddress'i kullanabilirsiniz.
Eğer kafanız karıştıysa Page Table Entry hakkında araştırma yapabilirsiniz.
RWX pagelerini gizlemek için diğer yollar
- DLL'inizi imzalanmış ve packlenmiş bir DLL'de mapleyin.
- Birden fazla Page'i allocate etmeyin.
Yakında
Battleye, EAC gibi güçlü Anti-Cheat sistemleri manual maplenen DLLleri tespit etmek için query memory protection kullanırlar. Whitelist içersine alınmış modüller dışında allocate edilmiş Read/Write pageleri varsa bu process içerisinde manual maplenmiş bir DLL olduğunu gösterir. Bunu geçmenin kolay yolları vardır. Bu RWX pagelerini gizlemek için bizim izleyeceğimiz yollar şunlardır: VAD manipülasyonu ve NX bit değişimi.
NOT: DLL'inizi manual maplemek için bir Driver'a ihtiyacınız var.
RWX detection neden var?
RWX detection var çünkü DLL kodunu çalıştırabilmek için execute iznine ve memory ile birlikte çalışması için read ve write iznine ihtiyacınız var. Execute izinleri zaten kendi başlarına şüphelilerdir çünkü execute izni mevcut olan kodu gösterir.
1) VAD gizlemesi
VAD yani virtual address descriptor (Türkçesi sanal adres tanımlayıcısı) bir processin memory aralıklarının özelliklerini açıklayan Tree Data Structure'dır. VAD'ın kökü EPROCESS->vadRoot içerisinde bulunur. VirtualQuery Virtual Pageler için korumayı belirlemek için VAD'dan bilgi alır. VAD Tree'den geçerseniz ve allocate edilmiş sayfalarınızı tanımlayan VAD node'unun koruma değerini değiştirirseniz VirtualQuery Result ile uğraşabilirsiniz.
Kod:
=
{
PAGE_NOACCESS,
PAGE_READONLY,
PAGE_EXECUTE,
PAGE_EXECUTE_READ,
PAGE_READWRITE,
PAGE_WRITECOPY,
PAGE_EXECUTE_READWRITE,
PAGE_EXECUTE_WRITECOPY,
PAGE_NOACCESS,
PAGE_NOCACHE | PAGE_READONLY,
PAGE_NOCACHE | PAGE_EXECUTE,
PAGE_NOCACHE | PAGE_EXECUTE_READ,
PAGE_NOCACHE | PAGE_READWRITE,
PAGE_NOCACHE | PAGE_WRITECOPY,
PAGE_NOCACHE | PAGE_EXECUTE_READWRITE,
PAGE_NOCACHE | PAGE_EXECUTE_WRITECOPY,
PAGE_NOACCESS,
PAGE_GUARD | PAGE_READONLY,
PAGE_GUARD | PAGE_EXECUTE,
PAGE_GUARD | PAGE_EXECUTE_READ,
PAGE_GUARD | PAGE_READWRITE,
PAGE_GUARD | PAGE_WRITECOPY,
PAGE_GUARD | PAGE_EXECUTE_READWRITE,
PAGE_GUARD | PAGE_EXECUTE_WRITECOPY,
PAGE_NOACCESS,
PAGE_WRITECOMBINE | PAGE_READONLY,
PAGE_WRITECOMBINE | PAGE_EXECUTE,
PAGE_WRITECOMBINE | PAGE_EXECUTE_READ,
PAGE_WRITECOMBINE | PAGE_READWRITE,
PAGE_WRITECOMBINE | PAGE_WRITECOPY,
PAGE_WRITECOMBINE | PAGE_EXECUTE_READWRITE,
PAGE_WRITECOMBINE | PAGE_EXECUTE_WRITECOPY
};
/// <summary>
/// Change VAD protection flags
/// </summary>
/// <param name="pProcess">Target process object</param>
/// <param name="address">Target address</param>
/// <param name="prot">New protection flags</param>
/// <returns>Status code</returns>
NTSTATUS BBProtectVAD( IN PEPROCESS pProcess, IN ULONG_PTR address, IN ULONG prot )
{
NTSTATUS status = STATUS_SUCCESS;
PMMVAD_SHORT pVadShort = NULL;
status = BBFindVAD( pProcess, address, &pVadShort );
if (NT_SUCCESS( status ))
pVadShort->u.VadFlags.Protection = prot;
return status;
}
#pragma warning(disable : 4055)
/// <summary>
/// Hide memory from NtQueryVirtualMemory
/// </summary>
/// <param name="pProcess">Target process object</param>
/// <param name="address">Target address</param>
/// <returns>Status code</returns>
NTSTATUS BBUnlinkVAD( IN PEPROCESS pProcess, IN ULONG_PTR address )
{
NTSTATUS status = STATUS_SUCCESS;
PMMVAD_SHORT pVadShort = NULL;
status = BBFindVAD( pProcess, address, &pVadShort );
if (!NT_SUCCESS( status ))
return status;
// Erase image name
if (pVadShort->u.VadFlags.VadType == VadImageMap)
{
PMMVAD pVadLong = (PMMVAD)pVadShort;
if (pVadLong->Subsection && pVadLong->Subsection->ControlArea && pVadLong->Subsection->ControlArea->FilePointer.Object)
{
PFILE_OBJECT pFile = (PFILE_OBJECT)(pVadLong->Subsection->ControlArea->FilePointer.Value & ~0xF);
pFile->FileName.Buffer[0] = L'\0';
pFile->FileName.Length = 0;
}
else
return STATUS_INVALID_ADDRESS;
}
// Make NO_ACCESS
else if (pVadShort->u.VadFlags.VadType == VadDevicePhysicalMemory)
{
pVadShort->u.VadFlags.Protection = MM_ZERO_ACCESS;
}
// Invalid VAD type
else
status = STATUS_INVALID_PARAMETER;
return status;
}
#pragma warning(default : 4055)
/// <summary>
/// Get region VAD type
/// </summary>
/// <param name="pProcess">Target process object</param>
/// <param name="address">Target address</param>
/// <param name="pType">Resulting VAD type</param>
/// <returns>Status code</returns>
NTSTATUS BBGetVadType( IN PEPROCESS pProcess, IN ULONG_PTR address, OUT PMI_VAD_TYPE pType )
{
NTSTATUS status = STATUS_SUCCESS;
PMMVAD_SHORT pVad = NULL;
status = BBFindVAD( pProcess, address, &pVad );
if (!NT_SUCCESS( status ))
return status;
*pType = pVad->u.VadFlags.VadType;
return status;
}
/// <summary>
/// Find VAD that describes target address
/// </summary>
/// <param name="pProcess">Target process object</param>
/// <param name="address">Address to find</param>
/// <param name="pResult">Found VAD. NULL if not found</param>
/// <returns>Status code</returns>
NTSTATUS BBFindVAD( IN PEPROCESS pProcess, IN ULONG_PTR address, OUT PMMVAD_SHORT* pResult )
{
NTSTATUS status = STATUS_SUCCESS;
ULONG_PTR vpnStart = address >> PAGE_SHIFT;
ASSERT( pProcess != NULL && pResult != NULL );
if (pProcess == NULL || pResult == NULL)
return STATUS_INVALID_PARAMETER;
if (dynData.VadRoot == 0)
{
DPRINT( "BlackBone: %s: Invalid VadRoot offset\n", __FUNCTION__ );
status = STATUS_INVALID_ADDRESS;
}
PMM_AVL_TABLE pTable = (PMM_AVL_TABLE)((PUCHAR)pProcess + dynData.VadRoot);
PMM_AVL_NODE pNode = GET_VAD_ROOT( pTable );
// Search VAD
if (MiFindNodeOrParent( pTable, vpnStart, &pNode ) == TableFoundNode)
{
*pResult = (PMMVAD_SHORT)pNode;
}
else
{
DPRINT( "BlackBone: %s: VAD entry for address 0x%p not found\n", __FUNCTION__, address );
status = STATUS_NOT_FOUND;
}
return status;
}
/// <summary>
/// Convert protection flags
/// </summary>
/// <param name="prot">Protection flags.</param>
/// <param name="fromPTE">If TRUE - convert to PTE protection, if FALSE - convert to Win32 protection</param>
/// <returns>Resulting protection flags</returns>
ULONG BBConvertProtection( IN ULONG prot, IN BOOLEAN fromPTE )
{
if (fromPTE != FALSE)
{
// Sanity check
if (prot < ARRAYSIZE( MmProtectToValue ))
return MmProtectToValue[prot];
}
else
{
for (int i = 0; i < ARRAYSIZE( MmProtectToValue ); i++)
if (MmProtectToValue[i] == prot)
return i;
}
return 0;
}
Bağlantıları görmek için lütfen
Giriş Yap
2) NX bitini değiştirme
Manual maplenen pageleri gizlemenin başka bir yolu pageleri sadece Read/Write izni ile açmak, allocate ettiğiniz pagelerin PTE'lerini (Page Table Entry) almak ve pageinize coverların altında execute izni eklemektir. Karmaşık gelebilir ama aslında kolay.
Sadece bir Page'in PTE'SİNİ yakalamak için bir fonksiyon yazmanız gerekir. Unexported MiGetPteAddress'i kullanabilirsiniz.
Eğer kafanız karıştıysa Page Table Entry hakkında araştırma yapabilirsiniz.
C++:
#pragma once
#include "Utils.h"
enum mapLevel
{
PML4_LEVEL,
PDPT_LEVEL,
PD_LEVEL,
PT_LEVEL,
PTE_LEVEL,
};
namespace Memory
{
int myInt = 1312312;
SIZE_T outSize;
PHYSICAL_MEMORY_RANGE physicalMemRange[10];
static int numberOfRuns = 0;
PT_ENTRY_64* GetPte(VOID* VirtualAddress, CR3 HostCr3)
{
ADDRESS_TRANSLATION_HELPER helper;
UINT32 level;
PT_ENTRY_64* finalEntry;
PML4E_64* pml4;
PML4E_64* pml4e;
PDPTE_64* pdpt;
PDPTE_64* pdpte;
PDE_64* pd;
PDE_64* pde;
PTE_64* pt;
PTE_64* pte;
helper.AsUInt64 = (UINT64)VirtualAddress;
PHYSICAL_ADDRESS addr;
addr.QuadPart = HostCr3.AddressOfPageDirectory << PAGE_SHIFT;
pml4 = (PML4E_64*)MmGetVirtualForPhysical(addr);
pml4e = &pml4[helper.AsIndex.Pml4];
if (pml4e->Present == FALSE)
{
finalEntry = (PT_ENTRY_64*)pml4e;
goto Exit;
}
addr.QuadPart = pml4e->PageFrameNumber << PAGE_SHIFT;
pdpt = (PDPTE_64*)MmGetVirtualForPhysical(addr);
pdpte = &pdpt[helper.AsIndex.Pdpt];
if ((pdpte->Present == FALSE) || (pdpte->LargePage != FALSE))
{
finalEntry = (PT_ENTRY_64*)pdpte;
goto Exit;
}
addr.QuadPart = pdpte->PageFrameNumber << PAGE_SHIFT;
pd = (PDE_64*)MmGetVirtualForPhysical(addr);
pde = &pd[helper.AsIndex.Pd];
if ((pde->Present == FALSE) || (pde->LargePage != FALSE))
{
finalEntry = (PT_ENTRY_64*)pde;
goto Exit;
}
addr.QuadPart = pde->PageFrameNumber << PAGE_SHIFT;
pt = (PTE_64*)MmGetVirtualForPhysical(addr);
pte = &pt[helper.AsIndex.Pt];
finalEntry = (PT_ENTRY_64*)pte;
return (PT_ENTRY_64*)pte;
Exit:
return finalEntry;
}
void scanPage(INPUT_STRUCT* context, PVOID64 virtualPage, ULONG size, PHYSICAL_ADDRESS addr)
{
if (virtualPage && MmIsAddressValid(virtualPage))
{
//MmCopyVirtualMemory(PsGetCurrentProcess(), &myInt, Globals::targetProcess, 0, sizeof(int), KernelMode, &outSize);
__invlpg(virtualPage);
PVOID64 foundAt = Utils::findPattern(context->serialNumber, context->serialLength, 'A',
(ULONG64)virtualPage, (ULONG64)virtualPage + size);
if (foundAt)
{
DbgPrint("[+] seri numarası fiziksel adreste tespit edildi %p !!\n",
(addr.QuadPart + ((DWORD64)foundAt - (DWORD64)virtualPage)));
if (context->wide == false)
{
Utils::print((char*)foundAt, 18);
}
else
{
Utils::wprint((wchar_t*)foundAt, 18);
}
if (memcmp(Globals::signatureGuard, (PVOID)((DWORD64)(foundAt)+context->serialLength), 3) == 0)
{
}
else
{
RtlCopyMemory(foundAt, Globals::spoofString, context->serialLength);
}
}
else
{
}
}
}
void getPhysicalMemoryRanges()
{
PPHYSICAL_MEMORY_RANGE MmPhysicalMemoryRange = MmGetPhysicalMemoryRanges();
numberOfRuns = 0;
for (int number_of_runs = 0;
(MmPhysicalMemoryRange[number_of_runs].BaseAddress.QuadPart) || (MmPhysicalMemoryRange[number_of_runs].NumberOfBytes.QuadPart);
number_of_runs++)
{
DbgPrint("base addr %llx, size %llx\n", MmPhysicalMemoryRange[number_of_runs].BaseAddress.QuadPart,
MmPhysicalMemoryRange[number_of_runs].NumberOfBytes.QuadPart);
physicalMemRange[number_of_runs] = MmPhysicalMemoryRange[number_of_runs];
numberOfRuns += 1;
}
return;
}
bool isAddressinPhysMemRange(PHYSICAL_ADDRESS addr)
{
for (int i = 0; i < numberOfRuns; ++i)
{
if ((addr.QuadPart >= physicalMemRange[i].BaseAddress.QuadPart)
&& (addr.QuadPart <= (physicalMemRange[i].BaseAddress.QuadPart + physicalMemRange[i].NumberOfBytes.QuadPart)))
{
return true;
}
}
return false;
}
void scanPhysicalMemory(_In_ PVOID Context)
{
DbgPrint("thread başlatıldı! \n");
getPhysicalMemoryRanges();
INPUT_STRUCT* context = (INPUT_STRUCT*)Context;
CR3 cr3;
cr3.Flags = __readcr3();
PHYSICAL_ADDRESS addr;
addr.QuadPart = cr3.AddressOfPageDirectory << PAGE_SHIFT;
DbgPrint("Page dizinin.base adresi %p \n", addr.QuadPart);
PML4E_64* pml4 = (PML4E_64*)Globals::pageManager.mapPage((PVOID)addr.QuadPart, PML4_LEVEL);
if (MmIsAddressValid(pml4) == FALSE || pml4 == 0)
{
DbgPrint("Hata! pml4 geçerli bir adres değil \n");
return;
}
for (int i = 0; i < 512; ++i)
{
if (pml4[i].Present == false)
{
continue;
}
addr.QuadPart = pml4[i].PageFrameNumber << PAGE_SHIFT;
PDPTE_64* pdpt = (PDPTE_64*)Globals::pageManager.mapPage((PVOID)addr.QuadPart, PDPT_LEVEL);
if (pdpt == 0 || (MmIsAddressValid(pdpt) == FALSE))
{
continue;
}
for (int ii = 0; ii < 512; ++ii)
{
addr.QuadPart = pdpt[ii].PageFrameNumber << PAGE_SHIFT;
if (pdpt[ii].Present == false)
{
continue;
}
if (pdpt[ii].LargePage == true)
{
PDE_64* pageDir = (PDE_64*)MmGetVirtualForPhysical(addr);
if (pageDir == 0 || (MmIsAddressValid(pageDir) == FALSE))
{
continue;
}
for (int kkk = 0; kkk < 262144; ++kkk)
{
scanPage(context, (pageDir + (kkk * PAGE_SIZE)), PAGE_SIZE, addr);
}
continue;
}
PDE_64* pageDir = (PDE_64*)Globals::pageManager.mapPage((PVOID)addr.QuadPart, PD_LEVEL);
if (pageDir == 0 || (MmIsAddressValid(pageDir) == FALSE))
{
continue;
}
for (int iii = 0; iii < 512; ++iii)
{
addr.QuadPart = pageDir[iii].PageFrameNumber << PAGE_SHIFT;
if (pageDir[iii].Present == false)
{
continue;
}
if (pageDir[iii].LargePage == true)
{
PTE_64* pageTable = (PTE_64*)MmGetVirtualForPhysical(addr);
scanPage(context, pageTable, PAGE_SIZE * 512, addr);
continue;
}
PTE_64* pageTable = (PTE_64*)Globals::pageManager.mapPage((PVOID)addr.QuadPart, PT_LEVEL);
if (pageTable == 0 || (MmIsAddressValid(pageTable) == FALSE))
{
continue;
}
for (int iiii = 0; iiii < 512; ++iiii)
{
if (pageTable[iiii].Present == false || pageTable[iiii].Write == false || pageTable[iiii].ExecuteDisable == false)
{
continue;
}
addr.QuadPart = pageTable[iiii].PageFrameNumber << PAGE_SHIFT;
if (isAddressinPhysMemRange(addr) == TRUE)
{
PVOID64 virtualPage = (PVOID64)Globals::pageManager.mapPage((PVOID)addr.QuadPart, 4);
scanPage(context, virtualPage, PAGE_SIZE, addr);
Globals::pageManager.UnmapPage((PVOID)addr.QuadPart, 4);
}
}
Globals::pageManager.UnmapPage((PVOID)addr.QuadPart, 3);
}
Globals::pageManager.UnmapPage((PVOID)addr.QuadPart, 2);
}
Globals::pageManager.UnmapPage((PVOID)addr.QuadPart, 1);
}
Globals::pageManager.UnmapPage((PVOID)addr.QuadPart, 0);
return;
}
}
RWX pagelerini gizlemek için diğer yollar
- DLL'inizi imzalanmış ve packlenmiş bir DLL'de mapleyin.
- Birden fazla Page'i allocate etmeyin.
Yakında
Bağlantıları görmek için lütfen
Giriş Yap
Driveri ile hazır kodu ekleyeceğim.
Son düzenleme: