Jump to content
/dev/not/null

Need help in packet decryption

Recommended Posts

After success SMSG_AUTH_RESPONSE client sends packet, after decryption it equals to b'\x00\x047\x00\x00\x00\x18\xb7\xce\xa7/Y\xff\xff\xff\xff',

What is the client packet structure ? First two bytes are opcode, aren't it ? But if so, I can't find this opcode in list. Maybe I'm doing something wrong ?

This is my enc/decrypt code:

    def encrypt(self, data):
        assert len(data) >= HeaderCrypt.ENCRYPT_HEADER_SIZE
        encrypted_header = [0] * HeaderCrypt.ENCRYPT_HEADER_SIZE

        for index in range(HeaderCrypt.ENCRYPT_HEADER_SIZE):
            enc = (data[index] ^ self.crypt_key[self.send_i]) + self.send_j
            enc %= 0x100
            encrypted_header[index] = self.send_j = enc
            self.send_i = (self.send_i + 1) % len(self.crypt_key)

        return bytes(encrypted_header) + data[HeaderCrypt.ENCRYPT_HEADER_SIZE:]

    def decrypt(self, data):
        assert len(data) >= HeaderCrypt.DECRYPT_HEADER_SIZE
        decrypted_header = [0] * HeaderCrypt.DECRYPT_HEADER_SIZE

        for index in range(self.DECRYPT_HEADER_SIZE):
            dec = (data[index] - self.recv_j) ^ self.crypt_key[self.recv_i]
            dec %= 0x100
            decrypted_header[index] = dec
            self.recv_j = data[index]
            self.recv_i = (self.recv_i + 1) % len(self.crypt_key)

        return bytes(decrypted_header) + data[HeaderCrypt.DECRYPT_HEADER_SIZE:]

Encryption works fine (because I can send SMSG_AUTH_RESPONSE and go to Character list loading), but I'm not sure about decryption. So, the question - what is the CMSG_* packets structure and what packet client send under 0x047 opcode after SMSG_AUTH_RESPONSE ?

Edited by /dev/not/null
added code

Share this post


Link to post
Share on other sites

Solved! The opcode is 55 (0x037) = CMSG_CHAR_ENUM.

Answering own question: in client request (b'\x00\x047\x00\x00\x00\x18\xb7\xce\xa7/Y\xff\xff\xff\xff') the first 2 bytes = packet size, next 4 bytes = opcode,

so 7\x00\x00\x00 is opcode here, which equals to 55 after translating into integer.

Edited by /dev/not/null

Share this post


Link to post
Share on other sites

Well, currently I getting another problem: my HeaderCrypt class decrypts only first packet correctly - next packets decrypts with incorrect opcodes.

My current HeaderCrypt implementation below:

class HeaderCrypt(object):

    ENCRYPT_HEADER_SIZE = 4
    DECRYPT_HEADER_SIZE = 6

    def __init__(self, session_key: bytes):
        self.crypt_key = self._generate_key(session_key)
        self.send_i = 0
        self.send_j = 0
        self.recv_i = 0
        self.recv_j = 0

    def encrypt(self, data: bytes):
        assert len(data) >= self.ENCRYPT_HEADER_SIZE
        encrypted_header = [0] * self.ENCRYPT_HEADER_SIZE

        for index in range(self.ENCRYPT_HEADER_SIZE):
            self.send_i %= len(self.crypt_key)
            enc = (data[index] ^ self.crypt_key[self.send_i]) + self.send_j
            enc %= 0x100
            self.send_i += 1
            encrypted_header[index] = self.send_j = enc

        return bytes(encrypted_header) + data[self.ENCRYPT_HEADER_SIZE:]

    def decrypt(self, data: bytes):
        assert len(data) >= self.DECRYPT_HEADER_SIZE
        decrypted_header = [0] * self.DECRYPT_HEADER_SIZE

        for index in range(self.DECRYPT_HEADER_SIZE):
            self.recv_i %= len(self.crypt_key)
            dec = (data[index] - self.recv_j) ^ self.crypt_key[self.recv_i]
            dec %= 0x100
            self.recv_i += 1
            self.recv_j = data[index]
            decrypted_header[index] = dec

        return bytes(decrypted_header) + data[self.DECRYPT_HEADER_SIZE:]

    def _generate_key(self, session_key):
        seed = b'8\xa7\x83\x15\xf8\x92%0q\x98g\xb1\x8c\x04\xe2\xaa'
        hashed = hmac.new(seed, session_key, sha1)
        return hashed.digest()

