一、前期准备工作
工具:
IDA7.0
CheatEngine7.5
Visual Studio2022
游戏:
cs2
二、代码定位
1.打开游戏后双击CS2 dumper , dump工具会在运行目录下生成output文件夹。
2.在offsets.cs和client_dll.cs里边找到所需的偏移
client_dll.cs:
public const nint m_iIDEntIndex = 0x1458; // CEntityIndex
offsets.cs:
public const nint dwLocalPlayerPawn = 0x1874040;
因为CEntityIndex是PlayerPawn里边的,打开完整的dump文件就能看到继承关系了,这里我就不过多赘述。
经过我的验证,当鼠标准星瞄准人物的时候,它的值是一个hpawn(上一篇解密自动开枪的帖子有说明)
我们不妨假设这里的原理是从准星发射一条射线,用于检测碰撞到的实体,然后返回实体地址
所以我们对这个地址找访问地址。
7FFE251B808F - 8B 9E 58140000 - mov ebx,[rsi+00001458]
7FFE251B80A1 - C7 86 58140000 FFFFFFFF - mov [rsi+00001458],FFFFFFFF
7FFE251B869B - 89 86 58140000 - mov [rsi+00001458],eax
7FFE2519E0B3 - 8B 86 58140000 - mov eax,[rsi+00001458]
我们发现有4条访问代码,前三句距离很近,所以我们先分析第一条。
进入反汇编代码后,右键选择当前函数,保存一下这个函数头
client.dll+878040 - 40 55 - push rbp
三、IDA加载client.dll
我们打开IDA,把dll拖进IDA里边,dll路径:Steam\steamapps\common\Counter-Strike Global Offensive\game\csgo\bin\win64\client.dll
等待IDA分析 完毕,让后修正一下偏移
在菜单栏里选择Edit–>Segment–>Rebase Program…
然后在IDA里边搜索刚刚那个函数,我们已经修正过偏移了,所以可以直接搜CE里client+878040里的878040跳到目标函数。
四、动态调试+静态分析
再次回到CE,从client+878040函数有下断点,然后单步跟,跟到这条指令时
client.dll+8783BE - E8 5DCC8EFF - call client.dll+165020
我们看看这时候寄存器的值,根据x64调用约定看他传了什么参数。
第一个参数RCX==7FFE2618D090,他是这条指令传进来的,是固定的
client.dll+878390 - 48 8B 0D D135FD00 - mov rcx,[client.dll+184B968] { (7FFE2618D090) }
第二个参数RDX==8D023FF988,我们把它放到内存浏览区域,发现他是个三维坐标
第三个参数R8==8D023FF978,我们把它放到内存浏览区域,发现他也是个三维坐标
到这里相比大家已经猜到,两个参数很像射线最终的起始点和终点,我们修改这两个值验证一下
游戏控制台输入
cl_showpos 1
可以显示本人坐标信息
然后用准星对准敌人 ,输入命令将敌人坐标设为当前玩家位置,这样就确定了敌人的坐标
ent_setpos -167.62 -84.28 63.87//坐标为自己坐标
然后我们躲到掩体后边,记录当前pos位置
400.52 792.05 63.87
然后我们回到刚刚那条call,下断点。
把第二个参数RDX和第三个参数R8分别改为自己坐标和敌人坐标之后,
单步走一下这个call,看到rax返回了25C1AF10601
然后我们把断点取消,出掩体,继续修改参数,发现rax返回了25C1AF10600
取一个字节,能发现当我们和敌人之间有掩体时,就返回01,没有障碍就返回00。
到这里,我们就成功定位了和障碍判断有关的函数
client.dll+165020 //和障碍判断有关的函数
我们用IDA看看client.dll+165020的逻辑,代码如下
char __fastcall sub_165020(__int64 a1, __int64 a2, __int64 a3, _DWORD *a4, __int64 a5, char a6, __int64 a7)
{
__int64 v7; // r15
signed int v8; // edi
__int64 v9; // rsi
_DWORD *v10; // rbx
__int64 v11; // rbp
unsigned __int64 *v12; // r14
int v13; // ecx
__int64 v14; // r9
signed __int64 v15; // rax
__int64 v17; // [rsp+30h] [rbp-88h]
int v18; // [rsp+38h] [rbp-80h]
int v19; // [rsp+3Ch] [rbp-7Ch]
char v20; // [rsp+58h] [rbp-60h]
void **v21; // [rsp+60h] [rbp-58h]
__int64 v22; // [rsp+68h] [rbp-50h]
__int128 v23; // [rsp+70h] [rbp-48h]
int v24; // [rsp+80h] [rbp-38h]
int v25; // [rsp+84h] [rbp-34h]
int v26; // [rsp+88h] [rbp-30h]
int v27; // [rsp+8Ch] [rbp-2Ch]
__int16 v28; // [rsp+90h] [rbp-28h]
int v29; // [rsp+92h] [rbp-26h]
char v30; // [rsp+96h] [rbp-22h]
char v31; // [rsp+97h] [rbp-21h]
char v32; // [rsp+98h] [rbp-20h]
v7 = a1;
_mm_store_si128((__m128i *)&v23, (__m128i)0i64);
v8 = -1;
v9 = 0i64;
v25 = -1;
v27 = -1;
v31 = v31 & 0xC9 | 0x49;
v29 = 983040;
v21 = &CTraceFilter::`vftable';
v10 = a4;
v11 = a3;
v30 = a6;
v12 = (unsigned __int64 *)a2;
v22 = a5;
v32 = 0;
v24 = sub_627C80(a4);
v28 = sub_634830(v10);
if ( v10
&& (!(*(__int64 (__fastcall **)(_DWORD *))(*(_QWORD *)v10 + 456i64))(v10)
|| !(*(_BYTE *)((*(__int64 (__fastcall **)(_DWORD *))(*(_QWORD *)v10 + 456i64))(v10) + 90) & 4)) )
{
v13 = v10[272];
if ( v13 != -1 )
{
if ( qword_195B678 )
{
if ( v13 != -2 )
{
v14 = *(_QWORD *)(qword_195B678 + 8 * ((unsigned __int64)(v13 & 0x7FFF) >> 9));
if ( v14 )
{
v15 = v14 + 120i64 * (v13 & 0x1FF);
if ( v15 )
{
if ( *(_DWORD *)(v15 + 16) == v13 )
v9 = *(_QWORD *)v15;
}
}
}
}
}
v8 = sub_627C80(v9);
}
v17 = 0i64;
v18 = 0;
v26 = v8;
v19 = 0;
v20 = 0;
return sub_660AD0(v7, (__int64)&v17, v12, v11, (__int64)&v21, a7);
}
我们发现他返回的是
sub_660AD0(v7, (__int64)&v17, v12, v11, (__int64)&v21, a7)
我们双击它进去看看
char __fastcall sub_660AD0(__int64 a1, __int64 a2, unsigned __int64 *a3, __int64 a4, __int64 a5, __int64 a6)
{
__int64 *v6; // rsi
__int64 v7; // rbx
__int64 v8; // r14
unsigned __int64 *v9; // r15
__int64 v10; // r13
__int64 v11; // xmm3_8
float v12; // xmm0_4
float v13; // xmm2_4
float v14; // xmm1_4
_BYTE *v15; // r12
bool v16; // zf
_BYTE *v17; // rbx
float v18; // xmm1_4
char v19; // di
__int64 v20; // rcx
__int64 v21; // rcx
int v22; // ebx
__int64 v23; // rsi
int v24; // eax
int v25; // edx
unsigned __int16 v26; // ax
__int64 v27; // r9
signed __int64 v28; // rax
__int64 v29; // rcx
char *v30; // rdx
bool v31; // bl
_BYTE *v32; // rax
__int64 v33; // rax
__int64 v34; // rax
char result; // al
float *v36; // [rsp+20h] [rbp-E0h]
__int128 *v37; // [rsp+28h] [rbp-D8h]
float v38; // [rsp+40h] [rbp-C0h]
float v39; // [rsp+44h] [rbp-BCh]
float v40; // [rsp+48h] [rbp-B8h]
__int128 v41; // [rsp+50h] [rbp-B0h]
__int128 v42; // [rsp+60h] [rbp-A0h]
__int64 v43; // [rsp+70h] [rbp-90h]
__int64 v44; // [rsp+78h] [rbp-88h]
int v45; // [rsp+80h] [rbp-80h]
int v46; // [rsp+84h] [rbp-7Ch]
int v47; // [rsp+88h] [rbp-78h]
char v48; // [rsp+8Ch] [rbp-74h]
__int64 v49; // [rsp+90h] [rbp-70h]
char v50; // [rsp+9Ch] [rbp-64h]
int v51; // [rsp+B0h] [rbp-50h]
char *v52; // [rsp+B8h] [rbp-48h]
int v53; // [rsp+C0h] [rbp-40h]
unsigned int v54; // [rsp+C4h] [rbp-3Ch]
char v55; // [rsp+C8h] [rbp-38h]
__int64 v56; // [rsp+2110h] [rbp+2010h]
v56 = a1;
v6 = (__int64 *)a1;
v7 = a4;
v8 = a6;
v9 = a3;
v10 = a2;
if ( dword_1A7B928 > *(_DWORD *)(*(_QWORD *)(__readgsqword(0x58u) + 8i64 * (unsigned int)TlsIndex) + 104i64) )
{
Init_thread_header(&dword_1A7B928);
if ( dword_1A7B928 == -1 )
{
qword_1A7B920 = VProf_FindOrCreateCounter("Physics/TraceShape (Client)", 0i64);
atexit(nullsub_398);
Init_thread_footer(&dword_1A7B928);
}
}
++*(_QWORD *)qword_1A7B920;
sub_F66000(v8);
*(_QWORD *)(v8 + 120) = *v9;
*(_DWORD *)(v8 + 128) = *((_DWORD *)v9 + 2);
*(_QWORD *)(v8 + 132) = *(_QWORD *)v7;
*(_DWORD *)(v8 + 140) = *(_DWORD *)(v7 + 8);
*(_BYTE *)(v8 + 182) = *(_BYTE *)(v10 + 40);
if ( !*v6 )
goto LABEL_60;
v11 = *v9;
v12 = *(float *)v7 - COERCE_FLOAT(*v9);
v13 = *(float *)(v7 + 4) - COERCE_FLOAT(_mm_shuffle_ps((__m128)*v9, (__m128)*v9, 85));
v14 = *(float *)(v7 + 8);
v15 = (_BYTE *)a5;
v16 = *(_WORD *)(a5 + 52) == 1;
v17 = (_BYTE *)(a5 + 8);
v18 = v14 - *((float *)v9 + 2);
v38 = v12;
v39 = v13;
v49 = v11;
v40 = v18;
if ( !v16 )
sub_79B650(a5 + 8);
v19 = 0;
if ( v15[56] )
{
v52 = 0i64;
v53 = 128;
v54 = 2147483648;
if ( (unsigned __int64)&v52 & 7 )
sub_11554E0();
v21 = *v6;
v52 = &v55;
v51 = 0;
(*(void (__fastcall **)(__int64, int *, __int64, unsigned __int64 *, float *, _BYTE *))(*(_QWORD *)v21 + 2080i64))(
v21,
&v51,
v10,
v9,
&v38,
v17);
if ( v51 )
{
sub_60DBC0(v52, &v52[64 * (signed __int64)v51], (signed __int64)v51 << 6 >> 6, (unsigned __int8)v56);
v22 = 0;
if ( v51 > 0 )
{
v23 = 0i64;
while ( 1 )
{
v24 = (*(__int64 (**)(void))(**(_QWORD **)&v52[v23] + 1192i64))();
v25 = v24;
if ( v24 != -1
&& qword_195B678
&& v24 != -2
&& (v26 = v24 & 0x7FFF, (v27 = *(_QWORD *)(qword_195B678 + 8 * ((unsigned __int64)v26 >> 9))) != 0)
&& (v28 = v27 + 120i64 * (v26 & 0x1FF)) != 0
&& *(_DWORD *)(v28 + 16) == v25 )
{
v29 = *(_QWORD *)v28;
}
else
{
v29 = 0i64;
}
if ( v25 == 0x8000
|| v29 && (*(unsigned __int8 (__fastcall **)(_BYTE *, __int64))(*(_QWORD *)v15 + 8i64))(v15, v29) )
{
break;
}
++v22;
v23 += 64i64;
if ( v22 >= v51 )
goto LABEL_26;
}
v37 = (__int128 *)&v52[64 * (signed __int64)v22];
v36 = &v38;
sub_61EB40(v56, v8);
}
LABEL_26:
v17 = v15 + 8;
}
v30 = v52;
v51 = 0;
if ( v53 >= 0 && v52 != &v55 )
{
if ( v52 && !(v54 & 0xC0000000) )
(*(void (__fastcall **)(_QWORD, char *))(*g_pMemAlloc + 24i64))(g_pMemAlloc, v52);
v30 = &v55;
v52 = &v55;
v53 = 128;
v54 = v54 & 0x3FFFFFFF | 0x80000000;
}
if ( !(v54 & 0xC0000000) && v30 )
(*(void (__cdecl **)(_QWORD, char *))(*g_pMemAlloc + 24i64))(g_pMemAlloc, v30);
}
else
{
v20 = *v6;
v43 = 0i64;
_mm_store_si128((__m128i *)&v41, (__m128i)0i64);
v42 = 0i64;
v44 = 0i64;
v45 = 0;
v46 = 1065353216;
v47 = -1;
v48 = 0;
(*(void (__fastcall **)(__int64, __int128 *, __int64, unsigned __int64 *, float *, _BYTE *))(*(_QWORD *)v20 + 2072i64))(
v20,
&v41,
v10,
v9,
&v38,
v17);
v37 = &v41;
v36 = &v38;
sub_61EB40((__int64)v6, v8);
}
if ( (*v17 >> 1) & 1 )
{
LODWORD(v37) = 0;
sub_13B4F0(off_1834420, v10, v9, &v38, v15, v37, v8);
}
v31 = *(float *)(v8 + 172) < 1.0 || *(_BYTE *)(v8 + 183);
v32 = (_BYTE *)sub_11469A0(&unk_1A7B378, 0xFFFFFFFFi64);
if ( !v32 )
v32 = *(_BYTE **)(qword_1A7B380 + 8);
if ( *v32 && qword_1A798B8 )
{
if ( !v31 )
v19 = -1;
if ( *(_BYTE *)(v10 + 40) )
{
sub_10A60A0(v10, &v49);
v33 = *(_QWORD *)qword_1A798B8;
LOBYTE(v56) = -1;
BYTE1(v56) = v19;
WORD1(v56) = -256;
(*(void (__fastcall **)(__int64, __int64, __int64, __int64 *, char *, __int128 *, __int64 *, signed __int64, _QWORD, _QWORD))(v33 + 376))(
qword_1A798B8,
v8 + 120,
v8 + 132,
&v49,
&v50,
&xmmword_1B47A80,
&v56,
4607182418800017408i64,
*(_QWORD *)&v38,
*(_QWORD *)&v40);
}
else
{
LOBYTE(v56) = -1;
v34 = *(_QWORD *)qword_1A798B8;
BYTE1(v56) = v19;
WORD1(v56) = -256;
LOBYTE(v36) = 1;
(*(void (__fastcall **)(__int64, __int64, __int64, __int64 *, float *, signed __int64))(v34 + 96))(
qword_1A798B8,
v8 + 120,
v8 + 132,
&v56,
v36,
4607182418800017408i64);
}
}
if ( *(float *)(v8 + 172) < 1.0 || *(_BYTE *)(v8 + 183) )
result = 1;
else
LABEL_60:
result = 0;
return result;
}
我们惊奇的发现,这就是真正的,游戏引擎内部的射线检测call
qword_1A7B920 = VProf_FindOrCreateCounter("Physics/TraceShape (Client)", 0i64);
五、IDA静态分析调用参数
我们先来看看sub_165020里是这么调用TraceShape的
return sub_660AD0(v7, (__int64)&v17, v12, v11, (__int64)&v21, a7);
第一个参数v7:
我们按X查找引用
可以看到v7是外边传进来的,我们出去看看这个a1是什么
sub_165020((__int64)off_184B968, (__int64)&v39, (__int64)&v36, v19, 798723i64, 3, (__int64)&v49);
那么v7是一个固定的client+184B968,第一个参数分析完毕。
第二个参数v17:
同样查找引用
所以v17是常数0,第二个参数分析完毕
第三、四个参数v12,v11:
也是外边传进来的,上边提到过a2是一个坐标,也就是起点坐标
所以第三个参数v12是一个起始坐标的地址
同理v11是一个终点坐标的地址
第五个参数&v21:
v21 = &CTraceFilter::`vftable';
我们发现v21是一个结构体,对应的汇编代码为
client.dll+165066 - 48 8D 05 BB7B0D01 - lea rax,[client.dll+123CC28] { (7FFE24A88270) }
所以v21是一个固定的client.dll+123CC28
第六个参数a7:
也是外边传进来的,我们继续出去看看
发现和a7(v49)有关的是sub_F66000(&v49),我们在进去看看
_int64 __fastcall sub_F66000(__int64 a1)
{
__int64 v1; // rbx
__int64 v2; // rcx
__int64 v3; // rax
__int64 v4; // rax
__int64 result; // rax
v1 = a1;
v2 = qword_1B58358;
if ( !qword_1B58358 )
{
v3 = sub_82DA30(160i64);
if ( v3 )
v2 = sub_F65E70(v3);
else
v2 = 0i64;
qword_1B58358 = v2;
}
v4 = qword_1B58360;
*(_QWORD *)(v1 + 144) = 0i64;
*(_DWORD *)(v1 + 152) = 0;
*(_QWORD *)v1 = v4;
*(_WORD *)(v1 + 180) = -1;
*(_DWORD *)(v1 + 176) = -1;
*(_DWORD *)(v1 + 172) = 1065353216;
*(_WORD *)(v1 + 183) = 0;
*(_QWORD *)(v1 + 40) = 0i64;
*(_DWORD *)(v1 + 168) = 0;
*(_QWORD *)(v1 + 8) = 0i64;
*(_QWORD *)(v1 + 16) = v2;
*(_QWORD *)(v1 + 24) = 0i64;
*(_QWORD *)(v1 + 32) = 0i64;
*(_OWORD *)(v1 + 48) = xmmword_1B47A70;
*(_OWORD *)(v1 + 64) = xmmword_1B47A80;
*(_QWORD *)(v1 + 156) = 0i64;
result = 0i64;
*(_DWORD *)(v1 + 164) = 0;
*(_BYTE *)(v1 + 182) = 0;
return result;
}
发现这是个初始化结构体
到此,我们的参数分析就完毕了,至于结构体里边有什么,我就不去深究了,因为胆识返回值就已经能做障碍判断了
六、调用测试
我能力不够,没把发把结构体完整的逆出来,只知道里边有一个PlayerEntity,一下是我的实例代码
void* TraceShape(float* Start, float* End)
{
PBYTE Module = (PBYTE)GetModuleHandle("client.dll");
// 射线检测CALL
using TTaceRay = bool (*)(void*, float*, float*, float*, Tracestruct1*, Tracestruct2*);
TTaceRay __TaceRay = (TTaceRay)(Module + 0x660AD0);
// 结构体初始化CALL
using initstruct1 = void (*)(void* __this, void* PlayerPawn, long long a3, char a4, short a5);
using initstruct2 = void (*)(void* __this);
initstruct1 initstruct1API = (initstruct1)(Module + 0x1AC530);//这里也可以直接传固定的[client+184B968]
initstruct2 initstruct2API = (initstruct2)(Module + 0xF66000);
bool TraceState = false;
void* a1 = *(void**)(Module + 0x184B968);//client.dll + 878390 - 48 8B 0D D135FD00 - mov rcx, [client.dll + 184B968] { (7FFD3F6FD090) }
float a2[50] = { };
void* PlayerPawn = *(void**)(Module + cs2_dumper::offsets::client_dll::dwLocalPlayerPawn);
Tracestruct1 a5;
memset(&a5, 0, sizeof(a5));
initstruct1API(a5.Pad, PlayerPawn, 0xC3003, 3, 0xf);
Tracestruct2 a6;
memset(&a6, 0, sizeof(a6));
initstruct2API(a6.Pad);
TraceState = __TaceRay(a1, a2, Start, End, &a5, &a6);
if (TraceState)
{
return a6.PlayerEntity;
}
return nullptr;
}
然后在绘制回调里把这个加进去然后根据掩体判断绘制不同颜色
uint64_t VisalbleEntity = (uint64_t)TraceShape(StarPos, EndPos);
if (VisalbleEntity == current)IsVisalble = true;
ImColor TargetColor = IsVisalble ? ImColor(0, 255, 0) : ImColor(255, 0, 0);
就完成了!