-
Sending Packets from WoW client
I've finally managed to read all in-bound and out-bound packets from the client (3.4.5.xxx)
While I've had fun dropping specific packets and seeing what effect it has on the client I figured it was time to try learn how to craft and send my own packets...
My goal for the time being is to successfully send a CMSG_RANDOM_ROLL= 0x3657, and get a valid response in return.
Code:
struct Packet {
void* VMT;
char _pad[0x18];
uint64_t Send() {
return ((uint64_t(*)(void*))(reinterpret_cast<__int64>(GetModuleHandle(nullptr)) + CLIENTSERVICES_SEND))((void*)this);
}
};
#pragma pack(0)
struct RandomRollPacket : Packet
{
int* rolls[2];
RandomRollPacket(int min, int max)
{
this->VMT = (void*)(reinterpret_cast<__int64>(GetModuleHandle(nullptr)) + RANDOM_ROLL_PACKET_VMT);
this->rolls[0] = 0;
this->rolls[1] = 0;
}
};
As you could likely make out from the code snippet . My attempt was to create the UserClientRandomRoll message and pass it through ClientServices::Send.
This does in fact send the packet with the correct OP code as I can see it on my other hooked functions - However I suspect the packet is malformed as the server doesn't respond.
It might be note worthy that if I pass rolls[0..1] = 0 (as in the above code) I do get a response from the server as if the player typed /roll 0 0 - however I've never managed to get any other response apart from that. If roll[0..1] is anything other than 0 the server simply doesn't respond regardless of how I attempt to structure the packet.
I have dug around TC a bit in hopes of understanding this system a little better with mixed success :
TC Random Roll
I also attempted the CMSG_CHAT_MESSAGE_SAY = 0x37E7 but with even less success.
At the moment I am hitting a bit of a wall, I suspect that either there is some sort of encryption that happens BEFORE ClientServices::Send is called and that is destroying the data in my packet or I am simply crafting the packets incorrectly.
I think my next attempt will be to manually build out the CDatastore (I would really like to avoid this if possible) however I am still not sure how or where the client handles its packet encryption!
-
Elite User
Are you sure your _pad has the correct size? I don't have any access to my old sources at my current location, but iirc it was something like 0x10 where the packet data struct starts in x64 inside jam classes.
It may have been changed over the years though.
Last edited by culino2; 02-24-2024 at 09:44 PM.
-
Established Member
I investigated the Script_RandomRoll lua handler and came up with this.
Your Random Roll packet looks to be 0x28 , while mine is 0x2C. Can you try with the structure I reversed?
Also, I think you might just be hitting undefined behavior. Not an expert with c or c++ , but the structure field of "int* rolls[2];" makes no sense to me.
Did you intend for a two capacity array of pointers? Or, is there some magic that auto creates an int array for you on struct initialization? Or isn't this just pointing to uninitialized memory???
All from 3.4.3.52237 classic
Code:
00000000 RandomRollPacket struc ; (sizeof=0x2C, mappedto_562)
00000000 ; XREF: Script_RandomRoll/r
00000000 vmt dq ? ; offset
00000008 padding_0x18 db 16 dup(?)
00000018 field_18 dd ?
0000001C field_1C dd ?
00000020 field_20 dd ?
00000024 minRoll dd ? ; XREF: Script_RandomRoll+FB/w
00000028 maxRoll dd ? ; XREF: Script_RandomRoll+FF/w
0000002C RandomRollPacket ends
Code:
__int64 __fastcall RandomRollPacket::Prepare(RandomRollPacket *rollPacket)
{
__int64 result; // rax
rollPacket->field_18 = 0;
rollPacket->vmt = (_QWORD **)sRollPacket::VMT;
result = (__int64)rollPacket;
BYTE1(rollPacket->field_20) = 0;
return result;
}
Code:
__int64 __fastcall Script_RandomRoll(__int64 luaState)
{
_BYTE *v2; // rax
int v3; // esi
int v4; // edi
const char *v5; // rax
int minRange; // edi
_BYTE *v7; // rax
const char *v8; // rax
int maxRange; // eax
int _maxRange; // ebx
RandomRollPacket randomRollPacket; // [rsp+20h] [rbp-38h] BYREF
if ( sub_2C4550() && sub_2C4550() )
{
v2 = (_BYTE *)sub_2C59E0(luaState, 1i64);
v3 = 16;
if ( !v2 || *v2 != 48 || (v4 = 16, ((v2[1] - 88) & 0xDF) != 0) )
v4 = 10;
v5 = (const char *)sub_2C59E0(luaState, 1i64);
minRange = strtol(v5, 0i64, v4);
v7 = (_BYTE *)sub_2C59E0(luaState, 2i64);
if ( !v7 || *v7 != 0x30 || ((v7[1] - 88) & 0xDF) != 0 )
v3 = 10;
v8 = (const char *)sub_2C59E0(luaState, 2i64);
maxRange = strtol(v8, 0i64, v3);
_maxRange = maxRange;
if ( minRange )
{
if ( minRange >= 0 )
goto LABEL_15;
}
else if ( maxRange )
{
LABEL_15:
if ( maxRange >= minRange && maxRange <= 1000000 )
{
RandomRollPacket::Prepare(&randomRollPacket);
randomRollPacket.minRoll = minRange;
randomRollPacket.maxRoll = _maxRange;
ClientServices::SendPacket((__int64)&randomRollPacket);
}
}
return 0i64;
}
lua::usageError(luaState, (__int64)"Usage: RandomRoll(\"max\") or RandomRoll(\"min\", \"max\")");
return 0i64;
}
-
Thank you guys both for the replies the padding was correct my RandomRollPacket struct itself was incorrect :
Code:
struct RandomRollPacket : Packet {
char unk_1 = 0x00;
char unk_2 = 0x00;
int Min;
int Max;
RandomRollPacket(int min, int max) : Min(124), Max(200) {
this->VMT = reinterpret_cast<void*>(reinterpret_cast<__int64>(GetModuleHandle(nullptr)) + RANDOM_ROLL_PACKET_VMT);
}
};
I can't say I fully understand why I needed to padd the Random packet with unk_1 & 2 but it got me there in end so I'll take it lol.
@__chase
I'd love to know how exactly you got to that RandomRoll structure I've tried generating my own in IDA but it just doesn't line up nicely at all...
-
Established Member
IDA will struggle to decompile most of the packet handler functions, because it only calls the packet's respective initialize function.
IDA will attempt to decompile the function and auto define packet structure on the stack but it can only see as far as the initialization functions access, so IDA gets confused and gives you additional stack variables when really they are to be part of the packet structure. Undefine the incorrectly identified stack variables or extend the packet array and consume those variables then correctly define them. You can confirm packet structure by reversing any packet's VMT index 2 (method 3) . For Random Roll's vmt index 2 refer to below:
Here's some insight into how i decided the size of the packet...
Note the highest field access in the method... an unsigned int (4bytes) offset 0x28 into the packet, thus my packet must be (atleast) 0x28 + 0x04 = 0x2C.
For more complex packet methods you will have to explore inner functions etc and make sure you're defining the structure properly.
Lastly, you can xref the 'sub_2DB960' method and auto detect every packet's vmt initialization function. Just cross reference the second argument to TrinityCore's opcodes to tell what packet handler you're looking at.
Code:
char __fastcall sub_770600(__int64 packet, _DWORD *a2)
{
int v4; // ecx
char v5; // al
_DWORD *v7; // [rsp+20h] [rbp-18h] BYREF
__int16 v8; // [rsp+28h] [rbp-10h]
sub_2DB960(a2, 0x3657);
v4 = *(unsigned __int8 *)(packet + 0x21);
v5 = *(_BYTE *)(packet + 0x21);
v7 = a2;
v8 = 0;
if ( v4 == (v5 & 1) )
{
LOBYTE(v8) = v4;
HIBYTE(v8) = 1;
}
sub_6FEA90((__int64)&v7);
sub_2DBA00(a2, *(unsigned int *)(packet + 0x24));
sub_2DBA00(a2, *(unsigned int *)(packet + 0x28));
if ( *(_BYTE *)(packet + 0x21) )
sub_2DB8C0(a2, *(_BYTE *)(packet + 0x20));
return 1;
}
-
Post Thanks / Like - 2 Thanks
MrFade,
Razzue (2 members gave Thanks to _chase for this useful post)