cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

cs2 障碍判断——寻找射线检测(TraceShape)

一、前期准备工作

工具:

IDA7.0

CheatEngine7.5

Visual Studio2022

 cs2 dump

游戏:

cs2

二、代码定位

1.打开游戏后双击CS2 dumper , dump工具会在运行目录下生成output文件夹。

图片[1]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

 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…

图片[2]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

然后在IDA里边搜索刚刚那个函数,我们已经修正过偏移了,所以可以直接搜CE里client+878040里的878040跳到目标函数。

图片[3]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

四、动态调试+静态分析

再次回到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,我们把它放到内存浏览区域,发现他是个三维坐标

图片[4]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

第三个参数R8==8D023FF978,我们把它放到内存浏览区域,发现他也是个三维坐标

图片[5]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

到这里相比大家已经猜到,两个参数很像射线最终的起始点和终点,我们修改这两个值验证一下

游戏控制台输入

cl_showpos 1

可以显示本人坐标信息

然后用准星对准敌人 ,输入命令将敌人坐标设为当前玩家位置,这样就确定了敌人的坐标

ent_setpos  -167.62 -84.28 63.87//坐标为自己坐标

然后我们躲到掩体后边,记录当前pos位置

400.52 792.05 63.87

然后我们回到刚刚那条call,下断点。

图片[6]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

把第二个参数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查找引用

图片[7]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

可以看到v7是外边传进来的,我们出去看看这个a1是什么

sub_165020((__int64)off_184B968, (__int64)&v39, (__int64)&v36, v19, 798723i64, 3, (__int64)&v49);

那么v7是一个固定的client+184B968,第一个参数分析完毕。

第二个参数v17:

同样查找引用

图片[8]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

所以v17是常数0,第二个参数分析完毕

第三、四个参数v12,v11:

图片[9]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

也是外边传进来的,上边提到过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:

也是外边传进来的,我们继续出去看看

图片[10]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

发现和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);

图片[11]-cs2 障碍判断——寻找射线检测(TraceShape)-游戏安全逆向社区论坛-技术社区-学技术网

就完成了!

请登录后发表评论