Here is my implementation of WoW packet decryption written in C. The ring buffer implementation may require some minor tweaks to work on windows. If anyone wants to modify it and post the changes I'll incorporate them and edit my post.
Everything should work as is on linux or OSX.
Credits:
- I learned how the encryption works from the mangos server emu source
- I'm using the OpenSSL implementations of HMAC and RC4.
- The ring buffer implementation is from the wikipedia page on circular buffers at Circular buffer - Wikipedia, the free encyclopedia
- Includes a fix from Nonal for large packets.
The whole decryption implementation is only 142 lines, so I thought it might be useful to the community as a clean, easy to read reference implementation. It's also quite fast, not that it really matters.
How to use it:
Before doing anything else, instantiate a wowcrypt:
Code:
struct wowcrypt s;
int order = 22;
wowcrypt_init(&s, order);
This single instance will handle data going both from the wow server to the client and vice versa. The ring buffers for storing the inbound/outbound packet data will be 2^order bytes long, so in my case 2^22 = 4MB. The ring buffers must be at least as large as the largest wow message.
When you want to clean up, delete the wowcrypt:
Code:
wowcrypt_destroy(&s);
You then need some way of getting the raw WoW network traffic into the buffers. You could do this by hooking the winsock send/recv, or by running an out of process packet sniffer, etc. At the end of the day, you should have some bytes, you insert them into the wowcrypt like this, given 'len' bytes in 'buf':
Code:
/* data going from wow server to wow client */
if (wowcrypt_free_bytes(&s,WOWCRYPT_INBOUND) < len) { not enough room! }
wowcrypt_insert(&s,WOWCRYPT_INBOUND,len,buf);
Swap WOWCRYPT_INBOUND for WOWCRYPT_OUTBOUND for data going the other way. wowcrypt_insert assumes there is enough room to insert the data, so you must check this yourself with wowcrypt_free_bytes and do something if there is not enough space. This should not happen if you always pop messages after inserting data and your buffer is large enough, depends on how your bot is organized.
WoW has two static HMAC keys which I've hard coded into the source. I've never seen these change. There is also a 40 byte session key that must be read from memory once WoW has instantiated the client connection.
As of 3.3.2 [11403], this key can be found starting at [[0x00C93410]+0x508].
The messages sent between the WoW client and the WoW server are unencrypted until the authentication handshake has been performed, at which point the headers become encrypted. In my experience on private servers, the handshake messages are always the first message going in each direction, but that may not always be true.
The code to setup decryption will look something like like this, note I'm assuming in this that all the unencrypted messages have already been received for simplicity:
Code:
/* pop the unencrypted messages before enabling encryption */
do {
wowcrypt_get_msg(&s,WOWCRYPT_INBOUND,&opcode,&len,&data);
/* do something with the message before popping it */
wowcrypt_pop_msg(&s,WOWCRYPT_INBOUND);
} while (not the inbound auth msg);
do {
wowcrypt_get_msg(&s,WOWCRYPT_OUTBOUND,&opcode,&len,&data);
/* do something with the message before popping it */
wowcrypt_pop_msg(&s,WOWCRYPT_OUTBOUND);
} while (not the outbound auth msg);
/* set the session key and enable decryption from here out */
wowcrypt_enable(&s,session_key,40);
From this point forward, messages will be decrypted on the fly, so further messages can be obtained as follows:
Code:
int opcode,length;
unsigned char *data;
if (wowcrypt_get_msg(&s,WOWCRYPT_INBOUND,&opcode,&len,&data)) {
/* do something with the message before popping it */
wowcrypt_pop_msg(&s,WOWCRYPT_INBOUND);
}
if (wowcrypt_get_msg(&s,WOWCRYPT_OUTBOUND,&opcode,&len,&data)) {
/* do something with the message before popping it */
wowcrypt_pop_msg(&s,WOWCRYPT_OUTBOUND);
}
wowcrypt_get_msg either returns zero if no message is ready, or returns 1 and sets opcode, len, and data to the opcode, length, and data pointer of the next decrypted message.
The 'len' will be the amount of data after the opcode. You can learn about what the different message types are and what they do by reading the mangos source code.
Note that the copy of the message you get out of wowcrypt_get_msg is still owned by wowcrypt and will be invalidated when you call wowcrypt_pop_msg to advance to the next message, so treat it as a read only and take your own copy if you need to keep it.
Here's the wowcrypt source:
Code:
/*
* WoW packet decryption
* pendra@mmowned
*
*/
#include <openssl/hmac.h>
#include <openssl/sha.h>
#include <openssl/evp.h>
#include "ringbuffer.h"
#define WOWCRYPT_SEED_KEY_SIZE 16
#define WOWCRYPT_DISCARD_LEN 1024
#define WOWCRYPT_INBOUND 0
#define WOWCRYPT_OUTBOUND 1
static const int wowcrypt_oplen[2] = { 2, 4 };
static const char wowcrypt_keys[2][WOWCRYPT_SEED_KEY_SIZE] =
{
{ 0x22, 0xBE, 0xE5, 0xCF, 0xBB, 0x07, 0x64, 0xD9, 0x00, 0x45, 0x1B, 0xD0, 0x24, 0xB8, 0xD5, 0x45 },
{ 0xF4, 0x66, 0x31, 0x59, 0xFC, 0x83, 0x6E, 0x31, 0x31, 0x02, 0x51, 0xD5, 0x44, 0x31, 0x67, 0x98 },
};
struct wowcrypt
{
EVP_CIPHER_CTX sarc4[2];
HMAC_CTX hmac[2];
unsigned char digest[2][SHA_DIGEST_LENGTH];
struct ring_buffer rb[2];
int hdr[2];
int enabled;
};
void wowcrypt_init(struct wowcrypt *s,int rb_order)
{
int i;
for (i = 0; i < 2 ; i++) {
HMAC_CTX_init(&s->hmac[i]);
HMAC_Init_ex(&s->hmac[i], wowcrypt_keys[i], WOWCRYPT_SEED_KEY_SIZE, EVP_sha1(), NULL);
EVP_CIPHER_CTX_init(&s->sarc4[i]);
ring_buffer_create(&s->rb[i],rb_order);
s->hdr[i] = 0;
}
s->enabled = 0;
}
void wowcrypt_destroy(struct wowcrypt *s)
{
int i;
for (i = 0; i < 2 ; i++) {
EVP_CIPHER_CTX_cleanup(&s->sarc4[i]);
HMAC_CTX_cleanup(&s->hmac[i]);
ring_buffer_free(&s->rb[i]);
}
}
void wowcrypt_enable(struct wowcrypt *s,unsigned char *seed, int length)
{
unsigned char buf[WOWCRYPT_DISCARD_LEN];
unsigned int hlen;
int len;
int i;
for (i = 0; i < 2 ; i++){
HMAC_Update(&s->hmac[i], seed, length);
HMAC_Final(&s->hmac[i], s->digest[i], &hlen);
EVP_EncryptInit_ex(&s->sarc4[i], EVP_rc4(), NULL, NULL, NULL);
EVP_CIPHER_CTX_set_key_length(&s->sarc4[i], SHA_DIGEST_LENGTH);
EVP_EncryptInit_ex(&s->sarc4[i], NULL, NULL, s->digest[i], NULL);
memset(buf,0,WOWCRYPT_DISCARD_LEN);
EVP_EncryptUpdate(&s->sarc4[i],buf,&len,buf,WOWCRYPT_DISCARD_LEN);
EVP_EncryptFinal_ex(&s->sarc4[i],buf,&len);
}
s->enabled = 1;
}
int wowcrypt_free_bytes(struct wowcrypt *s,int dir) {
return ring_buffer_count_free_bytes(&s->rb[dir & 0x1]);
}
void wowcrypt_insert(struct wowcrypt *s, int dir, int len, unsigned char *data)
{
struct ring_buffer *rb = &s->rb[dir & 0x1];
memcpy(ring_buffer_write_address(rb),data,len);
ring_buffer_write_advance(rb,len);
}
int wowcrypt_get_msg(struct wowcrypt *s, int dir, int *opcode,int *length, unsigned char **data)
{
const int d = dir & 0x01;
struct ring_buffer *rb = &s->rb[d];
int *hdr = &s->hdr[d];
const int oplen = wowcrypt_oplen[d];
const int bl = ring_buffer_count_bytes(rb);
unsigned char *ba = ring_buffer_read_address(rb);
int sizelen = 2;
int l;
if (*hdr==0) {
if (bl >= sizelen + oplen) {
if (s->enabled) {
EVP_EncryptUpdate(&s->sarc4[d],ba,&l,ba,sizelen + oplen);
EVP_EncryptFinal_ex(&s->sarc4[d],ba,&l);
}
*hdr = 1;
} else {
return 0;
}
}
if (*ba >= 0x80) {
sizelen = 3;
}
if (*hdr == 1 && sizelen == 3) {
if (bl >= sizelen + oplen) {
if (s->enabled) {
EVP_EncryptUpdate(&s->sarc4[d],ba+oplen+2,&l,ba+oplen+2,1);
EVP_EncryptFinal_ex(&s->sarc4[d],ba+oplen+2,&l);
}
*hdr = 2;
} else {
return 0;
}
} else {
*hdr = 2;
}
l = ntohs(*(unsigned short*)(ba+sizelen-2));
if (sizelen==3) {
unsigned int added = (*ba) & 0x7F;
l += added<<16;
}
if (bl >= sizelen + l) {
*length = l - oplen;
*opcode = (oplen == 2) ? *(unsigned short*)(ba+sizelen) : *(unsigned int*)(ba+sizelen);
*data = ba + sizelen + oplen;
return 1;
} else {
return 0;
}
}
void wowcrypt_pop_msg(struct wowcrypt *s, int dir)
{
const int d = dir & 0x01;
struct ring_buffer *rb = &s->rb[d];
int *hdr = &s->hdr[d];
const unsigned char *ba = ring_buffer_read_address(rb);
if (*ba < 0x80) {
ring_buffer_read_advance(rb,2 + ntohs(*(unsigned short*)(ba)));
} else {
ring_buffer_read_advance(rb,3 + ntohs(*(unsigned short*)(ba+1)) + ((*ba & 0x7f)<<16) );
}
*hdr = 0;
}
Here's the ring buffer implementation it uses:
Code:
/*
* This was taken verbatim from http://en.wikipedia.org/wiki/Circular_buffer
*/
#ifndef __RINGBUFFER_H__
#define __RINGBUFFER_H__
#include <sys/mman.h>
#include <stdlib.h>
#include <unistd.h>
#define report_exceptional_condition() abort ()
struct ring_buffer {
char *address;
unsigned long count_bytes;
unsigned long write_offset_bytes;
unsigned long read_offset_bytes;
};
void ring_buffer_create(struct ring_buffer *buffer, unsigned long order) {
char path[] = "/dev/shm/ring-buffer-XXXXXX";
int file_descriptor;
void *address;
int status;
file_descriptor = mkstemp (path);
if (file_descriptor < 0) report_exceptional_condition ();
status = unlink (path);
if (status) report_exceptional_condition ();
buffer->count_bytes = 1UL << order;
buffer->write_offset_bytes = 0;
buffer->read_offset_bytes = 0;
status = ftruncate (file_descriptor, buffer->count_bytes);
if (status) report_exceptional_condition ();
buffer->address = mmap(NULL, buffer->count_bytes << 1, PROT_NONE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (buffer->address == MAP_FAILED) report_exceptional_condition ();
address = mmap(buffer->address, buffer->count_bytes, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_SHARED, file_descriptor, 0);
if (address != buffer->address) report_exceptional_condition ();
address = mmap(buffer->address + buffer->count_bytes,
buffer->count_bytes, PROT_READ | PROT_WRITE,
MAP_FIXED | MAP_SHARED, file_descriptor, 0);
if (address != buffer->address + buffer->count_bytes) report_exceptional_condition ();
status = close(file_descriptor);
if (status) report_exceptional_condition ();
}
void ring_buffer_free (struct ring_buffer *buffer) {
int status;
status = munmap (buffer->address, buffer->count_bytes << 1);
if (status) report_exceptional_condition ();
}
void *ring_buffer_write_address (struct ring_buffer *buffer) {
return buffer->address + buffer->write_offset_bytes;
}
oid ring_buffer_write_advance (struct ring_buffer *buffer,
unsigned long count_bytes) {
buffer->write_offset_bytes += count_bytes;
}
void *ring_buffer_read_address (struct ring_buffer *buffer) {
return buffer->address + buffer->read_offset_bytes;
}
void ring_buffer_read_advance (struct ring_buffer *buffer,
unsigned long count_bytes) {
buffer->read_offset_bytes += count_bytes;
if (buffer->read_offset_bytes >= buffer->count_bytes) {
buffer->read_offset_bytes -= buffer->count_bytes;
buffer->write_offset_bytes -= buffer->count_bytes;
}
}
unsigned long ring_buffer_count_bytes (struct ring_buffer *buffer) {
return buffer->write_offset_bytes - buffer->read_offset_bytes;
}
unsigned long ring_buffer_count_free_bytes (struct ring_buffer *buffer) {
return buffer->count_bytes - ring_buffer_count_bytes (buffer);
}
void ring_buffer_clear (struct ring_buffer *buffer) {
buffer->write_offset_bytes = 0;
buffer->read_offset_bytes = 0;
}
#endif
Enjoy...