Пишем бота для MMORPG с ассемблером и дренейками. Часть 3

в 16:04, , рубрики: .net, c#.net, game development, mmorpg, reverse engineering, world of warcraft, боты, игры, Программирование, реверс-инжиниринг

Пишем бота для MMORPG с ассемблером и дренейками. Часть 3 - 1 Привет, %username%! Итак, продолжим написание нашего бота. Из прошлых статей, мы научились находить адрес перехватываемой функции для DirectX 9 и 11, а так же исполнять произвольный ассемблерный код в главном потоке игры и прятать от различных методов защиты. Теперь все эти знания можно применить в реальных боевых условиях. И начнем мы с исследования программы, для которой мы и пишем бот.

Disclaimer: Автор не несет ответственности за применение вами знаний полученных в данной статье или ущерб в результате их использования. Вся информация здесь изложена только в познавательных целях. Особенно для компаний разрабатывающих MMORPG, что бы помочь им бороться с ботоводами. И, естественно, автор статьи не ботовод, не читер и никогда ими не был.


Для тех кто пропустил прошлые статьи вот содержание, а кто все прочел идем дальше:

Содержание

  1. Часть 0 — Поиск точки внедрения кода
  2. Часть 1 — Внедрение и исполнение стороннего кода
  3. Часть 2 — Прячем код от посторонних глаз
  4. Часть 3 — Под прицелом World of Warcraft 5.4.x (Структуры)
  5. Часть 4 — Под прицелом World of Warcraft 5.4.x (Перемещение)
  6. Часть 5 — Под прицелом World of Warcraft 5.4.x (Кастуем фаерболл)

Перед тем как начать, можете поставить на закачку World of Warcraft 5.4.x, для начала, нам понадобиться только Wow.exe, так что можете качать только его. А теперь я Вас погружу в святая святых всех читеров и ботоводов игры World of Warcraft.
Начну совсем из далека. У каждой программы есть алгоритм по которому она работает и память в которой она хранит данные, другими словами — есть код, а есть данные иногда данные бывают и в самом коде. Так во для того что бы иметь представление об окружающем мире в игре, нам необходимы именно данные. Что бы их получить, рассмотрим как они хранятся в памяти и какие они бывают. Итак, введем понятие игрового объекта (в дальнейшем WowObject) — это базовый объект, который хранится в памяти и имеет такие свойства Descriptors, ObjectType и Guid, вот его структура:

    [StructLayout(LayoutKind.Sequential)]
    struct WowObjStruct
    {
        IntPtr vtable;              // 0x00
        public IntPtr Descriptors;  // 0x4
        IntPtr unk1;                // 0x8
        public int ObjectType;      // 0xC
        int unk3;                   // 0x10
        IntPtr unk4;                // 0x14
        IntPtr unk5;                // 0x18
        IntPtr unk6;                // 0x1C
        IntPtr unk7;                // 0x20
        IntPtr unk8;                // 0x24
        public ulong Guid;          // 0x28
    }

где Guid — уникальное значение объекта в игре, OjectType — тип объекта в игре и может принимать следующие значения:

public enum WoWObjectType : int
{
    Object = 0,
    Item = 1,
    Container = 2,
    Unit = 3,
    Player = 4,
    GameObject = 5,
    DynamicObject = 6,
    Corpse = 7,
    AreaTrigger = 8,
    SceneObject = 9,
    NumClientObjectTypes = 0xA,
    None = 0x270f,
}

[Flags]
public enum WoWObjectTypeFlags
{
    Object = 1 << WoWObjectType.Object,
    Item = 1 << WoWObjectType.Item,
    Container = 1 << WoWObjectType.Container,
    Unit = 1 << WoWObjectType.Unit,
    Player = 1 << WoWObjectType.Player,
    GameObject = 1 << WoWObjectType.GameObject,
    DynamicObject = 1 << WoWObjectType.DynamicObject,
    Corpse = 1 << WoWObjectType.Corpse,
    AreaTrigger = 1 << WoWObjectType.AreaTrigger,
    SceneObject = 1 << WoWObjectType.SceneObject

}

а Descriptors — это указатель на память с данными об объекте WowObject. Что бы было понятнее приведу небольшой пример:

public class WowObject
{
    private IntPtr BaseAddress;
    private WowObjStruct ObjectData;

    public WowObject(IntPtr address)
    {
        BaseAddress = address;
        ObjectData = Memory.Process.Read<WowObjStruct>(BaseAddress);
    }

