40: Aimbot改(TraceLine導入)

概要

36回 のAimbotでは、壁の向こう側に敵がいる時でもそちらを狙い続けてしまい、 その間に敵に打たれるという欠点がありました。
今回は、AimbotでTraceLine関数を用いる事で、壁の向こう側にいる的を狙わないようにする事を目標にやっていきます。
チート対象のゲームは、x86でオープンソースのAssault Cube v1.2.0.2を使います。
Windows対象で、Visual Studio 2019を使用しC++で記述する。ソースコードはまとめてページの一番下にある。
この記事は、3639までの内容の続きなので、 まだ見ていない方はそちらから見ていただければ。

体力が0の敵を狙わないようにする

早速、36回のコードを編集する形で、やっていきたいのですが。その前に体力が0の敵を狙わないようにする処理だけ追加します。 とはいえ、やる事は以下を追加するだけです。 ※ 体力のオフセットとかは、他の方がまとめてくれている物 を使用しています。

TraceLine関数の導入

TraceLineを導入していきましょう。
まず、39回のTraceLineのリバース結果を活かして、以下のように定義します。 今回このTraceLine関数はコンパイラの最適化かなんかの理由で、普通の呼び出し規約が使われておらず、 eaxを引数として扱うという特殊な呼び出し規約になっています。そのため、TraceLine の呼び出しはインラインアセンブラで行うため、わざわざ関数型の定義をする必要が無いのですが、 一応引数をどんな順番で入れたらいいかとかが書いておくとわかりやすいので、書いておきます。

続いて、TraceLine関数のラッパー関数である IsVisible関数 を作成します。 ちなみに、0x24 はスタックに置く引数の合計サイズで、9個の引数 x 各4Byte = 36(0x24)Byte を関数からリターンしてきた後に、綺麗にしてるって感じです。traceresult.collided は、true なら「障害物がある」という事で、「敵は見えない」という事になるので、!traceresult.collided を返しています。

続いて、TraceLine関数のアドレスを割り出す処理を入れます。 後は、以下のように呼び出して終わりです!

精度の微調整

これでコアな機能は終了ではあるのですが、実際にこれでやってみると、 自分よりもちょっと上にいる敵に対してイマイチちゃんとエイムしてくれない感じになると思います。 今回、TraceLine の from は自分の座標を入れているわけですが、実はゲーム内では少し違う座標が使われています。 上記はTraceLine関数の先頭でブレークした時のスタックの画像と、プレイヤークラスをReClassで表示した物ですが、 z座標に +4.3 した値が、TraceLine の from に使われています。
プレイヤーの座標が、プレイヤーの具体的にどの位置の座標なのかは自分は把握できていませんが、 例えば足元の座標の場合、以下のようなケースでエイムできるはずなのに TraceLine では衝突判定が出てしまいます。 おそらくこの関係とかで少し z座標 を上げてると思われるので、以下のようにすると少し制度が上がります。

やってみる

では、実際にビルドしてインジェクトしてやってみましょう。
以下のような感じで、敵が見える時はエイムし、敵が見えない時は自由に視点操作できる と言う風になっているはずです。

ソースコード

dllmain.cpp

#include "pch.h"
    #include <cmath>
    
    float PI = 3.14159265359;
    typedef struct Vector3 {
        float x;
        float y;
        float z;
    
        Vector3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}
    } Vector3;
    
    typedef struct TraceResult {
        Vector3 pos;
        bool collided;
    
        TraceResult() : pos(Vector3(0, 0, 0)), collided(false) {}
    } TraceResult;
    
    typedef void(__cdecl* _TraceLine)(
        Vector3 from,
        Vector3 to,
        DWORD* owner,
        bool bCheckPlayer,
        bool flag_shouldbe0
    );
    _TraceLine TraceLine;
    
    float GetDistance(Vector3 delta) {
        return (float)sqrt(pow(delta.x, 2) + pow(delta.y, 2) + pow(delta.z, 2));
    }
    
    bool IsVisible(Vector3 from, Vector3 to, DWORD* owner) {
        TraceResult traceresult;
        from.z += 4.3;
        to.z   += 4.3;
    
        __asm {
            push 0
            push 0
            push owner
            push to.z
            push to.y
            push to.x
            push from.z
            push from.y
            push from.x
            lea eax, [traceresult]
            call TraceLine
            add esp, 0x24
        }
        return !traceresult.collided;
    }
    
    
    DWORD WINAPI ThreadMain(LPVOID params) {
        DWORD baseAddr       = (DWORD)GetModuleHandle("ac_client.exe");
        DWORD entityListAddr = *(DWORD*)(baseAddr + 0x10f4f8);
        DWORD playerAddr     = *(DWORD*)(baseAddr + 0x10f4f4);
        DWORD yawAddr        = playerAddr + 0x40;
        DWORD pitchAddr      = playerAddr + 0x44;
    
        TraceLine = (_TraceLine)(baseAddr + 0x08a310);
    
        while (1) {
            int maxPlayer = *(int*)(baseAddr + 0x10F500);
    
            Vector3 myPos(
                *(float*)(playerAddr + 0x34),
                *(float*)(playerAddr + 0x38),
                *(float*)(playerAddr + 0x3c)
            );
    
            // Get closest entity
            DWORD closestEntityAddr = 0;
            float closestDist = -1;
            for (int i = 1; i < maxPlayer; i++) {
                DWORD entityAddr = *(DWORD*)(entityListAddr + 4 * i);
    
                // skip if enemy is dead
                if (*(int*)(entityAddr + 0xF8) < 1) continue;
    
                Vector3 entPos(
                    *(float*)(entityAddr + 0x34),
                    *(float*)(entityAddr + 0x38),
                    *(float*)(entityAddr + 0x3C)
                );
    
                // skip if enemy is not visible
                if (!IsVisible(myPos, entPos, (DWORD*)playerAddr)) continue;
    
                Vector3 delta(
                    entPos.x - myPos.x,
                    entPos.y - myPos.y,
                    entPos.z - myPos.z
                );
                float d = GetDistance(delta);
    
                if (closestDist == -1 || d < closestDist) {
                    closestDist = d;
                    closestEntityAddr = entityAddr;
                }
            }
    
            // Aim closest entity
            if (closestDist != -1 && closestEntityAddr != 0) {
                Vector3 entPos(
                    *(float*)(closestEntityAddr + 0x34),
                    *(float*)(closestEntityAddr + 0x38),
                    *(float*)(closestEntityAddr + 0x3C)
                );
                Vector3 delta(
                    entPos.x - myPos.x,
                    entPos.y - myPos.y,
                    entPos.z - myPos.z
                );
    
                float aimPitch = asin(delta.z / GetDistance(delta)) * (180 / PI);
                float aimYaw = atan2(delta.y, delta.x) * (180 / PI) + 90;
                if (aimYaw < 0) aimYaw += 360;
    
                *(float*)yawAddr = aimYaw;
                *(float*)pitchAddr = aimPitch;
            }
        }
    
        return 0;
    }
    
    BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
        if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
            CreateThread(0, 0, ThreadMain, hModule, 0, 0);
        }
        return TRUE;
    }