Samstag, 9. Oktober 2010

On to the character selection

As you might have seen in this this video we got to the point of entering the character selection screen. This is a really good start. Today I got the latest copy of code by the videos author. I'm currently reviewing it and will hopefully put it to gitorious later.

The code itself is written using C#. This allows rapid prototyping of applications and good development speed. The code is capable of handling the login but you can't play the game yet. It basically is split into 2 states
  • loginStage
  • marginStage
During the loginState 15 packets will be sent by the server. In the following marginState 4 packets will be sent. We don't handle ping-pong-packets to verify the server is up and running yet. Also right now the code is not multi threaded.

The biggest downside right now is that we can send the packets the client expects but don't know what they mean in detail.

So stay tuned for further updates.

Mittwoch, 7. April 2010

Moving the code to gitorious

I wanted to test gitorious for a while now so I decided it was a good idea to host the code I wrote there. This allows regular readers to follow the progress and even to contribute back.

Right now it's a big junkyard but I'll continue cleaning it up as soon as time permits.

A few words to the repositories:

  • gls is the meta server written in C# which provides the infratructure to select a server from the client
  • htdocs is the content the client needs to properly display something
  • server is where the server code will be written
  • patches is the set of wine patches I wrote to trace what lotro is doing

http://gitorious.org/lotro/patches

Sonntag, 17. Januar 2010

Mimicing the clients behaviour

I'm still trying to understand what exactly the client is doing during startup. I wrote the following program to rebuild the behaviour of the client. It's not really good yet, especially the part that is mimicing the winsock stuff. I'm certainly unclear about what the client is sending. First I thought it was constant data, but then again it was completely different. Anyway, here is the program:

/* winegcc lotro-behaviour.c -Isource/wine/include/ -lws2_32 -o lotro-behaviour && ./lotro-behaviour.exe */

#include 
#include 
#include 

/* Copy from rsaenh.c */
#define RSAENH_MAGIC_RSA2        0x32415352
#define RSAENH_MAGIC_RSA1        0x31415352

/* This method is used to dump a region of memory */
static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) {
 DWORD i;
 if (pbData && dwDataLen > 0) {
  for (i = 0; i < dwDataLen; i++) {
   printf("0x%02x (%d)\n", pbData[i], pbData[i]);
  }
 }
}

/* Load the clients private key into the given pointer */
static void load_private_key(CONST BYTE * pbData) {
 BLOBHEADER * pHeader = (BLOBHEADER *) pbData;
 pHeader->bType = PRIVATEKEYBLOB;
 pHeader->bVersion = 0x2;
 pHeader->reserved = 0;
 pHeader->aiKeyAlg = CALG_RSA_KEYX;

 RSAPUBKEY * pRsaPubKey = (RSAPUBKEY *) (pbData + sizeof(BLOBHEADER));
 pRsaPubKey->magic = RSAENH_MAGIC_RSA2;
 pRsaPubKey->bitlen = 512;
 pRsaPubKey->pubexp = 1;

 BYTE pbSrc[288] = {
  0xab, 0xef, 0xfa, 0xc6, 0x7d, 0xe8, 0xde, 0xfb,
  0x68, 0x38, 0x09, 0x92, 0xd9, 0x42, 0x7e, 0x6b,
  0x89, 0x9e, 0x21, 0xd7, 0x52, 0x1c, 0x99, 0x3c,
  0x17, 0x48, 0x4e, 0x3a, 0x44, 0x02, 0xf2, 0xfa,
  0x74, 0x57, 0xda, 0xe4, 0xd3, 0xc0, 0x35, 0x67,
  0xfa, 0x6e, 0xdf, 0x78, 0x4c, 0x75, 0x35, 0x1c,
  0xa0, 0x74, 0x49, 0xe3, 0x20, 0x13, 0x71, 0x35,
  0x65, 0xdf, 0x12, 0x20, 0xf5, 0xf5, 0xf5, 0xc1,
  0xed, 0x5c, 0x91, 0x36, 0x75, 0xb0, 0xa9, 0x9c,
  0x04, 0xdb, 0x0c, 0x8c, 0xbf, 0x99, 0x75, 0x13,
  0x7e, 0x87, 0x80, 0x4b, 0x71, 0x94, 0xb8, 0x00,
  0xa0, 0x7d, 0xb7, 0x53, 0xdd, 0x20, 0x63, 0xee,
  0xf7, 0x83, 0x41, 0xfe, 0x16, 0xa7, 0x6e, 0xdf,
  0x21, 0x7d, 0x76, 0xc0, 0x85, 0xd5, 0x65, 0x7f,
  0x00, 0x23, 0x57, 0x45, 0x52, 0x02, 0x9d, 0xea,
  0x69, 0xac, 0x1f, 0xfd, 0x3f, 0x8c, 0x4a, 0xd0,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x64, 0xd5, 0xaa, 0xb1, 0xa6, 0x03, 0x18, 0x92,
  0x03, 0xaa, 0x31, 0x2e, 0x48, 0x4b, 0x65, 0x20,
  0x99, 0xcd, 0xc6, 0x0c, 0x15, 0x0c, 0xbf, 0x3e,
  0xff, 0x78, 0x95, 0x67, 0xb1, 0x74, 0x5b, 0x60,
  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 };
 memcpy((void *)pbData + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), &pbSrc, 288);
}