After success SMSG_AUTH_RESPONSE my server receives CMSG_CHAR_ENUM and decrypts it successfully. Next packets like CMSG_PING, CMSG_PLAYER_LOGIN etc decrypts incorrect. If I understands it correctly, the send_i, send_j, recv_i, recv_j values stores for the next decrypt operation. I got some experiments with this and some packets decrypting successfully only when I re-initialize HeaderCrypt, but this is wrong behavior, isn't it ?

Can anybody helps me with enc/decryption alg ? I don't asking for code, just explain me what should be implemented or what I have missed ?

Currently only first packet (CMSG_CHAR_ENUM) decrypts successfully. Also I tried to decrypt one packet for 10 times and only after that packet decrypts successfully. Something wrong with my current decryption alg, but I don't know what exactly. 

BTW, does opcode always in 3rd and 4th bytes ?

Edited by /dev/not/null

Share this post


Link to post
Share on other sites

Hello, 

Which client is it? If it's vanilla, you can have a look here how to decode. https://github.com/Warkdev/JaNGOSRealm/blob/master/src/main/java/eu/jangos/realm/network/decoder/RealmPacketDecoder.java

Crypt attribute is set for the session. And its implementation is https://github.com/Warkdev/JaNGOSRealm/blob/master/src/main/java/eu/jangos/realm/utils/VanillaCrypt.java

Share this post


Link to post
Share on other sites

My code identical to another sources, sorry, but information is not useful. I need more deep explanation of the encryption/decryption alg.

At least, what alg 2.4.3 client uses for packets encryption ?

Share this post


Link to post
Share on other sites

Hi, 

Sorry but you won't find more detailed info for the time being because it's not documented. If it doesn't work, your code can't be identical. 

Tal'

Share this post


Link to post
Share on other sites

I think recv_i changes incorrect. I tried to experimenting, for example added 3 to recv_i after for loop and this allowed me to parse CMSG_PLAYER_LOGIN. Continuing to understand how the algorithm works.

Share this post


Link to post
Share on other sites

Well... Currently I created the workaround for solving this problem. I hope this is temporary workaround.

I noticed some packets decrypts correctly only after calling decrypt method several times. So:

        def decrypt(packet: bytes):
            result = packet
            for index in range(20):
          	# I calls this method several time until packet will be decrypted successfully
                enc = session.header_crypt.decrypt(packet)
                try:
               	    # this code checks if packet decrypts successfully
                    opcode = WorldOpCode(int.from_bytes(enc[2:6], 'little')).value
                except ValueError:
                    continue
                else:
                    result = enc
                    break

            return result

        packet = decrypt(packet)

Some packets like CMSG_CHAR_ENUM are always decrypts the first time. Some packets like CMSG_PING decrypts on 9th iteration.

When I entering the world, packets with next opcodes (CMSG_VOICE_SESSION_ENABLECMSG_VOICE_SESSION_ENABLE) always decrypts on 1st iteration.

Another packets like CMSG_OPT_OUT_OF_LOOTCMSG_CHAR_CREATE, CMSG_PLAYER_LOGIN decrypts the first time.

Maybe somebody understands why decryption works so weird in my case ?

I checked - session_key generates correctly, hash for checking if session_key same on client and server also generates correctly. And I'm sure decrypting algorithm also correct. Maybe exists some differences between Python and C++ which I didn't noticed ?

Edited by /dev/not/null

Share this post


Link to post
Share on other sites

Not being an expert on handling the packet at this level, I would still advise do not take a risk with this operation:

dec %= 0x100

The modulo op in Python is quite a thing. Use bitwise operation instead:

dec &= 0xFF

Unsure if it helps, but it is the only difference from the referenced Java code I've managed to spot.

  • Like 1

Share this post


Link to post
Share on other sites
11 hours ago, Olion said:

Not being an expert on handling the packet at this level, I would still advise do not take a risk with this operation:

dec %= 0x100

The modulo op in Python is quite a thing. Use bitwise operation instead:

dec &= 0xFF

Unsure if it helps, but it is the only difference from the referenced Java code I've managed to spot.

Thanks for your answer. I tested, unfortunately this not helps. Btw, %= 0x100 and &= 0xFF returns same result.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Contact Us

To contact us click here
You can also email us at [email protected]

Privacy Policy | Terms & Conditions

Repositories

The Link to the master list
of MaNGOS repositories:
Copyright © getMaNGOS. All rights Reserved.

This website is in no way associated with or endorsed by Blizzard Entertainment®
×