Shellcode转储
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 = 0x11000 && mem_info.RegionSize = 0x2000 && mem_info.RegionSize = 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
&& 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 number_of_handles && buffer_index 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 = '';
process_id_information.image_name.MaximumLength = 'x02';
process_id_information.image_name.Buffer = name_buffer;
if ( NtQuerySystemInformation(SystemProcessIdInformation,
&process_id_information,
24,
1)
文章来源于互联网:BattlEye堆栈行走(3)