/* Load the servers public key into the given pointer */
static void load_public_key(CONST BYTE * pbData) {
 BLOBHEADER * pHeader = (BLOBHEADER *) pbData;
 pHeader->bType = PUBLICKEYBLOB;
 pHeader->bVersion = 0x2;
 pHeader->reserved = 0;
 pHeader->aiKeyAlg = CALG_RSA_KEYX;

 RSAPUBKEY * pRsaPubKey = (RSAPUBKEY *) (pbData + sizeof(BLOBHEADER));
 pRsaPubKey->magic = RSAENH_MAGIC_RSA1;
 pRsaPubKey->bitlen = 1024;
 pRsaPubKey->pubexp = 0x010001;

 BYTE pbSrc[128] = {
  0x4d, 0x03, 0x8d, 0xd7, 0xd8, 0xa8, 0x85, 0x97,
  0x2d, 0x04, 0xde, 0x57, 0xe2, 0x77, 0xc4, 0xbf,
  0xc6, 0x13, 0x15, 0x0e, 0x26, 0x72, 0x99, 0x11,
  0xa1, 0xcd, 0xf3, 0x8f, 0xd5, 0x5d, 0x63, 0x4c,
  0xf3, 0x75, 0xb7, 0x62, 0x9d, 0xee, 0xe8, 0xd5,
  0xc5, 0xa7, 0x0e, 0x3c, 0x9c, 0x51, 0xd4, 0x3f,
  0x00, 0x45, 0xb2, 0xed, 0x19, 0xb1, 0xca, 0xda,
  0x84, 0x1c, 0x6b, 0x35, 0x41, 0x17, 0x46, 0xd0,
  0x56, 0xb9, 0x4c, 0x06, 0xcd, 0x8d, 0xc5, 0x50,
  0x39, 0x36, 0x4e, 0xe3, 0x9b, 0x5e, 0x3c, 0x44,
  0xe8, 0x04, 0x0e, 0x90, 0xc4, 0x89, 0xfc, 0x24,
  0xe9, 0xc4, 0xff, 0xc6, 0x97, 0xfd, 0xd6, 0x5d,
  0x2b, 0x67, 0x5c, 0xa2, 0x7f, 0x8d, 0x2a, 0xb9,
  0x5a, 0x52, 0x3c, 0xec, 0x0d, 0xc1, 0x55, 0xc8,
  0xc1, 0xb0, 0x07, 0x3a, 0xdc, 0x03, 0x9c, 0x38,
  0x34, 0x08, 0x0e, 0x19, 0x27, 0xcd, 0x7b, 0xd0
 };
 memcpy((void *)pbData + sizeof(BLOBHEADER) + sizeof(RSAPUBKEY), &pbSrc, 128);
}

void main() {
 int retval;
 char* localIP;
 BOOLEAN first = TRUE;
 BYTE bDataPrivKey[sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + 288];
 BYTE bDataPubKey[sizeof(BLOBHEADER) + sizeof(RSAPUBKEY) + 128];

 WSADATA wsaData;
 retval = WSAStartup(2, &wsaData);
 if (retval != 0) {
  printf("WSAStartup returned %d\n", retval);
  return;
 }
 SOCKET socket = WSASocket(AF_INET, SOCK_DGRAM, 0, (LPWSAPROTOCOL_INFO) NULL, 0, 0x1 /* What is this for */);
 BYTE name[256];
 gethostname(&name, 256);
 struct hostent* localHost = gethostbyname((const CHAR *) &name);
 localIP = inet_ntoa (*(struct in_addr *)*localHost->h_addr_list);

 struct sockaddr_in addr;
 addr.sin_family = AF_INET;
 /* addr.sin_addr.s_addr = inet_addr(localIP); */
 addr.sin_addr.s_addr = inet_addr("0.0.0.0");
 addr.sin_port = htons(10691);
 retval = bind(socket, (const struct sockaddr *) &addr, sizeof(addr));
 if (retval != 0) {
  printf("bind returned %d\n", retval);
  closesocket(socket);
  WSACleanup();
  return;
 }

 CHAR optval[4];
 int optlen = 4;
 retval = getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&optval, &optlen);
 if (retval != 0) {
  printf("bind returned %d\n", retval);
  closesocket(socket);
  WSACleanup();
  return;
 }
 //byte_dump((CONST BYTE *) &optval, 4);
 retval = getsockopt(socket, SOL_SOCKET, SO_RCVBUF, (char *)&optval, &optlen);
 //byte_dump((CONST BYTE *) &optval, 4);

 HCRYPTPROV hProv;
 CryptAcquireContext(&hProv, "", MS_ENHANCED_PROV, 1, CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET);

 /* Loads the private key */
 BYTE bDataPriv[512];
 DWORD dwDataPrivLen = 512;
 first = TRUE;
 do {
  if (!CryptGetProvParam(hProv, PP_ENUMALGS_EX, (BYTE *)&bDataPriv, &dwDataPrivLen, first ? CRYPT_FIRST : CRYPT_NEXT)) {
   printf("No more CryptProvParam to fullfill CALG_RSA_KEYX for Priv\n");
   break;
  }
  first = FALSE;
 } while (((PROV_ENUMALGS_EX *) &bDataPriv)->aiAlgid != CALG_RSA_KEYX);

 load_private_key((CONST BYTE * ) &bDataPrivKey);
 HCRYPTKEY hPrivKey;
 CryptImportKey(hProv, (BYTE *)&bDataPrivKey, sizeof(bDataPrivKey), 0, CRYPT_EXPORTABLE, &hPrivKey);

 /* Loads the public key */
 BYTE bDataPub1[512];
 DWORD dwDataPubLen1 = 512;
 first = TRUE;
 do {
  if (!CryptGetProvParam(hProv, PP_ENUMALGS_EX, (BYTE *)&bDataPub1, &dwDataPubLen1, first ? CRYPT_FIRST : CRYPT_NEXT)) {
   printf("No more CryptProvParam to fullfill CALG_RSA_KEYX for Pub\n");
   break;
  }
  first = FALSE;
 } while (((PROV_ENUMALGS_EX *) &bDataPub1)->aiAlgid != CALG_RSA_KEYX);

 BYTE bDataPub2[512];
 DWORD dwDataPubLen2 = 512;
 first = TRUE;
 do {
  if (!CryptGetProvParam(hProv, PP_ENUMALGS_EX, (BYTE *)&bDataPub2, &dwDataPubLen2, first ? CRYPT_FIRST : CRYPT_NEXT)) {
   printf("No more CryptProvParam to fullfill CALG_RC4 for Pub\n");
   break;
  }
  first = FALSE;
 } while (((PROV_ENUMALGS_EX *) &bDataPub2)->aiAlgid != CALG_RC4);

 load_public_key((CONST BYTE * ) &bDataPubKey);
 HCRYPTKEY hPubKey;
 CryptImportKey(hProv, (BYTE *)&bDataPubKey, sizeof(bDataPubKey), 0, CRYPT_EXPORTABLE, &hPubKey);


 /* CryptGenKey */
 HCRYPTKEY hRcKey;
 CryptGenKey (hProv, CALG_RC4, CRYPT_EXPORTABLE, &hRcKey);

 /* CryptExportKey */
 DWORD dwDataLen;
 CryptExportKey(hRcKey, hPubKey, SIMPLEBLOB, 0, 0, &dwDataLen);

 BYTE * pbData = malloc(dwDataLen);
 CryptExportKey(hRcKey, hPubKey, SIMPLEBLOB, 0, pbData, &dwDataLen);

 BYTE encryptData1[] = {
  0x01, 0x32, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
  0x31
 };
 DWORD encryptData1Len = 9;
 CryptEncrypt(hRcKey, 0, TRUE, 0, 0, &encryptData1Len, 9);
 CryptEncrypt(hRcKey, 0, TRUE, 0, (BYTE *) &encryptData1, &encryptData1Len, 9);


 char buffer0[] = {
  0x54, 0xa7
 };
 char buffer1[] = {
  0xb0, 0x02, 0x0f, 0x08, 0xed, 0x00, 0x00, 0x00,
  0x48, 0xfe, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00
 };
 char buffer2[] = {
  0x48, 0xfe, 0x0d, 0x08, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00
 };
 WSABUF wsabufs[3];
 wsabufs[0].len = sizeof(buffer0);
 wsabufs[0].buf = malloc(sizeof(buffer0));
 memcpy(wsabufs[0].buf, &buffer0, sizeof(buffer0));
 wsabufs[1].len = sizeof(buffer1);
 wsabufs[1].buf = malloc(sizeof(buffer1));
 memcpy(wsabufs[1].buf, &buffer1, sizeof(buffer1));
 wsabufs[2].len = sizeof(buffer2);
 wsabufs[2].buf = malloc(sizeof(buffer2));
 memcpy(wsabufs[2].buf, &buffer2, sizeof(buffer2));
 struct sockaddr_in to;
 to.sin_family = AF_INET;
 to.sin_port = htons(9000);
 to.sin_addr.s_addr = inet_addr("192.168.1.140");
 DWORD bytesSent;
 WSASendTo(socket, (LPWSABUF)&wsabufs, 3, &bytesSent, 0, (const struct sockaddr *) &to, 16, 0, 0);

 free(pbData);
 CryptReleaseContext(hProv, 0);

 closesocket(socket);
 WSACleanup();
}

Based on what I learned I tried to look at what the server needs to do as initial setup. I came up with the following dummy:

/* Run with: winegcc lotro-server.c -Isource/wine/include/ -lws2_32 -o lotro-server && ./lotro-server.exe */

#include 
#include 
#include 
#include 

/* This method is used to dump a region of memory */
static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) {
 DWORD i;
 if (pbData && dwDataLen > 0) {
  for (i = 0; i < dwDataLen; i++) {
   printf("0x%02x (%d)\n", pbData[i], pbData[i]);
  }
 }
}

int main() {
 WORD wVersionRequested;
 WSADATA wsaData;
 int err = 0, remoteAddrLen = sizeof(SOCKADDR_IN);
 SOCKET s = INVALID_SOCKET;
 struct addrinfo hints, *result = NULL, *remoteAddr;
 char buf[256];

 /* Initialize winsock with version 2.2 */
 wVersionRequested = MAKEWORD(2, 2);
 err = WSAStartup(wVersionRequested, &wsaData);
 if (err != NO_ERROR) {
  printf("WSAStartup failed: %d\n", err);
  return -1;
 }

 /* Verify the system is winsock 2.2 capable */
 if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
  printf("WSAStartup returned wrong version\n");
  WSACleanup();
  return -2;
 }

 /* Determine our local address */
 hints.ai_family = AF_INET;
 hints.ai_socktype = SOCK_DGRAM;
 hints.ai_protocol = 0;
 hints.ai_flags = AI_PASSIVE;
 err = getaddrinfo(NULL, "9000", &hints, &result);
 if (err != 0) {
  printf("getaddrinfo failed: %d\n", err);
  WSACleanup();
  return -3;
 }

 s = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
 if (s == INVALID_SOCKET) {
  printf("Creating the socket failed: %d\n", WSAGetLastError());
  freeaddrinfo(result);
  WSACleanup();
  return -4;
 }

 err = bind(s, result->ai_addr, (int)result->ai_addrlen);
 if (err == SOCKET_ERROR) {
  printf("Binding failed: %d\n", WSAGetLastError());
  closesocket(s);
  freeaddrinfo(result);
  WSACleanup();
  return -5;
 }

 printf("Waiting for client\n");

 while(1) {
  err = recvfrom(s, buf, 256, 0, (SOCKADDR *) &remoteAddr, &remoteAddrLen);
  if (err == SOCKET_ERROR) {
   printf("Recieve failed: %d\n", WSAGetLastError());
   closesocket(s);
   freeaddrinfo(result);
   WSACleanup();
   return -6;
  }
  printf("Read %d bytes\n", err);
  byte_dump((CONST BYTE *) &buf, err);

  err = sendto(s ,buf, err, 0, (SOCKADDR*)&remoteAddr, remoteAddrLen);
  if (err == SOCKET_ERROR) {
   printf("Send failed: %d\n", WSAGetLastError());
   closesocket(s);
   freeaddrinfo(result);
   WSACleanup();
   return -7;
  }
  printf("Sent %d bytes\n", err);
 }

 closesocket(s);
 freeaddrinfo(result);
 WSACleanup();
 return 0;
}

Dienstag, 12. Januar 2010

Houston, we have a key

I finally got some time to hack around in the source code of wine to allow it to dump the key that will be imported. This hacky patch is capable of dumping the private RSA key that will be used for exchanging the RC4 key that will be used during client and server communication.

