此专有shellcode的另一种机制是对所有正在运行的进程进行完整的地址空间枚举。该枚举例程检查在shellcode和手动映射的可移植可执行文件中经常出现的内存异常2。
2手动映射可执行文件是复制Windows图像加载器的过程
这是通过枚举所有进程及其各自的线程来完成的。通过检查每个线程的起始地址并将其与已知的模块地址范围进行交叉引用,可以推断出哪些线程用于执行动态分配的shellcode。当发现这样的异常时,线程起始地址,线程句柄,线程索引和线程创建时间都被发送到相应的游戏服务器以进行进一步调查。
之所以会这样做是因为将代码分配到受信任的进程中会增加隐身性。这种方法可以缓解这种情况,因为如果直接为它们启动线程,则shellcode会很出色。这不会捕获任何使用诸如线程劫持之类的方法来执行Shellcode的人,这是另一种方法。
反编译如下:
query_buffer_size = 0x150;
while ( 1 )
{
// QUERY PROCESS LIST
query_buffer_size += 0x400;
query_buffer = (SYSTEM_PROCESS_INFORMATION *)realloc(query_buffer, query_buffer_size);
if ( !query_buffer )
break;
query_status = NtQuerySystemInformation(
SystemProcessInformation, query_buffer,
query_buffer_size, &query_buffer_size);
if ( query_status != STATUS_INFO_LENGTH_MISMATCH )
{
if ( query_status >= 0 )
{
// QUERY MODULE LIST SIZE
module_list_size = 0;
NtQuerySystemInformation)(SystemModuleInformation, &module_list_size, 0, &module_list_size);
modules_buffer = (RTL_PROCESS_MODULES *)realloc(0, module_list_size);
if ( modules_buffer )
{
// QUERY MODULE LIST
if ( NtQuerySystemInformation)(
SystemModuleInformation,
modules_buffer,
module_list_size,
1) >= 0 )
{
for ( current_process_entry = query_buffer;
current_process_entry->UniqueProcessId != GAME_PROCESS_ID;
current_process_entry =
(std::uint64_t)current_process_entry +
current_process_entry->NextEntryOffset) )
{
if ( !current_process_entry->NextEntryOffset )
goto STOP_PROCESS_ITERATION_LABEL;
}
for ( thread_index = 0; thread_index < current_process_entry->NumberOfThreads; ++thread_index )
{
// CHECK IF THREAD IS INSIDE OF ANY KNOWN MODULE
for ( module_count = 0;
module_count < modules_buffer->NumberOfModules
&& current_process_entry->threads[thread_index].StartAddress <
modules_buffer->Modules[module_count].ImageBase
|| current_process_entry->threads[thread_index].StartAddress >=
(char *)modules_buffer->Modules[module_count].ImageBase +
modules_buffer->Modules[module_count].ImageSize);
++module_count )
{
;
}
if ( module_count == modules_buffer->NumberOfModules )// IF NOT INSIDE OF ANY MODULES, DUMP
{
// SEND A REPORT !
thread_report.pad = 0;
thread_report.id = 0xF;
thread_report.thread_base_address =
current_process_entry->threads[thread_index].StartAddress;
thread_report.thread_handle =
current_process_entry->threads[thread_index].ClientId.UniqueThread;
thread_report.thread_index =
current_process_entry->NumberOfThreads - (thread_index + 1);
thread_report.create_time =
current_process_entry->threads[thread_index].CreateTime -
current_process_entry->CreateTime;
thread_report.windows_directory_delta = nullptr;
if ( GetWindowsDirectoryA(&directory_path, 0x80) )
{
windows_directory_handle = CreateFileA(
&directory_path,
GENERIC_READ,
7,
0,
3,
0x2000000,
0);
if ( windows_directory_handle != INVALID_HANDLE_VALUE )
{
if ( GetFileTime(windows_directory_handle, 0, 0, &last_write_time) )
thread_report.windows_directory_delta =
last_write_time -
current_process_entry->threads[thread_index].CreateTime;
CloseHandle(windows_directory_handle);
}
}
thread_report.driver_folder_delta = nullptr;
system_directory_length = GetSystemDirectoryA(&directory_path, 128);
if ( system_directory_length )
{
// Append \\Drivers
std::memcpy(&directory_path + system_directory_length, "\\Drivers", 9);
driver_folder_handle = CreateFileA(&directory_path, GENERIC_READ, 7, 0i, 3, 0x2000000, 0);
if ( driver_folder_handle != INVALID_HANDLE_VALUE )
{
if ( GetFileTime(driver_folder_handle, 0, 0, &drivers_folder_last_write_time) )
thread_report.driver_folder_delta =
drivers_folder_last_write_time -
current_process_entry->threads[thread_index].CreateTime;
CloseHandle(driver_folder_handle);
}
}
battleye::send(&thread_report.pad, 0x2A, 0);
}
}
}
STOP_PROCESS_ITERATION_LABEL:
free(modules_buffer);
}
free(query_buffer);
}
break;
}
}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);
}
}
}
}
}原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。