Jump to content
Search In
  • More options...
Find results that contain...
Find results in...

Welcome to our site

Take a moment to join our board

W1cked

[Go] Xor Encryption Issues

Recommended Posts

I've noticed that after being logged in for quite some time that my Xor encrypt function runs out of range. I have tried to debug this for hours now and can't figure it out. It appears that @Spirited's source uses two keys, where I am trying to use one key. I can't figure it out, and I only noticed it now because I left my client running all day and came back to realize that my encrypt function didn't work as expected.

It appears to happen around the 2730th time the encrypt function runs.

I've attached the code to the playground so you can what I mean.

https://play.golang.org/p/HfnKFeqnVh_Z

I know it has something to do with with line:

dst[i] = dst[i] ^ c.dKey[(c.encrypt>>8)+0x100]

More specifically, I know it has something to do with adding 256 (0x100)

I'm just not sure why.

Below is the same thing on the playground, I've attached it here for archival purposes.

 

package main

import (
	"encoding/binary"
	"fmt"
)

func main() {
	x := NewXor()

	nonEnc := []byte{24, 0, 241, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}

	for i := 0; i < 3000; i++ {
		fmt.Println(i)
		enc := make([]byte, 24)
		x.Encrypt(enc, nonEnc)
	}
}

// DefaultKey for the Conquer Online game client. Used for interoperability with the game client.
var DefaultKey = []byte{0x9D, 0x0F, 0xFA, 0x13, 0x62, 0x79, 0x5C, 0x6D}

//Xor cipher used for encrypting/decrypting client/server stream.
//dKey is the default key that will be used until the player requests
//that the game key (gKey) be initialized using accountId and token.
//thereafter, the gKey will be used for encrypting/decrypting.
type Xor struct {
	decrypt, encrypt uint32
	dKey             [0x200]byte
	gKey             [0x200]byte
	gameInit         bool
}

// NewCipher assumes the input key is correct (that the length of the key is
// 8 bytes) and returns a new instance of the cipher after generating the
// initial keystream.
func NewXor() *Xor {
	c := new(Xor)
	key := DefaultKey
	for i := 0; i < 0x100; i++ {
		c.dKey[i] = key[0]
		c.dKey[i+0x100] = key[4]
		key[0] = (key[1]+byte(key[0]*key[2]))*key[0] + key[3]
		key[4] = (key[5]-byte(key[4]*key[6]))*key[4] + key[7]
	}
	return c
}

// GenerateKeys uses the token and identity of the client (in this project,
// the 64-bit token buffer from MsgConnect if it were supported by the client
// patch) as the key for altering the keystream.
func (c *Xor) GenerateKeys(accountId uint32, token uint32) {
	temp1 := uint32(((token + accountId) ^ 0x4321) ^ token)
	sqr := uint32(temp1 * temp1)

	tmp1 := make([]byte, 4)
	tmp2 := make([]byte, 4)
	binary.LittleEndian.PutUint32(tmp1, temp1)
	binary.LittleEndian.PutUint32(tmp2, sqr)

	for i := 0; i < 0x100; i++ {
		c.gKey[i] = c.dKey[i] ^ tmp1[i%4]
		c.gKey[i+0x100] = c.dKey[i + 0x100] ^ tmp2[i%4]
	}

	c.gameInit = true
	c.encrypt = 0
}

// Encrypt sets dst with the result of XORing src with the key stream. Dst and
// src may be the same slice, but otherwise should not overlap. Decrypt uses
// the encryption counter (encrypt).
func (c *Xor) Encrypt(dst, src []byte) {
	for i, v := range src {
		dst[i] = v ^ 0xAB
		dst[i] = dst[i]>>4 | dst[i]<<4
		dst[i] = dst[i] ^ c.dKey[byte(c.encrypt&0xff)]
		dst[i] = dst[i] ^ c.dKey[(c.encrypt>>8)+0x100]
		c.encrypt++
	}
}