    public bool IsValid { get { return BaseAddress != IntPtr.Zero; } }

    public T GetValue<T>(Enum index) where T : struct
    {
        return Memory.Process.Read<T>(ObjectData.Descriptors + (int)index * IntPtr.Size);
    }

    public void SetValue<T>(Enum index, T val) where T : struct
    {
        Memory.Process.Write<T>(ObjectData.Descriptors + (int)index * IntPtr.Size, val);
    }

    public bool IsA(WoWObjectTypeFlags flags)
    {
        return (GetValue<int>(Descriptors.ObjectFields.Type) & (int)flags) != 0;
    }
}

Например мы хотим получить EntryId (EntryId — это что-то вроде класса, для объектов, т.е. 2 одинаковых предмета в игровом мире имеют одинаковый EntryId, но Guid у них разный), вот базовые дескрипторы для WowObject:

public enum ObjectFields
{
    Guid = 0,
    Data = 2,
    Type = 4,
    EntryId = 5,
    DynamicFlags = 6,
    Scale = 7,
    End = 8,
}

[Flags]
public enum ObjectDynamicFlags : uint
{
    Invisible = 1 << 0,
    Lootable = 1 << 1,
    TrackUnit = 1 << 2,
    TaggedByOther = 1 << 3,
    TaggedByMe = 1 << 4,
    Unknown = 1 << 5,
    Dead = 1 << 6,
    ReferAFriendLinked = 1 << 7,
    IsTappedByAllThreatList = 1 << 8,
}

Очевидно, что код будет следующим:

public class WowObject
{
    //Ранее объявленные члены класса
    public int Entry
    {
        get { return GetValue<int>(ObjectFields.EntryId); }
    }
}

Все игровые объекты игры хранятся последовательно, т.е. структура следующего объекта будет найдена по смещению 0x28 (см. WowObjStruct) от текущего указателя при условии, что он существует. Теперь разберемся как найти все структуры игры. Всеми WowObject заправляет менеджер объектов (далее ObjectManager).

Объявим его используя следующие структуры

    [StructLayout(LayoutKind.Sequential)]
    struct TSExplicitList // 12
    {
        public TSList baseClass; // 12
    }

    [StructLayout(LayoutKind.Sequential)]
    struct TSList // 12
    {
        public int m_linkoffset; // 4
        public TSLink m_terminator; // 8
    }

    [StructLayout(LayoutKind.Sequential)]
    struct TSLink // 8
    {
        public IntPtr m_prevlink; //TSLink *m_prevlink // 4
        public IntPtr m_next; // C_OBJECTHASH *m_next // 4
    }

    [StructLayout(LayoutKind.Sequential)]
    struct TSHashTable // 44
    {
        public IntPtr vtable; // 4
        public TSExplicitList m_fulllist; // 12
        public int m_fullnessIndicator; // 4
        public TSGrowableArray m_slotlistarray; // 20
        public int m_slotmask; // 4
    }

    [StructLayout(LayoutKind.Sequential)]
    struct TSBaseArray // 16
    {
        public IntPtr vtable; // 4
        public uint m_alloc; // 4
        public uint m_count; // 4
        public IntPtr m_data;//TSExplicitList* m_data; // 4
    }

    [StructLayout(LayoutKind.Sequential)]
    struct TSFixedArray // 16
    {
        public TSBaseArray baseClass; // 16
    }

    [StructLayout(LayoutKind.Sequential)]
    struct TSGrowableArray // 20
    {
        public TSFixedArray baseclass; // 16
        public uint m_chunk; // 4
    }
    [StructLayout(LayoutKind.Sequential)]
    struct CurMgr // 248 bytes x86, 456 bytes x64
    {
        public TSHashTable VisibleObjects; // m_objects 44
        public TSHashTable LazyCleanupObjects; // m_lazyCleanupObjects 44
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 11)]
        // m_lazyCleanupFifo, m_freeObjects, m_visibleObjects, m_reenabledObjects, whateverObjects...
        public TSExplicitList[] Links; // Links[10] has all objects stored in VisibleObjects it seems 12 * 11 = 132
#if !X64
        public int Unknown1; // wtf is that and why x86 only? // 4
        public int Unknown2; // not sure if this actually reflects the new object manager structure, but it does get the rest of the struct aligned correctly
        public int Unknown3; // not sure if this actually reflects the new object manager structure, but it does get the rest of the struct aligned correctly
#endif
        public ulong ActivePlayer; // 8
        public int PlayerType; // 4
        public int MapId; // 4
        public IntPtr ClientConnection; // 4
        public IntPtr MovementGlobals; // 4
    }

