Всем привет. В этой статье я поделюсь с вами результатом обратной разработки драйвера анти-чита FACEIT.
Обфускация:
В качестве обфускации, анти-чит использует виртуальную машину "Griffin". Такое же решение используется в Vanguard'e.
Убедиться в этом можно взглянув на секции:
| Name | | Virtual Size | | Characteristics |
.grfn10 0x2514eb 60000060 ( ER )
.grfn20 0x7d168 C0000040 ( RW )
.grfn11 0x51d569a 60000060 ( ER )
.grfn22 0x4ceb0 C0000040 ( RW )
Перейдем к результату обратной разработки:
| Сбор контекста выполнения ( FACEIT_AC.sys + 0x876C4 ):
* Анти-чит использует это для того, чтобы получать информацию о потоках на каждом ядре процессора. Далее, он анализирует ее и отправляет ее на сервер.
| Сканирование целостности PML4 ( FACEIT_AC.sys + 0x6D26C ):
* Анти-чит использует это для того, чтобы предотвратить PML4 Hijack ( суть этого эксплоита заключается в мануальном создании таблицы, ее модификация и запись адреса этой таблицы в CR3 процесса )
| Верификация модулей ( FACEIT_AC.sys + 0x40E48 ):
* Анти-чит использует это для того, чтобы верифицировать модули. Вызывается эта функция в LoadImageNotify каллбеке.
Вывод: Благодаря данной статье вы сможете узнать полезную информацию о Faceit'e. Надеюсь, эта статья была полезной!
Обфускация:
В качестве обфускации, анти-чит использует виртуальную машину "Griffin". Такое же решение используется в Vanguard'e.
Убедиться в этом можно взглянув на секции:
| Name | | Virtual Size | | Characteristics |
.grfn10 0x2514eb 60000060 ( ER )
.grfn20 0x7d168 C0000040 ( RW )
.grfn11 0x51d569a 60000060 ( ER )
.grfn22 0x4ceb0 C0000040 ( RW )
Перейдем к результату обратной разработки:
| Сбор контекста выполнения ( FACEIT_AC.sys + 0x876C4 ):
C++:
bool __fastcall capture_thread_information( volatile signed __int32* buffer ) {
const auto processor_idx = get_current_process_idx( ); // KeGetCurrentProcessorNumber
const auto slot = reinterpret_cast< pslot_t >( buffer + ( processor_idx * sizeof( cpu_slot_t ) ) );
if ( !slot->filled ) {
// заполнение данных о текущем потоке в слот
/*
*( _QWORD* )&a1[ v2 + 10 ] = *( _QWORD* )( ( char* )KeGetCurrentThread( ) + ( unsigned __int16 )word_1401070AC );
*( _QWORD* )&a1[ v2 + 12 ] = *( _QWORD* )( ( char* )KeGetCurrentThread( ) + ( unsigned int )dword_140107150 );
*( _QWORD* )&a1[ v2 + 14 ] = *( _QWORD* )( ( char* )KeGetCurrentThread( ) + ( unsigned int )dword_140107154 );
*/
slot->thread = KeGetCurrentThread( );
slot->cr3 = __readcr3( );
slot->hal_reserved_data = ( void* )KeGetPcr( )->HalReserved[ 8 ];
const auto stack_pointer = ReturnAddress( );
const auto stack_offset = calculate_stack_delta( &stack_pointer );
slot->stack_depth = ( stack_offset > 0x400 ) ? 1024 : stack_offset;
slot->filled = 1;
memcpy( &slot->stack_buffer_dump, &stack_pointer, slot->stack_depth );
_InterlockedIncrement( buffer );
}
return 1;
}
| Сканирование целостности PML4 ( FACEIT_AC.sys + 0x6D26C ):
C++:
void scan_kernel_page_tables( uintptr_t* current_pml4, uintptr_t* copied_pml4 ) {
// индексы с 0x100 ( 256 ) -> 0x1ff ( 511 ) отвечают за Kernel mode
for ( int idx{ 0x100 }; idx < 0x200; ++idx ) {
const auto current = current_pml4[ idx ];
const auto copied = copied_pml4[ idx ];
// проверка на активный "Present" бит
if ( ( copied & 1 ) != 0 ) {
// проверка на модифицированный PFN
if ( ( ( current ^ copied ) & 0x0000FFFFFFFFF000ULL ) != 0 ) {
// страница была модиицирована
const auto report = reinterpret_cast< preport_t >( allocate_pool_non_paged_nx( 0x658 ) );
if ( report ) {
// заполнение репорта данными
memset( report, 0, sizeof( report_t ) );
report->pml4_index = idx;
report->report_type = 18; // не факт, что это именно тип репорта, но скорее всего это оно и есть
/*
v30 = ((__int64 (__fastcall *)(__int64))(qword_140105900 ^ qword_140105908))(a2);
*((_DWORD *)v15 + 395) = v30;
*((_DWORD *)v15 + 396) = ((__int64 (__fastcall *)(__int64))(qword_140106110 ^ qword_140106118))(a2);
*((_DWORD *)v15 + 397) = dword_140106D18;
*/
send_report( report );
}
ex_free_pool( report );
break;
}
}
}
}
| Верификация модулей ( FACEIT_AC.sys + 0x40E48 ):
C++:
bool __fastcall is_not_blacklisted_timestamp( int timestamp ) {
return timestamp != 1475236997
&& timestamp != 1429858907
&& timestamp != 1362979926
&& timestamp != 1315311890
&& timestamp != 1117003152
&& timestamp != 1372912075
&& timestamp != 1236914549
&& timestamp != 1228525034
&& timestamp != 1508258062
&& timestamp != 1218220134
&& timestamp != 1192260120
&& timestamp != 1512106853
&& timestamp != 1215694711
&& timestamp != 1384442563
&& timestamp != 1350982095
&& timestamp != 1224284476
&& timestamp != 1246526006
&& timestamp != 1461742989
&& timestamp != 1457508537
&& timestamp != 1411041869
&& timestamp != 1521788191
&& timestamp != 1534392208
&& timestamp != 1539231917
&& timestamp != 1304986844
&& timestamp != 1367461187
&& timestamp != 1577333030
&& timestamp != 1566803778
&& timestamp != 1126291254
&& timestamp != 1302902331
&& timestamp != 1421434629
&& timestamp != 1331398650
&& timestamp != 1231373451
&& timestamp != 1390546403
&& timestamp != 1192770227
&& timestamp != 1193148207
&& timestamp != 1428561609
&& timestamp != 1479375988
&& timestamp != 1257225291
&& timestamp != 1608492340
&& timestamp != 1574187260
&& timestamp != 1530670833;
}
bool verify_module( void* ctx, void* module_base ) {
// копирование хеадера модуля с диска
const auto buffer = copy_process_header( ctx );
const auto dos_header = reinterpret_cast< PIMAGE_DOS_HEADER >( buffer );
if ( dos_header->e_magic != 0x5a4d )
return;
const auto nt_headers = reinterpret_cast< PIMAGE_NT_HEADERS64 >( buffer + dos_header->e_lfanew );
if ( nt_headers->Signature != 0x4550 )
return;
// если таймстамп модуля находится в базе с заблокированными, отправляем репорт
// ID "9" => report_blacklist_id
if ( !is_not_blacklisted_timestamp( nt_headers->FileHeader.TimeDateStamp ) )
return report( 9 );
const auto mapped_image = map_image( buffer, module_base );
if ( mapped_image ) {
// если size_of_image на диске != size_of_image в файле, отправляем репорт
// ID "10" => module_size_mismatch
if ( !verify_module_size( module_base, mapped_image, buffer ) )
return report( 10 );
// если находим разницу между активным кодом и файлом на диске
// ID "5" => code_patch
if ( scan_for_patches( module_base, mapped_image, buffer ) )
return report( 5 );
const auto security_directory = nt_headers->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_SECURITY ].VirtualAddress;
const auto security_directory_size = nt_headers->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_SECURITY ].Size;
if ( security_directory
&& security_directory_size ) {
const auto cert_data = get_cert_data( mapped_image, security_directory );
// если подпись является невалидной ( или она отстуствует ), отправляем репорт
// ID "6" => invalid_signature
if ( is_signature_invalid( cert_data ) )
return report( 6 );
// если владелец подписи является недоверенным ( не microsoft и не игровые модули ), отправляем репорт
// ID "11" => signer_untrusted
if ( is_signer_untrusted( cert_data ) )
return report( 11 );
}
}
return true;
}
* Анти-чит использует это для того, чтобы верифицировать модули. Вызывается эта функция в LoadImageNotify каллбеке.
Вывод: Благодаря данной статье вы сможете узнать полезную информацию о Faceit'e. Надеюсь, эта статья была полезной!