// Decrypt sets dst with the result of XORing src with the keystream. Dst and
// src may be the same slice, but otherwise should not overlap. Decrypt uses
// the decryption counter (decrypt).
func (c *Xor) Decrypt(dst, src []byte) {
	if c.gameInit == false {
		for i, v := range src {
			dst[i] = v ^ 0xAB
			dst[i] = dst[i]>>4 | dst[i]<<4
			dst[i] = dst[i] ^ c.dKey[byte(c.decrypt&0xff)]
			dst[i] = dst[i] ^ c.dKey[(c.decrypt>>8)+0x100]
			c.decrypt++
		}
		return
	}

	for i, v := range src {
		dst[i] = (byte)(v ^ 0xAB)
		dst[i] = (byte)(dst[i]>>4 | dst[i]<<4)
		dst[i] = (byte)(dst[i] ^ c.gKey[c.decrypt&0xff])
		dst[i] = (byte)(dst[i] ^ c.gKey[(c.decrypt>>8)+0x100])
		c.decrypt++
	}
}

//BlockSize is here to satisfy the cipher.Block interface. It is unimplemented
func (c *Xor) BlockSize() int {
	panic("Xor BlockSize should never be called. Implemented to fulfill interface.")
}

 

Share this post


Link to post
Share on other sites

I'm at work right now, but that XOR cipher they made is also a counter based cipher. Do you know what your counters are for the decrypt and encrypt directions when the failure occurs? It might be that the data type is wrong for those counters. I can't check right now, but the EO source should be a good reference for that as well.

Share this post


Link to post
Share on other sites

Interesting, 65536 is where it fails. (65536 >> 8 ) + 0x100 would be 512, so 512 is where it's running out of range. 

Share this post


Link to post
Share on other sites
40 minutes ago, W1cked said:

Interesting, 65536 is where it fails. (65536 >> 8 ) + 0x100 would be 512, so 512 is where it's running out of range. 

So, potentially my code doesn't handle wrapping and the client is expecting wrapping at an unsigned short range. Let me look into it more when I get home, but the EO source should show what it expects. Maybe it does a mod op that I'm not doing in my implementation. I based mine off of CtpSky's work and never compared it.

Share this post


Link to post
Share on other sites

So it appears that Comet suffers from the same affliction, which I forgot is where I referenced this cipher, not Chimera. I'm looking in the EO source now though.

Share this post


Link to post
Share on other sites
6 hours ago, W1cked said:

So it appears that Comet suffers from the same affliction, which I forgot is where I referenced this cipher, not Chimera. I'm looking in the EO source now though.

Gosh, it looks so crazy similar to my current implementation of Chimera that I didn't even realize it wasn't. Ok, so it looks like the EO source is using an older encryption. Looking at other server sources though, those counters are unsigned shorts. If you change your data types to Uint16, then you should be good to go. I'll make some changes to Comet to reflect that change. Sorry for the confusion. I blame CptSky's implementation, but it's entirely possible that I just mistyped. 😰

  • Like 1

Share this post


Link to post
Share on other sites

I think what I may have done is copied the functions and rewrote it to match your Comet source, because the code comments don’t look like something I would write. I’m not that detailed. 

I think really the only thing I did differently was GenerateKeys, and swapped Encrypt/Decrypt to use one key instead of two. Hope you don’t mind me using it. 

I can’t fully test it til I get back to my PC but it looks like that did the trick!

Share this post


Link to post
Share on other sites
18 minutes ago, W1cked said:

I think what I may have done is copied the functions and rewrote it to match your Comet source, because the code comments don’t look like something I would write. I’m not that detailed. 

I think really the only thing I did differently was GenerateKeys, and swapped Encrypt/Decrypt to use one key instead of two. Hope you don’t mind me using it. 

I can’t fully test it til I get back to my PC but it looks like that did the trick!

Oh, I don't mind. That's why there're public under an open source license.

  • Thanks 1

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

×

Important Information

By using this site, you agree to our Terms of Use.