Shellcode还将扫描游戏进程和Windows进程lsass.exe,以查找可疑的内存分配。尽管上一节中提到的上一个内存扫描在特定于线程创建的所有进程中查找一般异常,但它着重于特定情况,甚至包括内存区域大小白名单,这对于滥用来说应该是微不足道的。
通过检查中的Type
字段,在game和lsass进程中扫描已知模块之外的可执行内存MEMORY_BASIC_INFORMATION
。这个字段将是MEM_IMAGE
如果存储器部分是由Windows图像加载器(正确映射Ldr
),而字段将是MEM_PRIVATE
或MEM_MAPPED
如果通过其它方式分配的。这实际上是检测shellcode的正确方法,并且是在三年前在我的项目MapDetection中实现的。值得庆幸的是,现在反作弊的速度很快了。
扫描完成后,添加了一个针对游戏的检查,引起了我的注意。shellcode将IsBadReadPtr
在保留和释放的内存上发送垃圾邮件,该内存应该始终返回true,因为在这些部分中通常不会有任何可用的内存。这样做的目的是抓住作弊者手动修改虚拟地址描述符3,以将其内存隐藏在反作弊中。虽然从理论上讲,这实际上是一个好主意,但这种垃圾邮件将损害性能,并且IsBadReadPtr非常容易挂钩。
3 Windows内存管理器使用“虚拟地址描述符”树来描述进程在分配时使用的内存范围。当进程使用VirutalAlloc分配内存时,内存管理器将在VAD树中创建一个条目。来源
for ( search_index = 0; ; ++search_index ) { search_count = lsass_handle ? 2 : 1; if ( search_index >= search_count ) break; // SEARCH CURRENT PROCESS BEFORE LSASS if ( search_index ) current_process = lsass_handle; else current_process = -1; // ITERATE ENTIRE ADDRESS SPACE OF PROCESS for ( current_address = 0; NtQueryVirtualMemory)( current_process, current_address, 0, &mem_info, sizeof(mem_info), &used_length) >= 0; current_address = (char *)mem_info.BaseAddress + mem_info.RegionSize ) { // FIND ANY EXECUTABLE MEMORY THAT DOES NOT BELONG TO A MODULE if ( mem_info.State == MEM_COMMIT && (mem_info.Protect == PAGE_EXECUTE || mem_info.Protect == PAGE_EXECUTE_READ || mem_info.Protect == PAGE_EXECUTE_READWRITE) && (mem_info.Type == MEM_PRIVATE || mem_info.Type == MEM_MAPPED) && (mem_info.BaseAddress > SHELLCODE_ADDRESS || mem_info.BaseAddress + mem_info.RegionSize <= SHELLCODE_ADDRESS) ) { report.pad = 0; report.id = 0x10; report.base_address = (__int64)mem_info.BaseAddress; report.region_size = mem_info.RegionSize; report.meta = mem_info.Type | mem_info.Protect | mem_info.State; battleye::send(&report, sizeof(report), 0); if ( !search_index && (mem_info.RegionSize != 0x12000 && mem_info.RegionSize >= 0x11000 && mem_info.RegionSize <= 0x500000 || mem_info.RegionSize == 0x9000 || mem_info.RegionSize == 0x7000 || mem_info.RegionSize >= 0x2000 && mem_info.RegionSize <= 0xF000 && mem_info.Protect == PAGE_EXECUTE_READ)) { // INITIATE RAW DATA PACKET report.pad = 0; report.id = 0xBE; battleye::send(&report, sizeof(report), false); // DUMP SHELLCODE IN CHUNKS OF 0x27EA (WHY?) for ( chunk_index = 0; ; ++chunk_index ) { if ( chunk_index >= mem_info.region_size / 0x27EA + 1 ) break; buffer_size = chunk_index >= mem_info.region_size / 0x27EA ? mem_info.region_size % 0x27EA : 0x27EA; if ( NtReadVirtualMemory(current_process, mem_info.base_address, &report.buffer, buffer_size, 0x00) < 0 ) break; report.pad = 0; report.id = 0xBEu; battleye::send(&v313, buffer_size + 2, false); } } } // TRY TO FIND DKOM'D MEMORY IN LOCAL PROCESS if ( !search_index && (mem_info.State == MEM_COMMIT && (mem_info.Protect == PAGE_NOACCESS || !mem_info.Protect) || mem_info.State == MEM_FREE || mem_info.State == MEM_RESERVE) ) { toggle = 0; for ( scan_address = current_address; scan_address < (char *)mem_info.BaseAddress + mem_info.RegionSize && scan_address < (char *)mem_info.BaseAddress + 0x40000000; scan_address += 0x20000 ) { if ( !IsBadReadPtr(scan_address, 1) && NtQueryVirtualMemory(GetCurrentProcess(), scan_address, 0, &local_mem_info, sizeof(local_mem_info), &used_length) >= 0 && local_mem_info.State == mem_info.State && (local_mem_info.State != 4096 || local_mem_info.Protect == mem_info.Protect) ) { if ( !toggle ) { report.pad = 0; report.id = 0x10; report.base_address = mem_info.BaseAddress; report.region_size = mem_info.RegionSize; report.meta = mem_info.Type | mem_info.Protect | mem_info.State; battleye::send(&report, sizeof(report), 0); toggle = 1; } report.pad = 0; report.id = 0x10; report.base_address = local_mem_info.BaseAddress; report.region_size = local_mem_info.RegionSize; report.meta = local_mem_info.Type | local_mem_info.Protect | local_mem_info.State; battleye::send(&local_mem_info, sizeof(report), 0); } } } } }
该机制将枚举计算机上所有打开的手柄,并标记任何游戏进程手柄。这样做是为了捕获作弊者,迫使其句柄具有通常无法获得的特定级别的访问权限,因为反作弊寄存器会回调,以防止进程获得游戏进程的内存修改权限。如果某个进程在游戏进程的打开句柄中被捕获,则会将相关信息(例如访问级别和进程名称)发送到游戏服务器:
report_buffer = (__int8 *)malloc(0x2800); report_buffer[0] = 0; report_buffer[1] = 0x11; buffer_index = 2; handle_info = 0; buffer_size = 0x20; do { buffer_size += 0x400; handle_info = (SYSTEM_HANDLE_INFORMATION *)realloc(handle_info, buffer_size); if ( !handle_info ) break; query_status = NtQuerySystemInformation(0x10, handle_info, buffer_size, &buffer_size);// SystemHandleInformation } while ( query_status == STATUS_INFO_LENGTH_MISMATCH ); if ( handle_info && query_status >= 0 ) { process_object_type_index = -1; for ( handle_index = 0; (unsigned int)handle_index < handle_info->number_of_handles && buffer_index <= 10107; ++handle_index ) { // ONLY FILTER PROCESS HANDLES if ( process_object_type_index == -1 || (unsigned __int8)handle_info->handles[handle_index].ObjectTypeIndex == process_object_type_index ) { // SEE IF OWNING PROCESS IS NOT GAME PROCESS if ( handle_info->handles[handle_index].UniqueProcessId != GetCurrentProcessId() ) { process_handle = OpenProcess( PROCESS_DUP_HANDLE, 0, *(unsigned int *)&handle_info->handles[handle_index].UniqueProcessId); if ( process_handle ) { // DUPLICATE THEIR HANDLE current_process_handle = GetCurrentProcess(); if ( DuplicateHandle( process_handle, (unsigned __int16)handle_info->handles[handle_index].HandleValue, current_process_handle, &duplicated_handle, PROCESS_QUERY_LIMITED_INFORMATION, 0, 0) ) { if ( process_object_type_index == -1 ) { if ( NtQueryObject(duplicated_handle, ObjectTypeInformation, &typeinfo, 0x400, 0) >= 0 && !_wcsnicmp(typeinfo.Buffer, "Process", typeinfo.Length / 2) ) { process_object_type_index = (unsigned __int8)handle_info->handles[handle_index].ObjectTypeIndex; } } if ( process_object_type_index != -1 ) { // DUMP OWNING PROCESS NAME target_process_id = GetProcessId(duplicated_handle); if ( target_process_id == GetCurrentProcessId() ) { if ( handle_info->handles[handle_index].GrantedAccess & PROCESS_VM_READ|PROCESS_VM_WRITE ) { owning_process = OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, 0, *(unsigned int *)&handle_info->handles[handle_index].UniqueProcessId); process_name_length = 0x80; if ( !owning_process || !QueryFullProcessImageNameA( owning_process, 0, &report_buffer[buffer_index + 1], &process_name_length) ) { process_name_length = 0; } if ( owning_process ) CloseHandle(owning_process); report_buffer[buffer_index] = process_name_length; after_name_index = buffer_index + (char)process_name_length + 1; *(_DWORD *)&report_buffer[after_name_index] = handle_info->handles[handle_index].GrantedAccess; buffer_index = after_name_index + 4; } } } CloseHandle(duplicated_handle); CloseHandle(process_handle); } else { CloseHandle(process_handle); } } } } } } if ( handle_info ) free(handle_info); battleye::send(report_buffer, buffer_index, false); free(report_buffer);
shellcode实现的第一个例程是一个包罗万象的功能,用于记录和转储有关所有正在运行的进程的信息。这是相当普遍的,但出于完整性考虑,已包含在本文中。这还将上传磁盘上主映像的文件大小。
snapshot_handle = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0x00 ); if ( snapshot_handle != INVALID_HANDLE_VALUE ) { process_entry.dwSize = 0x130; if ( Process32First(snapshot_handle, &process_entry) ) { report_buffer = (std::uint8_t*)malloc(0x5000); report_buffer[0] = 0; report_buffer[1] = 0xB; buffer_index = 2; // ITERATE PROCESSES do { target_process_handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_entry.th32ProcessID); // QUERY PROCESS IAMGE NAME name_length = 0x100; query_result = QueryFullProcessImageNameW(target_process_handle, 0, &name_buffer, &name_length); name_length = WideCharToMultiByte( CP_UTF8, 0x00, &name_buffer, name_length, &report_buffer[buffer_index + 5], 0xFF, nullptr, nullptr); valid_query = target_process_handle && query_result && name_length; // Query file size if ( valid_query ) { if ( GetFileAttributesExW(&name_buffer, GetFileExInfoStandard, &file_attributes) ) file_size = file_attributes.nFileSizeLow; else file_size = 0; } else { // TRY QUERY AGAIN WITHOUT HANDLE process_id_information.process_id = (void *)process_entry.th32ProcessID; process_id_information.image_name.Length = '\0'; process_id_information.image_name.MaximumLength = '\x02\0'; process_id_information.image_name.Buffer = name_buffer; if ( NtQuerySystemInformation(SystemProcessIdInformation, &process_id_information, 24, 1) < 0 ) { name_length = 0; } else { name_address = &report_buffer[buffer_index + 5]; name_length = WideCharToMultiByte( CP_UTF8, 0, (__int64 *)process_id_information.image_name.Buffer, process_id_information.image_name.Length / 2, name_address, 0xFF, nullptr, nullptr); } file_size = 0; } // IF MANUAL QUERY WORKED if ( name_length ) { *(_DWORD *)&report_buffer[buffer_index] = process_entry.th32ProcessID; report_buffer[buffer_index + 4] = name_length; *(_DWORD *)&report_buffer[buffer_index + 5 + name_length] = file_size; buffer_index += name_length + 9; } if ( target_process_handle ) CloseHandle(target_process_handle); // CACHE LSASS HANDLE FOR LATER !! if ( *(_DWORD *)process_entry.szExeFile == 'sasl' ) lsass_handle = OpenProcess(0x410, 0, process_entry.th32ProcessID); } while ( Process32Next(snapshot_handle, &process_entry) && buffer_index < 0x4EFB ); // CLEANUP CloseHandle((__int64)snapshot_handle); battleye::send(report_buffer, buffer_index, 0); free(report_buffer); } }
Knative 是基于 Kubernetes 的开源 Serverless 应用编排框架。阿里云 Knative 在...
作者 许力 阿里云原生多模数据库Lindorm与东软云科技推出联合解决方案 共建面向...
新冠肺炎疫情的全球大流行不仅深刻影响了世界政治经济格局的发展演进,而且加速...
1.我手机掉进厕所了怎么办?是屎在给我发信息吗? 2.生活就像新闻联播,不是换...
数据目录已成为企业数据管理策略的重要组成部分,但选择合适的数据目录并不是简...
年味到底是什么?不同的时代,人们迎接春节的方式也在变换。 在70后的眼里,年味...
?又到一年毕业季,即将开启(实习及正式)职场生涯的同学们会有不少疑惑。比如,...
1.男女之间是真的可以有纯友谊的,只要一个打死不说一个装傻到底。 2.不要迷恋...
1.世界上最悲哀的事莫过于睡眠不足,尤其是在感冒的时候。 2.我是一个很有原则...
我们将创建以下三个Shell脚本来锁定和解锁多个帐户和查看账号状态。 创建锁定用...