А это смещения для конкретной версии World of Warcraft

    public enum ObjectManager
    {
        connection = 0xEC4140,
        objectManager = 0x462c,
    }

Сейчас объясню как их найти. Для начала запускаем IDA и открываем в нем, уже надеюсь скачанный, Wow.exe. Как откроется, запоминаем BaseAddress, чаще всего он 0x400000, жмем Ctrl+L и ищем метку aObjectmgrclien. После перехода на нее жмем Ctrl+X и открываем последний референс. Вы должны увидеть, что-то такое:

    push    0
    push    0A9Fh
    push    offset aObjectmgrclien ; "ObjectMgrClient.cpp"
    push    100h
    call    sub_5DC588
    test    eax, eax
    jz      short loc_79EEEB
    mov     ecx, eax
    call    sub_79E1E1
    jmp     short loc_79EEED
loc_79EEEB:
    xor     eax, eax
loc_79EEED:
    mov     ecx, dword_12C4140
    fldz
    mov     [ecx+462Ch], eax

Нас интересует первый dword, это dword_12C4140 и следующий за ним mov [ecx+462Ch], eax. Отсюда получаем, connection = 0x12C4140 — 0x400000 = 0xEC4140, objectManager = 0x462C. Для пересчета, я использую HackCalc
Пишем бота для MMORPG с ассемблером и дренейками. Часть 3 - 2

    public class ObjectManager : IEnumerable<WowObject>
    {
        private CurMgr _curMgr;
        private IntPtr _baseAddress;
        private WowGuid _activePlayer;
        private WowPlayer _activePlayerObj;

        public void UpdateBaseAddress()
        {
            var connection = Memory.Process.Read<IntPtr>((int)ObjectManager.connection, true);
            _baseAddress = Memory.Process.Read<IntPtr>(connection + (int)ObjectManager.objectManager);
        }

        private IntPtr BaseAddress
        {
            get { return _baseAddress; }
        }

        public WowGuid ActivePlayer
        {
            get { return _activePlayer; }
        }

        public WowPlayer ActivePlayerObj
        {
            get { return _activePlayerObj; }
        }

        public IntPtr ClientConnection
        {
            get { return _curMgr.ClientConnection; }
        }

        public IntPtr FirstObject()
        {
            return _curMgr.VisibleObjects.m_fulllist.baseClass.m_terminator.m_next;
        }

        public IntPtr NextObject(IntPtr current)
        {
            return Memory.Process.Read<IntPtr>(current + _curMgr.VisibleObjects.m_fulllist.baseClass.m_linkoffset + IntPtr.Size);
        }

        public IEnumerable<WowObject> GetObjects()
        {
            _curMgr = Memory.Process.Read<CurMgr>(BaseAddress);
            _activePlayer = new WowGuid(_curMgr.ActivePlayer);
            IntPtr first = FirstObject();
            while (((first.ToInt64() & 1) == 0) && first != IntPtr.Zero)
            {
                var wowObject = new WowObject(first);
                if (wowObject.Guid.Value == _curMgr.ActivePlayer)
                {
                     _activePlayerObj = new WowPlayer(first);
                }
                first = NextObject(first);
            }
        }

        public IEnumerator<WowObject> GetEnumerator()
        {
             return GetObjects().GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

Таким образом мы можем получить все WowObject, ну и давайте научимся находить всех игровых юнитов (это все игровые и не игровые персонажи). Введем класс WowUnit:


public enum UnitField
{
    CachedSubName = 0,
    UnitClassificationOffset2 = 32,
    CachedQuestItem1 = 48,
    CachedTypeFlag = 76,
    IsBossOffset2 = 76,
    CachedModelId1 = 92,
    CachedName = 108,
    UNIT_SPEED = 128,
    TaxiStatus = 0xc0,
    TransportGUID = 2096,
    UNIT_FIELD_X = 0x838,
    UNIT_FIELD_Y = 0x83C,
    UNIT_FIELD_Z = 0x840,
    UNIT_FIELD_R = 2120,
    DBCacheRow = 2484,
    IsBossOffset1 = 2484,
    UnitClassificationOffset1 = 2484,
    CanInterrupt = 3172,
    CastingSpellID = 3256,
    ChannelSpellID = 3280,
}

public enum UnitFields
{
    Charm = ObjectFields.End + 0,
    Summon = 10,
    Critter = 12,
    CharmedBy = 14,
    SummonedBy = 16,
    CreatedBy = 18,
    DemonCreator = 20,
    Target = 22,
    BattlePetCompanionGUID = 24,
    ChannelObject = 26,
    ChannelSpell = 28,
    SummonedByHomeRealm = 29,
    Sex = 30,
    DisplayPower = 31,
    OverrideDisplayPowerID = 32,
    Health = 33,
    Power = 34,
    MaxHealth = 39,
    MaxPower = 40,
    PowerRegenFlatModifier = 45,
    PowerRegenInterruptedFlatModifier = 50,
    Level = 55,
    EffectiveLevel = 56,
    FactionTemplate = 57,
    VirtualItemID = 58,
    Flags = 61,
    Flags2 = 62,
    AuraState = 63,
    AttackRoundBaseTime = 64,
    RangedAttackRoundBaseTime = 66,
    BoundingRadius = 67,
    CombatReach = 68,
    DisplayID = 69,
    NativeDisplayID = 70,
    MountDisplayID = 71,
    MinDamage = 72,
    MaxDamage = 73,
    MinOffHandDamage = 74,
    MaxOffHandDamage = 75,
    AnimTier = 76,
    PetNumber = 77,
    PetNameTimestamp = 78,
    PetExperience = 79,
    PetNextLevelExperience = 80,
    ModCastingSpeed = 81,
    ModSpellHaste = 82,
    ModHaste = 83,
    ModRangedHaste = 84,
    ModHasteRegen = 85,
    CreatedBySpell = 86,
    NpcFlag = 87,
    EmoteState = 89,
    Stats = 90,
    StatPosBuff = 95,
    StatNegBuff = 100,
    Resistances = 105,
    ResistanceBuffModsPositive = 112,
    ResistanceBuffModsNegative = 119,
    BaseMana = 126,
    BaseHealth = 127,
    ShapeshiftForm = 128,
    AttackPower = 129,
    AttackPowerModPos = 130,
    AttackPowerModNeg = 131,
    AttackPowerMultiplier = 132,
    RangedAttackPower = 133,
    RangedAttackPowerModPos = 134,
    RangedAttackPowerModNeg = 135,
    RangedAttackPowerMultiplier = 136,
    MinRangedDamage = 137,
    MaxRangedDamage = 138,
    PowerCostModifier = 139,
    PowerCostMultiplier = 146,
    MaxHealthModifier = 153,
    HoverHeight = 154,
    MinItemLevel = 155,
    MaxItemLevel = 156,
    WildBattlePetLevel = 157,
    BattlePetCompanionNameTimestamp = 158,
    InteractSpellID = 159,
    End = 160,
}

public class WowUnit : WowObject
{
    public WowUnit(IntPtr address)
        : base(address)
    {
    }
    public int Health
    {
        get { return GetValue<int>(UnitFields.Health); }
    }

    public int MaxHealth
    {
        get { return GetValue<int>(UnitFields.MaxHealth); }
    }

    public bool IsAlive
    {
        get { return !IsDead; }
    }

    public bool IsDead
    {
        get { return this.Health <= 0 || (DynamicFlags & ObjectDynamicFlags.Dead) != 0; }
    }


    public ulong TransportGuid
    {
        get { return GetValue<ulong>(UnitField.TransportGUID); }
    }


    public bool InTransport
    {
        get { return TransportGuid > 0; }
    }

    public Vector3 Position
    {
        get
        {
            if (Pointer == IntPtr.Zero) return Vector3.Zero;
            if (InTransport)
            {
                var wowObject = Memory.ObjectManager.GetObjectByGUID(TransportGuid);
                if (wowObject != null)
                {
                    var wowUnit = new WowUnit(wowObject.Pointer);
                    if (wowUnit.IsValid && wowUnit.IsAlive)
                        return wowUnit.Position;
                }
            }

            var position = new Vector3(
                Memory.Process.Read<float>(Pointer + (int)UnitField.UNIT_FIELD_X),
                Memory.Process.Read<float>(Pointer + (int)UnitField.UNIT_FIELD_Y),
                Memory.Process.Read<float>(Pointer + (int)UnitField.UNIT_FIELD_Z),
                "None");

            return position;
        }
    }
}

Остается проитерировать ObjectManager и проверить wowObject.IsA(WoWObjectTypeFlags.Unit). На сегодня все, статья и так вышла огромная для усвоения. Если что-то будет не получаться, пишите в личку с ссылочкой на сорсы на GitHub. Желаю удачи!

Автор: 3axap4eHko

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js