📜 ⬆️ ⬇️

We write a bot for MMORPG with assembler and draenei. Part 3

Hi% username%! So, let's continue writing our bot. From past articles, we learned how to find the address of the intercepted function for DirectX 9 and 11, as well as execute arbitrary assembly code in the main stream of the game and hide from various methods of protection. Now all this knowledge can be applied in real combat conditions. And we begin with the study of the program for which we write the bot.

Disclaimer: The author is not responsible for your use of the knowledge gained in this article or damage as a result of their use. All information here is for educational purposes only. Especially for companies developing MMORPG, that would help them to fight with the bot. And, of course, the author of the article is not a botmaster, not a cheater, and never was.




For those who missed past articles, here is the content, and who read everything, we go further:
Content

  1. Part 0 - Search for a code injection point
  2. Part 1 - Implementing and executing third-party code
  3. Part 2 - Hide the code from prying eyes
  4. Part 3 - Under the gun World of Warcraft 5.4.x (Structures)
  5. Part 4 - Under the gun World of Warcraft 5.4.x (Moving)
  6. Part 5 - Under the gun World of Warcraft 5.4.x (Casting Fireball)

')
Before you start, you can put on World of Warcraft 5.4.x download, for a start, we only need Wow.exe, so you can only download it. And now I will immerse you in the holy of holies of all the cheaters and botovodov World of Warcraft game.
I'll start quite far away. Each program has an algorithm by which it works and the memory in which it stores data, in other words, there is code, and sometimes there is data in the code itself. So in order to have an idea about the world around us in the game, we need the data. To get them, consider how they are stored in memory and how they are. So, we introduce the concept of a game object (hereinafter WowObject) - this is the basic object that is stored in memory and has the following properties Descriptors, ObjectType and Guid, here is its structure:

[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 } 

where Guid is the unique value of the object in the game, OjectType is the type of object in the game and can take the following values:
 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 } 

and Descriptors is a pointer to memory with data about a WowObject object. To make it clearer I will give a small example:
 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; } } 

For example, we want to get an EntryId (EntryId is something like a class for objects, that is, 2 identical objects in the game world have the same EntryId, but they have a different Guid), here are the basic descriptors for 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, } 

Obviously, the code will be as follows:
 public class WowObject { //    public int Entry { get { return GetValue<int>(ObjectFields.EntryId); } } } 

All game objects of the game are stored sequentially, i.e. the structure of the next object will be found at offset 0x28 (see WowObjStruct ) from the current pointer, provided that it exists. Now let's figure out how to find all the structures of the game. All WowObject is managed by the object manager (hereinafter ObjectManager).
Declare it using the following structures.
  [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 } 


And this offset for a specific version of World of Warcraft
  public enum ObjectManager { connection = 0xEC4140, objectManager = 0x462c, } 

Now I will explain how to find them. To get started, run IDA and open it, I hope already downloaded, Wow.exe. How to open, remember BaseAddress, most often it is 0x400000, press Ctrl + L and look for the label aObjectmgrclien . After switching to it, press Ctrl + X and open the last reference. You should see something like this:
  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 

We are interested in the first dword, this is dword_12C4140 and the next following it is mov [ecx + 462Ch], eax . From here we get, connection = 0x12C4140 - 0x400000 = 0xEC4140, objectManager = 0x462C. For recalculation, I use HackCalc

  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(); } } 

Thus, we can get all the WowObjects, well, let's learn how to find all game units (these are all game and not game characters). We introduce the WowUnit class:
 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; } } } 

It remains to iterate ObjectManager and check wowObject.IsA (WoWObjectTypeFlags.Unit). That's all for now, and so the article came out huge for assimilation. If something will not work, write in a personal with a link to the weights on GitHub. Good luck!

Source: https://habr.com/ru/post/251353/


All Articles