Bu rehber yazısında, büyük bir anti-hile sağlayıcısı olan BattlEye'ın sezgisel tekniklerden birine göz atacağız. En yaygın olarak bilinen adıyla stack walking.
Bu genellikle bir fonksiyonun hooklanması ve söz konusu fonksiyonu tam olarak kimin çağırdığını bulmak için stack'i dolaşmak suretiyle yapılır. Biri bunu neden yapsın? Diğer tüm programlarda olduğu gibi, video oyunu hack'lerinin de klavye bilgilerini almak, konsola yazdırmak veya belirli matematiksel ifadeleri hesaplamak için kullandıkları bir dizi iyi bilinen işlevi vardır.
Video oyunu hileleri ayrıca ister bellekte ister diskte olsun varlıklarını gizlemeye çalışmayı sever. Böylece hile karşıtı yazılım onu bulamaz. Bu hile programlarının unuttuğu şey, diğer kütüphanelerdeki işlevleri düzenli olarak çağırdıklarıdır ve bu bilinmeyen hileleri sezgisel olarak tespit etmek için kullanılabilir.
BattlEye, bu kamuya açık bir şekilde kanıtlanmamış ve bu makaleden önce sadece söylentiler olmasına rağmen "stack walking" uygulamıştır. Stack walking yönteminin etrafındaki tırnak işaretlerine dikkat edin çünkü burada göreceğiniz şey gerçek bir stack walking değildir. Bu sadece bir dönüş adresi kontrolü ve bir arayan dökümüdür. Gerçek bir stack walker, yığını dolaşır ve düzgün bir callstack oluşturur.
Hile karşıtı sistem siz oyun oynarken oyun sürecine dinamik olarak shell kodu gönderir. Bu shell kodlarının farklı boyutları ve farklı amaçları vardır ve aynı anda yayınlanmazlar. Böyle bir sistemin en güzel yanı, araştırmacıların rekabetçi bir maç devam ederken anti-hileyi dinamik olarak analiz etmelerini gerektirmesi ve söz konusu anti-hilenin özelliklerini belirlemeyi zorlaştırmasıdır.
Bu aynı zamanda anti-hilenin farklı kullanıcılara farklı önlemler uygulamasına olanak tanıyacaktır, örneğin sadece anormal bir öldürme/ölüm oranına sahip bir kişiye daha invaziv bir modül yayınlamak gibi. Bu BattlEye shell kodlarından biri bu yığın analizini yapmaktan sorumludur ve burada belgelediğim shellcodemain'e kıyasla marjinal olarak daha küçük boyutu nedeniyle shellcode8kb olarak adlandırıyoruz. Bu küçük shell kodu, AddVectoredExceptionHandler işlevini kullanarak bir vektörlü exception handler kuracak ve ardından aşağıdaki işlevlerde breakpoint tuzakları kuracaktır:
Bunu, yaygın olarak kullanılan işlevlerin bu listesini yineleyerek, ilgili işlevin ilk komutunu bir breakpoint noktası görevi gören int3'e ayarlayarak yapar. Bu breakpoint noktası ayarlandığında, ilgili işleve yapılan tüm çağrılar, tam kayıt ve yığın erişimine sahip olan istisna işleyiciden geçecektir. Bu erişimle, exception handler stack'ın en üstünden çağıran adresini dökecek ve sezgisel koşullardan herhangi biri karşılanırsa, çağıran işlevin 32 baytı dökülecek ve 0x31 rapor kimliğiyle BattlEye sunucularına gönderilecektir:
Gördüğümüz gibi, exception handler, bellek sayfasının bariz bir şekilde değiştirildiği veya bilinen bir işlem modülüne ait olmadığı (MEM_IMAGE bellek sayfası türü manualmappers tarafından ayarlanmamıştır) tüm arayanları çöpe atacaktır. Ayrıca, NtQueryVirtualMemory çağrısının tamamen başarısız olduğu durumlarda, hilecilerin bu sistem çağrısını hooklanmasını ve modüllerini stack dumper'ından gizlemesini önlemek için arayanları da dump edecektir.
Son koşul aslında oldukça ilginçtir. Görünüşe göre BattlEye geliştiricileri, insanların oyunlarında bu hile yöntemini kullandığını gördü ve doğrudan hedef almaya karar verdi. BattlEye'ın sunucusuna gönderilen tam yapı şöyledir:
Kaynak: Secret.Club
Bu genellikle bir fonksiyonun hooklanması ve söz konusu fonksiyonu tam olarak kimin çağırdığını bulmak için stack'i dolaşmak suretiyle yapılır. Biri bunu neden yapsın? Diğer tüm programlarda olduğu gibi, video oyunu hack'lerinin de klavye bilgilerini almak, konsola yazdırmak veya belirli matematiksel ifadeleri hesaplamak için kullandıkları bir dizi iyi bilinen işlevi vardır.
Video oyunu hileleri ayrıca ister bellekte ister diskte olsun varlıklarını gizlemeye çalışmayı sever. Böylece hile karşıtı yazılım onu bulamaz. Bu hile programlarının unuttuğu şey, diğer kütüphanelerdeki işlevleri düzenli olarak çağırdıklarıdır ve bu bilinmeyen hileleri sezgisel olarak tespit etmek için kullanılabilir.
std::print gibi yaygın işlevler üzerinde bir yığın yürüme motoru uygulayarak, kendilerini gizleseler bile bu hileleri bulabileceksiniz.BattlEye, bu kamuya açık bir şekilde kanıtlanmamış ve bu makaleden önce sadece söylentiler olmasına rağmen "stack walking" uygulamıştır. Stack walking yönteminin etrafındaki tırnak işaretlerine dikkat edin çünkü burada göreceğiniz şey gerçek bir stack walking değildir. Bu sadece bir dönüş adresi kontrolü ve bir arayan dökümüdür. Gerçek bir stack walker, yığını dolaşır ve düzgün bir callstack oluşturur.
Hile karşıtı sistem siz oyun oynarken oyun sürecine dinamik olarak shell kodu gönderir. Bu shell kodlarının farklı boyutları ve farklı amaçları vardır ve aynı anda yayınlanmazlar. Böyle bir sistemin en güzel yanı, araştırmacıların rekabetçi bir maç devam ederken anti-hileyi dinamik olarak analiz etmelerini gerektirmesi ve söz konusu anti-hilenin özelliklerini belirlemeyi zorlaştırmasıdır.
Bu aynı zamanda anti-hilenin farklı kullanıcılara farklı önlemler uygulamasına olanak tanıyacaktır, örneğin sadece anormal bir öldürme/ölüm oranına sahip bir kişiye daha invaziv bir modül yayınlamak gibi. Bu BattlEye shell kodlarından biri bu yığın analizini yapmaktan sorumludur ve burada belgelediğim shellcodemain'e kıyasla marjinal olarak daha küçük boyutu nedeniyle shellcode8kb olarak adlandırıyoruz. Bu küçük shell kodu, AddVectoredExceptionHandler işlevini kullanarak bir vektörlü exception handler kuracak ve ardından aşağıdaki işlevlerde breakpoint tuzakları kuracaktır:
Kod:
GetAsyncKeyState
GetCursorPos
IsBadReadPtr
NtUserGetAsyncKeyState
GetForegroundWindow
CallWindowProcW
NtUserPeekMessage
NtSetEvent
sqrtf
__stdio_common_vsprintf_s
CDXGIFactory::TakeLock
TppTimerpExecuteCallback
Bunu, yaygın olarak kullanılan işlevlerin bu listesini yineleyerek, ilgili işlevin ilk komutunu bir breakpoint noktası görevi gören int3'e ayarlayarak yapar. Bu breakpoint noktası ayarlandığında, ilgili işleve yapılan tüm çağrılar, tam kayıt ve yığın erişimine sahip olan istisna işleyiciden geçecektir. Bu erişimle, exception handler stack'ın en üstünden çağıran adresini dökecek ve sezgisel koşullardan herhangi biri karşılanırsa, çağıran işlevin 32 baytı dökülecek ve 0x31 rapor kimliğiyle BattlEye sunucularına gönderilecektir:
C:
__int64 battleye::exception_handler(_EXCEPTION_POINTERS *exception)
{
if (exception->ExceptionRecord->ExceptionCode != STATUS_BREAKPOINT)
return 0;
const auto caller_function = *(__int64 **)exception->ContextRecord->Rsp;
MEMORY_BASIC_INFORMATION caller_memory_information = {};
auto desired_size = 0;
// QUERY THE MEMORY PAGE OF THE CALLER
const auto call_failed = NtQueryVirtualMemory(
GetCurrentProcess(),
caller_function,
MemoryBasicInformation,
&caller_memory_information,
sizeof(caller_memory_information),
&desired_size) < 0;
// IS THE MEMORY SOMEHOW NOT COMMITTED? (WOULD SUGGEST VAD MANIPULATIUON)
const auto non_commit = caller_memory_information.State != MEM_COMMIT;
// IS THE PAGE EXECUTABLE BUT DOES NOT BELONG TO A PROPERLY LOADED MODULE?
const auto foreign_image = caller_memory_information.Type != MEM_IMAGE && caller_memory_information.RegionSize > 0x2000;
// IS THE CALL BEING SPOOFED BY NAMAZSO?
const auto spoof = *(_WORD *)caller_function == 0x23FF; // jmp qword ptr [rbx]
// FLAG ALL ANBORMALITIES
if (call_failed || non_commit || foreign_image || spoof)
{
report_stack.unknown = 0;
report_stack.report_id = 0x31;
report_stack.hook_id = hook_id;
report_stack.caller = (__int64)caller_function;
report_stack.function_dump[0] = *caller_function;
report_stack.function_dump[1] = caller_function[1];
report_stack.function_dump[2] = caller_function[2];
report_stack.function_dump[3] = caller_function[3];
if (!call_failed)
{
report_stack.allocation_base = caller_memory_information.AllocationBase;
report_stack.base_address = caller_memory_information.BaseAddress;
report_stack.region_size = caller_memory_information.RegionSize;
report_stack.type_protect_state = caller_memory_information.Type | caller_memory_information.Protect | caller_memory_information.State;
}
battleye::report(&report_stack, sizeof(report_stack), false);
return -1;
}
}
Gördüğümüz gibi, exception handler, bellek sayfasının bariz bir şekilde değiştirildiği veya bilinen bir işlem modülüne ait olmadığı (MEM_IMAGE bellek sayfası türü manualmappers tarafından ayarlanmamıştır) tüm arayanları çöpe atacaktır. Ayrıca, NtQueryVirtualMemory çağrısının tamamen başarısız olduğu durumlarda, hilecilerin bu sistem çağrısını hooklanmasını ve modüllerini stack dumper'ından gizlemesini önlemek için arayanları da dump edecektir.
Son koşul aslında oldukça ilginçtir. Görünüşe göre BattlEye geliştiricileri, insanların oyunlarında bu hile yöntemini kullandığını gördü ve doğrudan hedef almaya karar verdi. BattlEye'ın sunucusuna gönderilen tam yapı şöyledir:
C:
struct __unaligned battleye_stack_report
{
__int8 unknown;
__int8 report_id;
__int8 val0;
__int64 caller;
__int64 function_dump[4];
__int64 allocation_base;
__int64 base_address;
__int32 region_size;
__int32 type_protect_state;
};
Kaynak: Secret.Club
Son düzenleme: