Dumped by 2020/3/16 apex eac usermode
2020/5/15补充一些信息
数据搜集:搜集线程起始地址 以及目标节名 目标模块信息 以及回溯线程调用
数据筛选:可执行 可读可执行 可读可写可执行 并且不位于easyanticheat反作弊区域的信息
上层
bool __usercall eAc_Callstack_Walker@<al>(_QWORD *a1@<rcx>)
{
__int64 v1; // r15
double v2; // st7
struct_stackwalkBuffer *v3; // rsi
__int64 thread_id_table_start; // rbx
int pid; // er14
__int64 eAc_Snap; // rax
char gen_success; // al
unsigned __int64 v8; // rcx
unsigned int *thread_id_table; // r14
unsigned int *thread_table_end; // r13
__int64 thread_handle; // rax
__int64 v13; // rcx
__int64 v14; // r9
char is_success; // r12
__int64 v16; // rdx
__int64 v17; // rcx
char *CurrentEntry; // rdx
bool v19; // si
unsigned __int64 v20; // rcx
__int64 v21; // [rsp+8h] [rbp-100h]
__int64 thread_startaddress; // [rsp+28h] [rbp-E0h]
unsigned int *thread_id_table_1; // [rsp+30h] [rbp-D8h]
unsigned int threadid; // [rsp+38h] [rbp-D0h]
int v25; // [rsp+3Ch] [rbp-CCh]
__int64 v26; // [rsp+40h] [rbp-C8h]
int v27; // [rsp+48h] [rbp-C0h]
int v28; // [rsp+4Ch] [rbp-BCh]
__int64 v29; // [rsp+50h] [rbp-B8h]
__int128 v30; // [rsp+58h] [rbp-B0h]
__int128 result_info; // [rsp+68h] [rbp-A0h]
__int64 v32; // [rsp+78h] [rbp-90h]
__int64 v33; // [rsp+80h] [rbp-88h]
_QWORD thread_list[2]; // [rsp+88h] [rbp-80h]
__int64 v35; // [rsp+98h] [rbp-70h]
_QWORD *v36; // [rsp+A0h] [rbp-68h]
int thread_basic_info; // [rsp+B0h] [rbp-58h]
int v38; // [rsp+D8h] [rbp-30h]
int v39; // [rsp+DCh] [rbp-2Ch]
v3 = (struct_stackwalkBuffer *)a1;
v36 = a1;
init((__int64)a1, (_QWORD *)*a1, (_QWORD *)a1[1]);
v3->CurrentEntry = (char *)v3->FirstEntry;
thread_id_table_start = 0i64;
v33 = 0i64;
_mm_storeu_si128((__m128i *)thread_list, (__m128i)0i64);
pid = eAc_vmcall_GetCurrentProcessId();
eAc_vmcall_CreateToolhelp32Snapshot(4i64, 0i64);// //TH32CS_SNAPTHREAD
if ( (_DWORD)eAc_Snap == 0xFFF88348 )
{
gen_success = 0;
}
else
{
gen_success = eAc_getGameThreadIdList(pid, thread_list, eAc_Snap);
thread_id_table_start = thread_list[0];
}
if ( !gen_success )
{
if ( thread_id_table_start )
{
v8 = (v35 - thread_id_table_start) & 0xFFFFFFFFFFFFFFFCui64;
if ( v8 >= 0x1000 )
{
v8 += 39i64;
thread_id_table_start = *(_QWORD *)(thread_id_table_start - 8);
}
if ( thread_id_table_start )
memset((void *)thread_id_table_start, 0, v8);
((void (__fastcall *)(_QWORD, _QWORD, __int64))*(&Eac_Table + 4))(Eac_Table, 0i64, thread_id_table_start);
}
return 0;
}
thread_id_table = (unsigned int *)thread_id_table_start;
thread_id_table_1 = (unsigned int *)thread_id_table_start;
thread_table_end = (unsigned int *)thread_list[1];
v36 = (_QWORD *)thread_list[1];
while ( thread_id_table != thread_table_end )
{
v25 = 0;
v26 = 0i64;
v27 = 0x7FFFFFFF;
v28 = 0x7FFFFFFF;
v29 = 0i64;
_mm_store_si128((__m128i *)&v30, (__m128i)0i64);
_mm_store_si128((__m128i *)&result_info, (__m128i)0i64);
v32 = 0i64;
threadid = *thread_id_table;
thread_handle = eAc_vmcall_OpenThread_0(0x40i64);
v2 = v2 * (double)*(signed int *)(thread_id_table_start + 4 * v13 - 8);
if ( !thread_handle )
goto LABEL_22;
thread_startaddress = 0i64;
if ( !eAc_NtQueryInformationThread_ThreadBasicInformation(thread_handle, (char *)&qword_18[0x12] + (_QWORD)&v21)
|| (is_success = 1,
!eAc_NtQueryInformationThread_ThreadQuerySetWin32StartAddress(
v1,
(__int64 *)((char *)&qword_18[1] + (_QWORD)&v21))) )
{
is_success = 0;
}
eAc_vmcall_CloseHandle_1(v1);
if ( is_success )
{
*(_DWORD *)((char *)&v21 + (_QWORD)&qword_18[3] + 4) = thread_basic_info;
v17 = *(__int64 *)((char *)&qword_18[19] + (_QWORD)&v21);
*(__int64 *)((char *)&qword_18[4] + (_QWORD)&v21) = v17;
*(_DWORD *)((char *)&qword_18[5] + (_QWORD)&v21) = v38;
*(_DWORD *)((char *)&v21 + (_QWORD)&qword_18[5] + 4) = v39;
*(__int64 *)((char *)&qword_18[6] + (_QWORD)&v21) = thread_startaddress;
if ( v17 )
{
*(__int64 *)((char *)&qword_18[7] + (_QWORD)&v21) = *(_QWORD *)(v17 + 8);
*(__int64 *)((char *)&qword_18[8] + (_QWORD)&v21) = *(_QWORD *)(v17 + 16);
}
eAc_stack_Walker(*thread_id_table, v16, (struct_stackwalkBuffer *)((char *)&qword_18[9] + (_QWORD)&v21));
LABEL_22:
CurrentEntry = v3->CurrentEntry;
if ( v3->LastEntry == CurrentEntry )
{
reallocate_vector_thread_information((unsigned __int64)CurrentEntry, &v3->FirstEntry, (__int64)&threadid, v2);
}
else
{
memcpy_thread_information(v13, (__int64)CurrentEntry, (__int64)&threadid, v14);
v3->CurrentEntry += 0x48;
}
}
reset_thread_information_struct(&threadid);
++thread_id_table;
thread_id_table_1 = thread_id_table;
}
v19 = (char *)v3->FirstEntry != v3->CurrentEntry;
if ( thread_id_table_start )
{
v20 = (v35 - thread_id_table_start) & 0xFFFFFFFFFFFFFFFCui64;
if ( v20 >= 0x1000 )
{
v20 += 39i64;
thread_id_table_start = *(_QWORD *)(thread_id_table_start - 8);
}
if ( thread_id_table_start )
memset((void *)thread_id_table_start, 0, v20);
((void (__fastcall *)(_QWORD, _QWORD, __int64))*(&Eac_Table + 4))(Eac_Table, 0i64, thread_id_table_start);
}
return v19;
}
实际的回溯逻辑
bool __fastcall eAc_stack_Walker(unsigned int thread_id, __int64 nouse, struct_stackwalkBuffer *stackwalkBuffer_1)
{
struct_stackwalkBuffer *stackwalkBuffer; // rbx
unsigned int thread_id_1; // edi
__int64 ThreadHandle; // rax
__int64 ThreadHandle_1; // rsi
unsigned __int64 v7; // rdi
_DWORD *v8; // rax
__int64 v9; // r9
__int64 v10; // rax
__int64 v11; // rcx
char v12; // t0
unsigned int v13; // edx
unsigned __int64 v14; // rdi
__int64 v15; // rsi
unsigned int v16; // edx
unsigned __int64 v17; // rdi
unsigned __int64 v18; // rdi
signed int v19; // edx
unsigned __int64 v20; // rdi
unsigned int v21; // edx
char *v22; // rdx
unsigned int i; // edi
__int64 functionTableEntry; // rax
__int64 v25; // r9
char *result_info; // rdx
bool result; // al
__int64 v28; // [rsp+38h] [rbp-590h]
__int64 modulebase; // [rsp+40h] [rbp-588h]
__int64 context_rip_1; // [rsp+48h] [rbp-580h]
__int64 v31; // [rsp+50h] [rbp-578h]
struct_stackwalkBuffer *stackwalkBuffer_1_1; // [rsp+70h] [rbp-558h]
__int64 context; // [rsp+80h] [rbp-548h]
char Context; // [rsp+90h] [rbp-538h]
int v35; // [rsp+C0h] [rbp-508h]
__int64 context_rip; // [rsp+168h] [rbp-460h]
int v37; // [rsp+540h] [rbp-88h]
int v38; // [rsp+544h] [rbp-84h]
int v39; // [rsp+548h] [rbp-80h]
int v40; // [rsp+54Ch] [rbp-7Ch]
int v41; // [rsp+550h] [rbp-78h]
__int128 v42; // [rsp+558h] [rbp-70h]
int v43; // [rsp+568h] [rbp-60h]
__int16 v44; // [rsp+56Ch] [rbp-5Ch]
char v45; // [rsp+56Eh] [rbp-5Ah]
__int128 v46; // [rsp+570h] [rbp-58h]
char v47; // [rsp+580h] [rbp-48h]
stackwalkBuffer = stackwalkBuffer_1;
thread_id_1 = thread_id;
stackwalkBuffer_1_1 = stackwalkBuffer_1;
memset(&Context, 0, 0x4D0ui64);
v35 = 1048577;
LOBYTE(v31) = 0;
stackwalkBuffer->CurrentEntry = (char *)stackwalkBuffer->FirstEntry;
if ( (unsigned int)eAc_vmcall_getCurrentThreadId() == thread_id_1 )// /vmp GetCurrentThreadId
return 0;
ThreadHandle = eAc_vmcall_OpenThread(0x48i64, 0i64, thread_id_1);// //vmp call OpenThread
ThreadHandle_1 = ThreadHandle;
if ( !ThreadHandle )
return 0;
v7 = (unsigned int)eAc_vmcall_GetThreadContext(ThreadHandle, &context);
v8 = (_DWORD *)eAc_vmcall_CloseHandle(ThreadHandle_1);
--*(_DWORD *)v7;
v10 = (unsigned int)(*v8 + (_DWORD)v8);
*(_BYTE *)(v10 - 117) += v11;
v12 = *(_BYTE *)(v10 + 4 * v11);
*(_DWORD *)v10 += v10;
*(_BYTE *)(v10 - 123) += v11;
*(_BYTE *)v7 = __ROR1__(*(_BYTE *)v7, -124);
if ( !LODWORD(eAc_Talbe2[26]) || !eAc_Talbe2[27] )
{
v37 = -248590068;
v38 = -1389132837;
v39 = -994849557;
v40 = -271005371;
v41 = 409220960;
v13 = -323093467;
v14 = 0i64;
do
{
v13 = ~(((v13 ^ (v13 << 13)) >> 7) ^ v13 ^ (v13 << 13) ^ ((((v13 ^ (v13 << 13)) >> 7) ^ v13 ^ (v13 << 13)) << 17));
*(int *)((char *)&v37 + v14) ^= v13;
v14 += 4i64;
}
while ( v14 < 0x14 );
v15 = eAc_getMod(&v37, 0i64);
memset(&v37, 0, 0x14ui64);
if ( v15 )
{
if ( !eAc_Talbe2[26] )
{
_mm_storeu_si128((__m128i *)&v42, _mm_load_si128((const __m128i *)&qword_D5AC0[234]));
v43 = 1692400592;
v44 = -18290;
v45 = 77;
v16 = 304514278;
v17 = 0i64;
do
{
*(_DWORD *)((char *)&v42 + v17) ^= v16;
v17 += 4i64;
v16 = __ROR4__(18788 * v16 + 6576056, 2);
}
while ( v17 < 0x14 );
v18 = 20i64;
do
{
*((_BYTE *)&v42 + v18) ^= v16;
v16 >>= 8;
++v18;
}
while ( v18 < 0x17 );
eAc_Talbe2[26] = eAc_get_Exp(v15, &v42, 0i64);
memset(&v42, 0, 0x17ui64);
}
if ( !eAc_Talbe2[27] )
{
_mm_storeu_si128((__m128i *)&v46, _mm_load_si128((const __m128i *)&qword_D5AC0[364]));
v47 = -91;
v19 = 1195700990;
v20 = 0i64;
do
{
*(_DWORD *)((char *)&v46 + v20) ^= v19;
v20 += 4i64;
v21 = ((v19 ^ (unsigned int)(v19 << 13)) >> 7) ^ v19 ^ (v19 << 13);
v19 = __ROL4__((v21 << 17) ^ v21, 4);
}
while ( v20 < 0x10 );
v47 ^= v19;
eAc_Talbe2[27] = eAc_get_Exp(v15, &v46, 0i64);
memset(&v46, 0, 0x11ui64);
}
}
if ( !eAc_Talbe2[26] || !eAc_Talbe2[27] )
{
result = 0;
memset(&v37, 0, 0x14ui64);
return result;
}
memset(&v37, 0, 0x14ui64);
v10 = context_rip;
}
v28 = v10;
v22 = stackwalkBuffer->CurrentEntry;
if ( stackwalkBuffer->LastEntry == v22 )
{
allocate_mem(&stackwalkBuffer->FirstEntry, v22, &v28, v9);
}
else
{
*(_QWORD *)v22 = v10;
stackwalkBuffer->CurrentEntry += 8;
}
for ( i = 0; i < 9; ++i ) // //try count
{
functionTableEntry = ((__int64 (__fastcall *)(__int64, __int64 *, _QWORD))eAc_Talbe2[26])(
context_rip,
&modulebase,
0i64); // /RtlLookupFunctionEntry
if ( functionTableEntry )
{
((void (__fastcall *)(_QWORD, __int64, __int64, __int64))eAc_Talbe2[27])(
0i64,
modulebase,
context_rip,
functionTableEntry); // //RtlVirtualUnwind
if ( !context_rip )
return (char *)stackwalkBuffer->FirstEntry != stackwalkBuffer->CurrentEntry;
context_rip_1 = context_rip;
result_info = stackwalkBuffer->CurrentEntry;
if ( stackwalkBuffer->LastEntry == result_info )
{
allocate_mem(&stackwalkBuffer->FirstEntry, result_info, &context_rip_1, v25);
}
else
{
*(_QWORD *)result_info = context_rip;
stackwalkBuffer->CurrentEntry += 8;
}
}
}
return (char *)stackwalkBuffer->FirstEntry != stackwalkBuffer->CurrentEntry;
}
筛选搜集到的信息
char __fastcall eAc_check_stack_walker_entry(__int64 a1, __int64 a2, __int64 *a3, _QWORD *a4, struct_a5 *a5)
{
__m128i v5; // xmm0
__int64 v6; // r12
__int64 *v7; // r14
struct_v8 *v8; // rdi
char success; // si
_QWORD *v10; // r13
unsigned __int64 start_address; // rcx
__int64 v12; // r8
__int128 v13; // xmm0
__int128 v14; // xmm1
__int128 v15; // xmm0
__int64 v16; // r8
__int64 v17; // r9
__int64 v18; // rcx
__int64 v19; // r8
__int64 v20; // rdx
__int64 v21; // rax
__int64 v22; // r8
__int64 v23; // r9
__int64 v24; // r9
unsigned __int64 *Localentry; // rbx
unsigned __int64 *CurrentEntry; // rdi
__int64 v27; // r8
__int128 v28; // xmm0
__int128 v29; // xmm1
__int128 v30; // xmm0
__int64 v31; // r8
struct_v34 *v32; // rcx
__int64 *v33; // rdx
struct_v34 *v34; // rax
char v36; // [rsp+18h] [rbp-E8h]
__int128 v37; // [rsp+30h] [rbp-D0h]
__int128 v38; // [rsp+50h] [rbp-B0h]
__int128 v39; // [rsp+60h] [rbp-A0h]
__int128 mem_protect; // [rsp+80h] [rbp-80h]
char v41; // [rsp+90h] [rbp-70h]
__int128 v42; // [rsp+A0h] [rbp-60h]
char v43; // [rsp+B0h] [rbp-50h]
__int128 v44; // [rsp+C0h] [rbp-40h]
__int64 v45; // [rsp+D0h] [rbp-30h]
__int128 v46; // [rsp+D8h] [rbp-28h]
__int128 v47; // [rsp+E8h] [rbp-18h]
__int128 v48; // [rsp+F8h] [rbp-8h]
char v49; // [rsp+108h] [rbp+8h]
char v50; // [rsp+128h] [rbp+28h]
v5 = _mm_load_si128((const __m128i *)&qword_D5A48[3]);
v6 = a2;
v7 = a3;
_mm_store_si128((__m128i *)&v42, v5);
v8 = (struct_v8 *)a1;
_mm_store_si128((__m128i *)&v44, v5);
success = 0;
v10 = a4;
memset((char *)&v37, 0, 0x30ui64);
memset((char *)&v39, 0, 0x70ui64);
*v10 = 0i64;
memset(&a5->char0, 0, 0x70ui64);
if ( sub_C1EB4(v6) || (unsigned int)sub_F03CE() == v8->dword0 )
{
success = 0;
goto LABEL_39;
}
start_address = v8->thread_startaddress;
if ( start_address && eac_get_memory_info(start_address, v6, (__int128 *)((char *)&v38 + 8)) )// //获取线程起始位置的内存属性
{
if ( (BYTE4(mem_protect) & 0x10 || BYTE4(mem_protect) & 0x20 || BYTE4(mem_protect) & 0x40)// //check page_Exec page_read_Exec page_readwrite_Exec
&& !in_Eac_or_in_module(eac_inc, v8->thread_startaddress) )//是否是eac内部反作弊的模块
{
v13 = *(__int128 *)((char *)&v38 + 8);
v14 = *(__int128 *)((char *)&v39 + 8);
success = 1;
*v10 = v8->thread_startaddress;
*(_OWORD *)&a5->char0 = v13;
v15 = mem_protect;
a5->oword10 = v14;
a5->oword20 = v15;
allocate((__int64)&a5[1], (__int64)&v41, v12);
allocate((__int64)&a5[1].oword20, (__int64)&v43, v16);
v18 = *v7;
v19 = *((_QWORD *)&v38 + 1);
v20 = *v7;
v21 = *(_QWORD *)(*v7 + 8);
while ( !*(_BYTE *)(v21 + 25) )
{
if ( *(_QWORD *)(v21 + 32) >= *((_QWORD *)&v38 + 1) )
{
v18 = v21;
v21 = *(_QWORD *)v21;
}
else
{
v21 = *(_QWORD *)(v21 + 16);
}
}
if ( v18 == v20 || *((_QWORD *)&v38 + 1) < *(_QWORD *)(v18 + 32) )
{
v18 = *v7;
v20 = *v7;
}
if ( v18 == v20 )
{
LABEL_18:
v46 = *(__int128 *)((char *)&v38 + 8);
v45 = v19;
v48 = mem_protect;
v47 = *(__int128 *)((char *)&v39 + 8);
sub_2A3EC(&v49, (__int64)&v41, v19, v17);
sub_2A3EC(&v50, (__int64)&v43, v22, v23);
sub_C4BAC((__int64 **)v7, (__int64)&v36, &v45, v24);
free((__int64)&v50);
free((__int64)&v49);
goto LABEL_39;
}
}
}
else
{
success = 1;
}
Localentry = v8->Localentry;
CurrentEntry = v8->CurrentEntry;
while ( Localentry != CurrentEntry ) // //这里eac检查每个线程回溯到的地址
{
if ( *Localentry
&& eac_get_memory_info(*Localentry, v6, (__int128 *)((char *)&v38 + 8))// //获取retaddress所属内存块的属性
&& (BYTE4(mem_protect) & 0x10 || BYTE4(mem_protect) & 0x20 || BYTE4(mem_protect) & 0x40)// //是否属于可执行位置
&& !in_Eac_or_in_module(eac_inc, *Localentry) )// //是否属于eac内部反作弊
{
v28 = *(__int128 *)((char *)&v38 + 8);
v29 = *(__int128 *)((char *)&v39 + 8);
success = 1;
*v10 = *Localentry;
*(_OWORD *)&a5->char0 = v28;
v30 = mem_protect;
a5->oword10 = v29;
a5->oword20 = v30;
allocate((__int64)&a5[1], (__int64)&v41, v27);
allocate((__int64)&a5[1].oword20, (__int64)&v43, v31);
v32 = (struct_v34 *)*v7;
v19 = *((_QWORD *)&v38 + 1);
v33 = (__int64 *)*v7;
v34 = *(struct_v34 **)(*v7 + 8);
while ( !v34->byte19 )
{
if ( v34->qword20 >= *((_QWORD *)&v38 + 1) )
{
v32 = v34;
v34 = (struct_v34 *)v34->int640;
}
else
{
v34 = (struct_v34 *)v34->qword10;
}
}
if ( v32 == (struct_v34 *)v33 || *((_QWORD *)&v38 + 1) < v32->qword20 )
{
v32 = (struct_v34 *)*v7;
v33 = (__int64 *)*v7;
}
if ( v32 == (struct_v34 *)v33 )
goto LABEL_18;
}
++Localentry;
}
LABEL_39:
free((__int64)&v43);
free((__int64)&v41);
return success;
}
检查是否属于eac反作弊模块
// //检查是否属于eac反作弊模块
char __fastcall in_Eac_or_in_module(__int64 eac_inc, __int64 check_address)
{
unsigned __int64 v2; // rbx
char credible; // si
unsigned __int64 check_address_1; // rbp
__int64 eac_global_data_1; // r14
__int64 module_info; // rbx
__int64 current_entry; // rdi
__int64 last_entry; // r15
unsigned __int64 anticheat_base; // rcx
unsigned __int64 v11; // [rsp+40h] [rbp+8h]
v11 = v2;
credible = 0;
check_address_1 = check_address;
eac_global_data_1 = eac_inc;
if ( !check_address )
return 0;
module_info = eac_inc + 0x84C0;
sub_1B98B5();
current_entry = *(_QWORD *)(module_info + 40);
for ( last_entry = *(_QWORD *)(module_info + 48); current_entry != last_entry; current_entry += 0x28i64 )// //查找当前地址属于哪个模块区域
{
if ( *(_QWORD *)(current_entry + 8)
&& *(_QWORD *)(current_entry + 16)
&& v11 >= *(_QWORD *)current_entry
&& v11 < *(_QWORD *)current_entry + (unsigned __int64)*(unsigned int *)(current_entry + 32) )
{
break;
}
}
sub_16AD61(module_info);
if ( current_entry != last_entry // //是否属于easyanticheat内部反作弊模块
|| (anticheat_base = *(_QWORD *)(eac_global_data_1 + 0x7488), check_address_1 >= anticheat_base)
&& check_address_1 < *(_QWORD *)(eac_global_data_1 + 0x7490) + anticheat_base )
{
credible = 1;
}
return credible;
}
**将搜集到的信息 发送到EAC反作弊服务器 位于用于某一个线程
{
*(_QWORD *)&data.oword10 = 0i64;
_mm_storeu_si128((__m128i *)&data, (__m128i)0i64);
if ( (unsigned __int8)sub_C08FC(j + 5, &data) )
eAc_anticheat::send_packet( // //将筛选好的数据发送到EasyAntiCheat反作弊服务器
eac_inc,
281i64,
*(_QWORD *)&data.char0,
(unsigned int)(*((_DWORD *)&data.char0 + 2) - *(_DWORD *)&data.char0));
v21 = v3[7];
if ( v3[8] == v21 )
{
sub_99454((__int64 *)v3 + 6, (__int64)v3[7], j + 5);
}
else
{
*(_OWORD *)v21 = *(_OWORD *)(j + 5);
*((_OWORD *)v21 + 1) = *(_OWORD *)(j + 7);
*((_OWORD *)v21 + 2) = *(_OWORD *)(j + 9);
sub_2A3EC(v21 + 6, (__int64)(j + 11), v19, v20);
sub_2A3EC(v21 + 10, (__int64)(j + 15), v22, v23);
v3[7] += 14;
}
sub_29A08(&data);
}
“EasyAntiCheat UserMode AntiCheat StackWalker”上的2条回复
如果有VT的话,EAC大部分的检测都是形同虚设。他们加入了VT时间校验之后,暂时还没发现什么很好的对抗他的方法。
话说,StackWalker具体指的是什么意思呢?
StackWalker 栈回溯
一般用于检测内部瓜
在我观察它的时候
它会回溯游戏所有线程调用 能够抓取noimage的调用 当然这个比较看时机。比如源引擎的vmt hook这里也是能够回溯出来的。