Привет, %username%! Итак, продолжим написание нашего бота. Из прошлых статей, мы научились находить адрес перехватываемой функции для DirectX 9 и 11, а так же исполнять произвольный ассемблерный код в главном потоке игры и прятать от различных методов защиты. Теперь все эти знания можно применить в реальных боевых условиях. И начнем мы с исследования программы, для которой мы и пишем бот.
Disclaimer: Автор не несет ответственности за применение вами знаний полученных в данной статье или ущерб в результате их использования. Вся информация здесь изложена только в познавательных целях. Особенно для компаний разрабатывающих MMORPG, что бы помочь им бороться с ботоводами. И, естественно, автор статьи не ботовод, не читер и никогда ими не был.
Для тех кто пропустил прошлые статьи вот содержание, а кто все прочел идем дальше:
Содержание
- Часть 0 — Поиск точки внедрения кода
- Часть 1 — Внедрение и исполнение стороннего кода
- Часть 2 — Прячем код от посторонних глаз
- Часть 3 — Под прицелом World of Warcraft 5.4.x (Структуры)
- Часть 4 — Под прицелом World of Warcraft 5.4.x (Перемещение)
- Часть 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
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