diff --git a/dlls/rsaenh/rsaenh.c b/dlls/rsaenh/rsaenh.c
index 665bc40..9c9dd5e 100644
--- a/dlls/rsaenh/rsaenh.c
+++ b/dlls/rsaenh/rsaenh.c
@@ -3016,9 +3016,16 @@ static BOOL import_key(HCRYPTPROV hProv, CONST BYTE *pbData, DWORD dwDataLen,
 BOOL WINAPI RSAENH_CPImportKey(HCRYPTPROV hProv, CONST BYTE *pbData, DWORD dwDataLen,
                                HCRYPTKEY hPubKey, DWORD dwFlags, HCRYPTKEY *phKey)
 {
+    DWORD i;
     TRACE("(hProv=%08lx, pbData=%p, dwDataLen=%d, hPubKey=%08lx, dwFlags=%08x, phKey=%p)\n",
         hProv, pbData, dwDataLen, hPubKey, dwFlags, phKey);

+    if (pbData && dwDataLen > 0) {
+        for (i = 0; i < dwDataLen; i++) {
+            printf("0x%02x (%d)\n", pbData[i], pbData[i]);
+       }
+    }
+
     if (dwFlags & CRYPT_IPSEC_HMAC_KEY)
     {
         FIXME("unimplemented for CRYPT_IPSEC_HMAC_KEY\n");
This patch dumps the 308 bytes of data that are the exported private key. So you are wondering how the private key looks like?
0x07 (7)
0x02 (2)
0x00 (0)
0x00 (0)
0x00 (0)
0xa4 (164)
0x00 (0)
0x00 (0)
0x52 (82)
0x53 (83)
0x41 (65)
0x32 (50)
0x00 (0)
0x02 (2)
0x00 (0)
0x00 (0)
0x01 (1)
0x00 (0)
0x00 (0)
0x00 (0)
0xab (171)
0xef (239)
0xfa (250)
0xc6 (198)
0x7d (125)
0xe8 (232)
0xde (222)
0xfb (251)
0x68 (104)
0x38 (56)
0x09 (9)
0x92 (146)
0xd9 (217)
0x42 (66)
0x7e (126)
0x6b (107)
0x89 (137)
0x9e (158)
0x21 (33)
0xd7 (215)
0x52 (82)
0x1c (28)
0x99 (153)
0x3c (60)
0x17 (23)
0x48 (72)
0x4e (78)
0x3a (58)
0x44 (68)
0x02 (2)
0xf2 (242)
0xfa (250)
0x74 (116)
0x57 (87)
0xda (218)
0xe4 (228)
0xd3 (211)
0xc0 (192)
0x35 (53)
0x67 (103)
0xfa (250)
0x6e (110)
0xdf (223)
0x78 (120)
0x4c (76)
0x75 (117)
0x35 (53)
0x1c (28)
0xa0 (160)
0x74 (116)
0x49 (73)
0xe3 (227)
0x20 (32)
0x13 (19)
0x71 (113)
0x35 (53)
0x65 (101)
0xdf (223)
0x12 (18)
0x20 (32)
0xf5 (245)
0xf5 (245)
0xf5 (245)
0xc1 (193)
0xed (237)
0x5c (92)
0x91 (145)
0x36 (54)
0x75 (117)
0xb0 (176)
0xa9 (169)
0x9c (156)
0x04 (4)
0xdb (219)
0x0c (12)
0x8c (140)
0xbf (191)
0x99 (153)
0x75 (117)
0x13 (19)
0x7e (126)
0x87 (135)
0x80 (128)
0x4b (75)
0x71 (113)
0x94 (148)
0xb8 (184)
0x00 (0)
0xa0 (160)
0x7d (125)
0xb7 (183)
0x53 (83)
0xdd (221)
0x20 (32)
0x63 (99)
0xee (238)
0xf7 (247)
0x83 (131)
0x41 (65)
0xfe (254)
0x16 (22)
0xa7 (167)
0x6e (110)
0xdf (223)
0x21 (33)
0x7d (125)
0x76 (118)
0xc0 (192)
0x85 (133)
0xd5 (213)
0x65 (101)
0x7f (127)
0x00 (0)
0x23 (35)
0x57 (87)
0x45 (69)
0x52 (82)
0x02 (2)
0x9d (157)
0xea (234)
0x69 (105)
0xac (172)
0x1f (31)
0xfd (253)
0x3f (63)
0x8c (140)
0x4a (74)
0xd0 (208)
0x01 (1)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x01 (1)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x64 (100)
0xd5 (213)
0xaa (170)
0xb1 (177)
0xa6 (166)
0x03 (3)
0x18 (24)
0x92 (146)
0x03 (3)
0xaa (170)
0x31 (49)
0x2e (46)
0x48 (72)
0x4b (75)
0x65 (101)
0x20 (32)
0x99 (153)
0xcd (205)
0xc6 (198)
0x0c (12)
0x15 (21)
0x0c (12)
0xbf (191)
0x3e (62)
0xff (255)
0x78 (120)
0x95 (149)
0x67 (103)
0xb1 (177)
0x74 (116)
0x5b (91)
0x60 (96)
0x01 (1)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
0x00 (0)
The first 20 bytes are header data and the rest is the key. This is really good! Now we can use the private key to generate a public key from it. But we need to code to recreated this key first. I reengineerd the code and it looks like this:
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) {
 DWORD i;
 if (pbData && dwDataLen > 0) {
  for (i = 0; i < dwDataLen; i++) {
   printf("0x%02x (%d)\n", pbData[i], pbData[i]);
  }
 }
}

int main() {

 BLOBHEADER header;
 header.bType = 0x7; /* PRIVATEKEYBLOB */
 header.bVersion = 2; /* Must be 2 */
 header.reserved = 0; /* Must be 0 */
 header.aiKeyAlg = 0xa400;
 byte_dump((CONST BYTE *) &header, sizeof(BLOBHEADER));

 RSAPUBKEY rsaPubKey;
 rsaPubKey.magic = 0x32415352; /* RSAENH_MAGIC_RSA2 */
 rsaPubKey.bitlen = 0x00000200; /* 512 bit long */
 rsaPubKey.pubexp = 0x00000001;
 byte_dump((CONST BYTE *) &rsaPubKey, sizeof(RSAPUBKEY));

 BYTE pbSrc[288] = {
  0xab, 0xef, 0xfa, 0xc6,
  0x7d, 0xe8, 0xde, 0xfb, 0x68, 0x38, 0x09, 0x92,
  0xd9, 0x42, 0x7e, 0x6b, 0x89, 0x9e, 0x21, 0xd7,
  0x52, 0x1c, 0x99, 0x3c, 0x17, 0x48, 0x4e, 0x3a,
  0x44, 0x02, 0xf2, 0xfa, 0x74, 0x57, 0xda, 0xe4,
  0xd3, 0xc0, 0x35, 0x67, 0xfa, 0x6e, 0xdf, 0x78,
  0x4c, 0x75, 0x35, 0x1c, 0xa0, 0x74, 0x49, 0xe3,
  0x20, 0x13, 0x71, 0x35, 0x65, 0xdf, 0x12, 0x20,
  0xf5, 0xf5, 0xf5, 0xc1, 0xed, 0x5c, 0x91, 0x36,
  0x75, 0xb0, 0xa9, 0x9c, 0x04, 0xdb, 0x0c, 0x8c,
  0xbf, 0x99, 0x75, 0x13, 0x7e, 0x87, 0x80, 0x4b,
  0x71, 0x94, 0xb8, 0x00, 0xa0, 0x7d, 0xb7, 0x53,
  0xdd, 0x20, 0x63, 0xee, 0xf7, 0x83, 0x41, 0xfe,
  0x16, 0xa7, 0x6e, 0xdf, 0x21, 0x7d, 0x76, 0xc0,
  0x85, 0xd5, 0x65, 0x7f, 0x00, 0x23, 0x57, 0x45,
  0x52, 0x02, 0x9d, 0xea, 0x69, 0xac, 0x1f, 0xfd,
  0x3f, 0x8c, 0x4a, 0xd0, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x64, 0xd5, 0xaa, 0xb1,
  0xa6, 0x03, 0x18, 0x92, 0x03, 0xaa, 0x31, 0x2e,
  0x48, 0x4b, 0x65, 0x20, 0x99, 0xcd, 0xc6, 0x0c,
  0x15, 0x0c, 0xbf, 0x3e, 0xff, 0x78, 0x95, 0x67,
  0xb1, 0x74, 0x5b, 0x60, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
 };
 byte_dump((CONST BYTE *) &pbSrc, 288);
}
Now we can pass this to CPImportKey and calculate the public (which is always possible from the private key). But first I need to figure out how to do so using the wincrypt API. Once we've done that we can do the following things.
  1. Create a server component that is using the public key to decrypt the packages passed to it.
  2. Dump what is returned by CryptGenRandom
  3. Find out what CryptCreateHash does and especially why it is used here
  4. Dump what is encrypted by CryptEncrypt
  5. Hack wine to use a faked public key that is imported during the second CPImportKey (as there is no way to calculate the private key for this)
There are much more things that will need to be done, but again we solved another mystery. Oh, and here is the dump of the import of the public key:
0x06 (6)
0x02 (2)
0x00 (0)
0x00 (0)
0x00 (0)
0xa4 (164)
0x00 (0)
0x00 (0)
0x52 (82)
0x53 (83)
0x41 (65)
0x31 (49)
0x00 (0)
0x04 (4)
0x00 (0)
0x00 (0)
0x01 (1)
0x00 (0)
0x01 (1)
0x00 (0)
0x4d (77)
0x03 (3)
0x8d (141)
0xd7 (215)
0xd8 (216)
0xa8 (168)
0x85 (133)
0x97 (151)
0x2d (45)
0x04 (4)
0xde (222)
0x57 (87)
0xe2 (226)
0x77 (119)
0xc4 (196)
0xbf (191)
0xc6 (198)
0x13 (19)
0x15 (21)
0x0e (14)
0x26 (38)
0x72 (114)
0x99 (153)
0x11 (17)
0xa1 (161)
0xcd (205)
0xf3 (243)
0x8f (143)
0xd5 (213)
0x5d (93)
0x63 (99)
0x4c (76)
0xf3 (243)
0x75 (117)
0xb7 (183)
0x62 (98)
0x9d (157)
0xee (238)
0xe8 (232)
0xd5 (213)
0xc5 (197)
0xa7 (167)
0x0e (14)
0x3c (60)
0x9c (156)
0x51 (81)
0xd4 (212)
0x3f (63)
0x00 (0)
0x45 (69)
0xb2 (178)
0xed (237)
0x19 (25)
0xb1 (177)
0xca (202)
0xda (218)
0x84 (132)
0x1c (28)
0x6b (107)
0x35 (53)
0x41 (65)
0x17 (23)
0x46 (70)
0xd0 (208)
0x56 (86)
0xb9 (185)
0x4c (76)
0x06 (6)
0xcd (205)
0x8d (141)
0xc5 (197)
0x50 (80)
0x39 (57)
0x36 (54)
0x4e (78)
0xe3 (227)
0x9b (155)
0x5e (94)
0x3c (60)
0x44 (68)
0xe8 (232)
0x04 (4)
0x0e (14)
0x90 (144)
0xc4 (196)
0x89 (137)
0xfc (252)
0x24 (36)
0xe9 (233)
0xc4 (196)
0xff (255)
0xc6 (198)
0x97 (151)
0xfd (253)
0xd6 (214)
0x5d (93)
0x2b (43)
0x67 (103)
0x5c (92)
0xa2 (162)
0x7f (127)
0x8d (141)
0x2a (42)
0xb9 (185)
0x5a (90)
0x52 (82)
0x3c (60)
0xec (236)
0x0d (13)
0xc1 (193)
0x55 (85)
0xc8 (200)
0xc1 (193)
0xb0 (176)
0x07 (7)
0x3a (58)
0xdc (220)
0x03 (3)
0x9c (156)
0x38 (56)
0x34 (52)
0x08 (8)
0x0e (14)
0x19 (25)
0x27 (39)
0xcd (205)
0x7b (123)
0xd0 (208)
Oh, and here is the code to dump the private key of the client and the public key of the server.
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>

static void byte_dump(CONST BYTE * pbData, DWORD dwDataLen) {
 DWORD i;
 if (pbData && dwDataLen > 0) {
  for (i = 0; i < dwDataLen; i++) {
   printf("0x%02x (%d)\n", pbData[i], pbData[i]);
  }
 }
}

static void dump_private_key() {
 BLOBHEADER header;
 header.bType = 0x7; /* PRIVATEKEYBLOB */
 header.bVersion = 2; /* Must be 2 */
 header.reserved = 0; /* Must be 0 */
 header.aiKeyAlg = 0xa400;
 byte_dump((CONST BYTE *) &header, sizeof(BLOBHEADER));

 RSAPUBKEY rsaPubKey;
 rsaPubKey.magic = 0x32415352; /* RSAENH_MAGIC_RSA2 */
 rsaPubKey.bitlen = 0x00000200; /* 512 bit long */
 rsaPubKey.pubexp = 0x00000001;
 byte_dump((CONST BYTE *) &rsaPubKey, sizeof(RSAPUBKEY));

 BYTE pbSrc[288] = {
  0xab, 0xef, 0xfa, 0xc6,
  0x7d, 0xe8, 0xde, 0xfb, 0x68, 0x38, 0x09, 0x92,
  0xd9, 0x42, 0x7e, 0x6b, 0x89, 0x9e, 0x21, 0xd7,
  0x52, 0x1c, 0x99, 0x3c, 0x17, 0x48, 0x4e, 0x3a,
  0x44, 0x02, 0xf2, 0xfa, 0x74, 0x57, 0xda, 0xe4,
  0xd3, 0xc0, 0x35, 0x67, 0xfa, 0x6e, 0xdf, 0x78,
  0x4c, 0x75, 0x35, 0x1c, 0xa0, 0x74, 0x49, 0xe3,
  0x20, 0x13, 0x71, 0x35, 0x65, 0xdf, 0x12, 0x20,
  0xf5, 0xf5, 0xf5, 0xc1, 0xed, 0x5c, 0x91, 0x36,
  0x75, 0xb0, 0xa9, 0x9c, 0x04, 0xdb, 0x0c, 0x8c,
  0xbf, 0x99, 0x75, 0x13, 0x7e, 0x87, 0x80, 0x4b,
  0x71, 0x94, 0xb8, 0x00, 0xa0, 0x7d, 0xb7, 0x53,
  0xdd, 0x20, 0x63, 0xee, 0xf7, 0x83, 0x41, 0xfe,
  0x16, 0xa7, 0x6e, 0xdf, 0x21, 0x7d, 0x76, 0xc0,
  0x85, 0xd5, 0x65, 0x7f, 0x00, 0x23, 0x57, 0x45,
  0x52, 0x02, 0x9d, 0xea, 0x69, 0xac, 0x1f, 0xfd,
  0x3f, 0x8c, 0x4a, 0xd0, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x64, 0xd5, 0xaa, 0xb1,
  0xa6, 0x03, 0x18, 0x92, 0x03, 0xaa, 0x31, 0x2e,
  0x48, 0x4b, 0x65, 0x20, 0x99, 0xcd, 0xc6, 0x0c,
  0x15, 0x0c, 0xbf, 0x3e, 0xff, 0x78, 0x95, 0x67,
  0xb1, 0x74, 0x5b, 0x60, 0x01, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00
 };
 byte_dump((CONST BYTE *) &pbSrc, 288);
}

static void dump_public_key() {
 BLOBHEADER header;
 header.bType = 0x6; /* PUBLICKEYBLOB */
 header.bVersion = 2; /* Must be 2 */
 header.reserved = 0; /* Must be 0 */
 header.aiKeyAlg = 0xa400;
 byte_dump((CONST BYTE *) &header, sizeof(BLOBHEADER));

 RSAPUBKEY rsaPubKey;
 rsaPubKey.magic = 0x32415352; /* RSAENH_MAGIC_RSA2 */
 rsaPubKey.bitlen = 0x00000400; /* 512 bit long */
 rsaPubKey.pubexp = 0x00010001;
 byte_dump((CONST BYTE *) &rsaPubKey, sizeof(RSAPUBKEY));

 BYTE pbSrc[128] = {
  0x4d, 0x03, 0x8d, 0xd7, 0xd8, 0xa8, 0x85, 0x97,
  0x2d, 0x04, 0xde, 0x57, 0xe2, 0x77, 0xc4, 0xbf,
  0xc6, 0x13, 0x15, 0x0e, 0x26, 0x72, 0x99, 0x11,
  0xa1, 0xcd, 0xf3, 0x8f, 0xd5, 0x5d, 0x63, 0x4c,
  0xf3, 0x75, 0xb7, 0x62, 0x9d, 0xee, 0xe8, 0xd5,
  0xc5, 0xa7, 0x0e, 0x3c, 0x9c, 0x51, 0xd4, 0x3f,
  0x00, 0x45, 0xb2, 0xed, 0x19, 0xb1, 0xca, 0xda,
  0x84, 0x1c, 0x6b, 0x35, 0x41, 0x17, 0x46, 0xd0,
  0x56, 0xb9, 0x4c, 0x06, 0xcd, 0x8d, 0xc5, 0x50,
  0x39, 0x36, 0x4e, 0xe3, 0x9b, 0x5e, 0x3c, 0x44,
  0xe8, 0x04, 0x0e, 0x90, 0xc4, 0x89, 0xfc, 0x24,
  0xe9, 0xc4, 0xff, 0xc6, 0x97, 0xfd, 0xd6, 0x5d,
  0x2b, 0x67, 0x5c, 0xa2, 0x7f, 0x8d, 0x2a, 0xb9,
  0x5a, 0x52, 0x3c, 0xec, 0x0d, 0xc1, 0x55, 0xc8,
  0xc1, 0xb0, 0x07, 0x3a, 0xdc, 0x03, 0x9c, 0x38,
  0x34, 0x08, 0x0e, 0x19, 0x27, 0xcd, 0x7b, 0xd0
 };
 byte_dump((CONST BYTE *) &pbSrc, 128);
}

int main() {
 dump_private_key();
 printf("\n");
 dump_public_key();
}
So it looks to me that:
  1. The client has it's own private and public RSA-keyset
  2. The server has a different RSA-one, too
  3. These keys are used to exchange the symetric RC4 key

It is really getting complicated.