To help you guyz reversing the game protocol, I will try to explain few things about how the packet are encrypted and decrypted.
What you have to know is that the binary use this C++ library : Crypto++ Library 5.6.2 - a Free C++ Class Library of Cryptographic Schemes.
We saw in some other threads that a lot of zlib_buffer are sent during the first packet sent to a TESO server.
I will summarize how packet works (after all the XML shit) :
When receiving a packet from the client on your own server , you must read 4 bytes to know the exact size (stored in Big-Endian) of the packet.
So the first DWORD is the full size of the packet (minus the 4 bytes that you just read).
Then start the header of sent packet by client (different from the server).
The first WORD is maybe the version, a second WORD is maybe the streamID, then a DWORD for the size of the data of the packet.
All those values again are in Big-Endian.
Let's take the following example :
Code:
[+] Send (127.0.0.1 : 4242) : 834 (00000342) bytes
[0000] 00 00 03 3E 00 01 00 01 00 00 03 36 2B 10 00 24 ........ ...6....
Here the full size of the packet is 0x0000033E (830) bytes.
The version is 0x0001 (1) and streamID is 0x0001 (1), and data size equal to 0x00000336 (822) bytes.
The first WORD inside data buffer equal to 0x2B10, it's the opcode of the packet (in Big-Endian again).
This opcode is responsible for beeing able to generate the key exchange, to able the client to discuss with the server in a encrypt way.
For this opcode here is the way all the data are "serialized" :
Code:
// Usefull definition
typedef struct
{
WORD size;
BYTE data[size + 1];
} TESO_Buffer;
// Usefull definition
typedef struct
{
DWORD uncomp_size;
DWORD comp_size;
BYTE data[comp_size];
} zlib_buffer;
// 0x2B10 packet structure
typedef struct
{
TESO_Buffer uuid;
TESO_Buffer version;
zlib_buffer Key3_public;
zlib_buffer Key5_public;
zlib_buffer Key2_public;
DWORD Region_protocol; // (Not sure)
zlib_buffer Key1_public;
DWORD unk_dword_00;
DWORD Lobby_protocol; // (Not sure)
DWORD unk_dword_01;
zlib_buffer Key4_public;
BYTE unk_byte_00;
TESO_Buffer Langage;
} TESO_FIRST;
As you can figure out, the client send you 5 zlib buffers, to make it simple, it's 5 public key generated by the client.
The public Key1 is generated by the function at RVA : 0x531f70 ( don't forget to add BaseAdress ).
P, Q, G (RVA = 0xd5ac3c) :
Code:
.rdata:0115AC3C a0xf518aa8781a8 db '0xF518AA8781A8DF278ABA4E7D64B7CB9D49462353',0
.rdata:0115AC67 align 4
.rdata:0115AC68 a0xa4d1cbd5c3fd db '0xA4D1CBD5C3FD34126765A442EFB99905F8104DD258AC507FD6406CFF14266D3'
.rdata:0115AC68
.rdata:0115AC68 db '1266FEA1E5C41564B777E690F5504F213160217B4B01B886A5E91547F9E2749F4'
.rdata:0115AC68 db 'D7FBD7D3B9A92EE1909D0D2263F80A76A6A24C087A091F531DBF0A0169B6A28AD'
.rdata:0115AC68 db '662A4D18E73AFA32D779D5918D08BC8858F4DCEF97C2A24855E6EEB22B3B2E5',0
.rdata:0115AD6B align 10h
.rdata:0115AD70 a0xb10b8f96a080 db '0xB10B8F96A080E01DDE92DE5EAE5D54EC52C99FBCFB06A3C69A6A9DCA52D23B6'
.rdata:0115AD70
.rdata:0115AD70 db '16073E28675A23D189838EF1E2EE652C013ECB4AEA906112324975C3CD49B83BF'
.rdata:0115AD70 db 'ACCBDD7D90C4BD7098488E9C219A73724EFFD6FAE5644738FAA31A4FF55BCCC0A'
.rdata:0115AD70 db '151AF5F0DC8B4BD45BF37DF365C1A65E68CFDA76D4DA708DF1FB2BC2E4A4371',0
You can figure out that thoses values used are from Diffie-Hellman (RFC 5114 - Additional Diffie-Hellman Groups for Use with IETF Standards).
So before the client try to connect, it will generate 5 Key pair (private and public), and store them in a class (or struct) defined like that :
Code:
00000000 CKey struc ; (sizeof=0x10)
00000000 Buffer_pubic dd ?
00000004 Size_public dd ?
00000008 Buffer_priv dd ?
0000000C Size_priv dd ?
00000010 CKey ends
Generally the size for public key is 0x80 and private is 0x14.
You can find them at those offset in the binary ( RVA = 0xf6b0c8 ) :
Code:
.data:0136B0C8 Ckey1 CKey <?>
.data:0136B0C8
.data:0136B0D8 Ckey2 CKey <?>
.data:0136B0D8
.data:0136B0E8 CKey3 CKey <?>
.data:0136B0E8
.data:0136B0F8 Ckey4 CKey <?>
.data:0136B0F8
.data:0136B108 Ckey5 CKey <?>
Now let's take an example to summarize all this stuff, I ask the client to connect to an desired ip adress and port, and it will send me the following packet :
Code:
[+] len(buf) = 4 (00000004)
0000 00 00 03 1e ....
[+] len(buf) = 798 (0000031E)
0000 00 01 00 01 00 00 03 16 2b 10 00 04 31 32 33 34 ........+...1234
0010 00 00 15 65 73 6f 2e 6c 69 76 65 2e 31 2e 30 2e ...eso.live.1.0.
0020 30 2e 37 30 39 37 31 37 00 00 00 00 80 00 00 00 0.709717........
0030 8b 78 9c 01 80 00 7f ff 67 41 0d 41 c4 f2 c6 99 .x......gA.A....
0040 76 2a ef 62 d2 37 ed 81 1a f6 ff b6 02 58 ea 37 v*.b.7.......X.7
0050 c8 1b 09 28 8f 87 ba 8c 9c 0d ee e2 50 93 fb 39 ...(........P..9
0060 3b bf 50 db 73 a3 c9 31 5f 0e 1c 98 35 a6 cd 53 ;.P.s..1_...5..S
0070 17 a7 84 40 07 75 4e 06 ed 9c 21 b8 f4 f3 1c 41 [email protected]...!....A
0080 5e 47 18 76 62 91 e0 da ad cd 6a 41 14 17 e8 da ^G.vb.....jA....
0090 5f e9 90 8b a6 9f 3c 3e 9d 6c fe 65 13 fa 62 3c _.....<>.l.e..b<
00a0 9e 02 ae 91 1b c9 fd 18 22 69 f9 bb db fc 52 77 ........"i....Rw
00b0 bc aa 82 ef 86 16 71 05 f6 b1 3f c8 00 00 00 80 ......q...?.....
00c0 00 00 00 8b 78 9c 01 80 00 7f ff 5b 0f 96 c3 8c ....x......[....
00d0 2a 10 71 18 cf a8 31 9a f3 f5 81 f3 35 3f d7 14 *.q...1.....5?..
00e0 c1 fc 42 22 cc 9f fb 42 70 00 29 d5 93 69 73 08 ..B"...Bp.)..is.
00f0 34 9d 3e be f1 cc 5d 95 94 62 78 7d 57 0a 08 79 4.>...]..bx}W..y
0100 f2 f1 b4 f5 45 e2 aa 0a 8c ef 3e fb 64 1f cc e2 ....E.....>.d...
0110 b4 c5 8b 2b d8 4b 22 00 42 dc a6 ff 7c 4c a6 c9 ...+.K".B...|L..
0120 36 e1 73 15 7b 43 2e 2a 59 60 04 6d 35 a0 ab 46 6.s.{C.*Y`.m5..F
0130 23 68 66 52 d8 59 d0 44 51 7a e0 f7 73 fc 82 3c #hfR.Y.DQz..s..<
0140 a6 01 18 bd bb b0 16 85 6d f4 2f f6 a3 3e cf 00 ........m./..>..
0150 00 00 80 00 00 00 8b 78 9c 01 80 00 7f ff 05 9d .......x........
0160 eb e0 65 24 ba f6 d9 03 e1 2c 75 bf d4 d5 3c 09 ..e$.....,u...<.
0170 ca 76 63 05 2a 24 8b 79 3a 4e 1d cf c9 0e 51 4b .vc.*$.y:N....QK
0180 08 df ad fd 41 42 d7 5f e7 c6 a0 39 13 05 03 29 ....AB._...9...)
0190 42 57 fb a6 1d ca 5e 4f f9 4e 20 1d 05 43 ad 2f BW....^O.N ..C./
01a0 86 36 d6 06 b9 12 4e fb 42 52 4d ca 8b cb 5a b1 .6....N.BRM...Z.
01b0 bb 21 03 03 e2 01 c1 2a e3 90 a1 0a 3a 63 0f bc .!.....*....:c..
01c0 b7 58 0f 5d d2 5c d7 12 b6 c4 7a f0 3d c6 7f 58 .X.]......z.=..X
01d0 fc af ee 0f 77 55 65 27 65 1f 8f 34 00 08 9e 4f ....wUe'e..4...O
01e0 38 aa 01 a1 0e d8 00 00 00 80 00 00 00 8b 78 9c 8.............x.
01f0 01 80 00 7f ff 4f df 0a e7 9b a0 0c 73 a7 48 3f .....O......s.H?
0200 27 6c 36 88 c3 0d 11 c1 fe 98 c4 c2 45 f9 3b 4d 'l6.........E.;M
0210 de bd e2 ca 86 14 76 24 86 02 f0 01 2d 90 f0 67 ......v$....-..g
0220 e0 2a 64 dc bd 54 30 20 91 58 94 e3 aa 5c ad bf .*d..T0 .X......
0230 c5 4c 0d c6 68 a7 00 4c 3d d7 75 ad 2d e8 d3 cb .L..h..L=.u.-...
0240 e2 af 5e 3e 5e de 91 1c 60 40 d4 19 5b a1 65 4a ..^>^...`@..[.eJ
0250 29 fb 23 3e c9 49 4f e3 97 75 fe 4b b5 41 6c 04 ).#>.IO..u.K.Al.
0260 a3 81 a3 9f ac b9 15 e9 5f 33 30 5d 43 57 8e b1 ........_30]CW..
0270 a8 6c 1c ab ba db df 3e b2 0e 95 c7 24 05 35 ea .l.....>....$.5.
0280 23 00 00 00 00 00 00 00 80 00 00 00 8b 78 9c 01 #............x..
0290 80 00 7f ff ad 73 98 aa c2 83 6e 19 1d 2d 37 c8 .....s....n..-7.
02a0 2b 8f 63 48 96 45 3d 62 52 19 cd 82 62 12 47 c4 +.cH.E=bR...b.G.
02b0 c4 e2 fb 8e a2 6e 75 c8 ad b1 87 22 db e5 20 56 .....nu....".. V
02c0 28 f0 4b 0c 08 04 b3 5f 87 76 26 92 87 6d 97 32 (.K...._.v&..m.2
02d0 b9 f8 95 02 9a 32 86 c7 56 1a 15 52 ed 1f a5 53 .....2..V..R...S
02e0 c2 06 bf 75 7e b2 de 7e b0 99 ba 9b 95 9c fe c2 ...u~..~........
02f0 c9 86 bf ed 80 08 65 18 68 83 3e 32 e5 a6 94 b7 ......e.h.>2....
0300 45 05 5f a0 1a 2d a2 a5 fd 8a 4d 97 0a 39 c1 6e E._..-....M..9.n
0310 2a 91 ba a1 3e 00 3d 96 04 00 02 65 6e 00 *...>.=....en.
If you parse this like explained before you should have this :
Code:
[+] NS_version = 0001
[+] NS_streamID = 0001
[+] size = 00000316
[+] opcode = 2B10
[+] login = 1234
[+] version = eso.live.1.0.0.709717
[+] unk_zlib_00 (CKEY3 PUBLIC): 67410d41c4f2c699762aef62d237ed811af6ffb60258ea37c81b09288f87ba8c9c0deee25093fb393bbf50db73a3c9315f0e1c9835a6cd5317a7844007754e06ed9c21b8f4f31c415e4718766291e0da
adcd6a411417e8da5fe9908ba69f3c3e9d6cfe6513fa623c9e02ae911bc9fd182269f9bbdbfc5277bcaa82ef86167105
[+] unk_zlib_01 (CKEY5 PUBLIC): 5b0f96c38c2a107118cfa8319af3f581f3353fd714c1fc4222cc9ffb42700029d593697308349d3ebef1cc5d959462787d570a0879f2f1b4f545e2aa0a8cef3efb641fcce2b4c58b2bd84b220042dca6
ff7c4ca6c936e173157b432e2a5960046d35a0ab4623686652d859d044517ae0f773fc823ca60118bdbbb016856df42f
[+] unk_zlib_02 (CKEY2 PUBLIC): 059debe06524baf6d903e12c75bfd4d53c09ca7663052a248b793a4e1dcfc90e514b08dfadfd4142d75fe7c6a039130503294257fba61dca5e4ff94e201d0543ad2f8636d606b9124efb42524dca8bcb
5ab1bb210303e201c12ae390a10a3a630fbcb7580f5dd25cd712b6c47af03dc67f58fcafee0f77556527651f8f340008
[+] unk_dword_00 = 01A10ED8 (Region Protocol ?)
[+] unk_zlib_03 (CKEY1 PUBLIC): 4fdf0ae79ba00c73a7483f276c3688c30d11c1fe98c4c245f93b4ddebde2ca861476248602f0012d90f067e02a64dcbd543020915894e3aa5cadbfc54c0dc668a7004c3dd775ad2de8d3cbe2af5e3e5e
de911c6040d4195ba1654a29fb233ec9494fe39775fe4bb5416c04a381a39facb915e95f33305d43578eb1a86c1cabba
[+] unk_dword_01 = 0E95C724
[+] unk_dword_02 = 0535EA23 (Lobby Protocol ?)
[+] unk_dword_03 = 00000000
[+] unk_zlib_04 (CKEY4 PUBLIC): ad7398aac2836e191d2d37c82b8f634896453d625219cd82621247c4c4e2fb8ea26e75c8adb18722dbe5205628f04b0c0804b35f87762692876d9732b9f895029a3286c7561a1552ed1fa553c206bf75
7eb2de7eb099ba9b959cfec2c986bfed8008651868833e32e5a694b745055fa01a2da2a5fd8a4d970a39c16e2a91baa1
[+] unk_byte_00 = 04
[+] langage = en
If we look with Ollydbg the 5 Key (Class CKey) I explained before, we can see that :
You can see that :
- the first zlib_buffer is the public key 3.
- the second zlib_buffer is the public key 5.
- the third zlib_buffer is the public key 2.
- the fourth zlib_buffer is the public key 1.
- the fifth zlib_buffer is the public key 4.
They use the schem explained here : Diffie-Hellman - Crypto++ Wiki
The server have to respond with the following packet :
Code:
// 0x2B10 packet answer
typedef struct
{
WORD opcode; // here equal to 0x2B08
BYTE unk_byte_00;
zlib_buffer Key1_public_server;
zlib_buffer Key3_public_server;
zlib_buffer IV; // length of IV must be equal to 0x10
zlib_buffer Key4_public_server;
zlib_buffer Key2_public_server;
} TESO_0x2B08;
So your server have to generate 4 key pair and send the public one to the client, and a IV for setuping AES.
Once the client receive this answer, it will make the following agreement :
Code:
# Agree 1
CryptoPP::DH2::Agree(AgreedValue = SharedSecret1, StaticSecretKey = CKey_1->Private_buf, ephemeralSecretKey = CKey_2->Private_buf, staticOtherPublicKey = StaticOtherPublicKey, ephemeralOtherPublicKey = Key3_public_server)
# Agree 2
CryptoPP::DH2::Agree(AgreedValue = SharedSecret2, StaticSecretKey = CKey_1->Private_buf, ephemeralSecretKey = CKey_3->Private_buf, staticOtherPublicKey = StaticOtherPublicKey, ephemeralOtherPublicKey = Key1_public_server)
# Agree 3
CryptoPP::DH2::Agree(AgreedValue = SharedSecret3, StaticSecretKey = CKey_1->Private_buf, ephemeralSecretKey = CKey_4->Private_buf, staticOtherPublicKey = StaticOtherPublicKey, ephemeralOtherPublicKey = Key4_public_server)
# Agree 4
CryptoPP::DH2::Agree(AgreedValue = SharedSecret4, StaticSecretKey = CKey_1->Private_buf, ephemeralSecretKey = CKey_5->Private_buf, staticOtherPublicKey = StaticOtherPublicKey, ephemeralOtherPublicKey = Key2_public_server)
StaticOtherPublicKey buffer is discussed further in the article.
This Agree function can be found here (RVA = 0x531bc0) :
Code:
.text:00931BB2 push 1
.text:00931BB4 push esi
.text:00931BB5 push ebx
.text:00931BB6 push eax
.text:00931BB7 push ecx
.text:00931BB8 push edi
.text:00931BB9 lea ecx, [ebp+var_2C]
.text:00931BBC mov byte ptr [ebp+var_4], 8
.text:00931BC0 call ?Agree@DH2@CryptoPP@@UBE_NPAEPBE111_N@Z ; CryptoPP::DH2::Agree(uchar *,uchar const *,uchar const *,uchar const *,uchar const *,bool)
There is 4 call to this agree to generate 4 SharedSecret of length 256 bytes.
The sha256 of the SharedSecret1 will be used at set key for CTR_AES, and use the IV sent by the server.
The SharedSecret1 is here to generate a CTR_Mode<AES>::Encryption, for sending encrypted packet to the server.
Code:
CTR_Mode<AES>::Encryption->SetKeyWithIV(SHA256(SharedSecret1), LENGTH_SHA256, IV);
The SharedSecret2 is here to generate a CTR_Mode<AES>:: Decryption, for received encrypted packet from the server.
Code:
CTR_Mode<AES>:: Decryption->SetKeyWithIV(SHA256(SharedSecret2), LENGTH_SHA256, IV);
The weird thing is the SharedSecret3 and SharedSecret4 are apparently not used (maybe someone knows more about it!).
The problem here is that the StaticOtherPublicKey used by the method Agree from CryptoPP:: DH2 is hardcoded inside the client binary ( RVA = 0xef9fc0 ) :
Code:
.data:012F9FC0 StaticOtherPublicKey db 67h,0ADh,27h,36h,92h,45h,0AAh,0A7h,2Eh,0E3h,43h,4Fh
.data:012F9FC0 db 0A3h,0C2h,52h,0Ch,33h,4Ch,3Eh,99h,0Fh,0FAh,9Dh,42h
.data:012F9FC0 db 73h,6,6Ch,0DFh,49h,13h,61h,0F4h,99h,5Ch,34h,31h,0FFh
.data:012F9FC0 db 0F2h,1Dh,2Eh,99h,0E4h,0A5h,38h,24h,1,6Bh,46h,0C8h,0B5h
.data:012F9FC0 db 21h,0D7h,4,49h,0D0h,2,0CEh,0C6h,3Dh,4Fh,0EBh,63h,0A7h
.data:012F9FC0 db 9Eh,8Eh,6Ch,9Bh,67h,0F4h,82h,0E4h,77h,38h,7Fh,0BDh
.data:012F9FC0 db 0C2h,0A0h,0C7h,25h,22h,3Dh,3Bh,23h,78h,38h,93h,0AEh
.data:012F9FC0 db 0A4h,6Eh,0DFh,51h,0E2h,6Eh,0E0h,0D0h,3Bh,57h,6,76h
.data:012F9FC0 db 0F9h,7Bh,76h,23h,0E9h,0EDh,6Ah,9Fh,60h,0DFh,2Eh,28h
.data:012F9FC0 db 5Fh,99h,0B1h,9Eh,7Fh,0DEh,77h,6Ch,0C0h,0E2h,6Dh,20h
.data:012F9FC0 db 37h,97h,0A8h,0D1h,44h
So you can't generate the ephemeralOtherPublicKey answer, because you don't know the PrivateKey of the server, and you won't be able to compute the same SharedSecret that the client do.
So you have two possibility for making your own server or sniffer !
- Patch the public key (StaticOtherPublicKey) hardcoded in the binary.
- Hook the method SetKeyWithIV from CTR_Mode<AES> class, and get the key (aka sha256 of the SharedSecret).
If you do correctly what I said the client will answer you :
Code:
[+] Send (127.0.0.1 : 4242) : 15 (0000000F) bytes
[0000] 00 00 00 0B 00 01 00 01 00 00 00 03 2B 0A 01 ........ .......
Parsed :
Code:
[+] NS_version = 0001
[+] NS_streamID = 0001
[+] size = 00000003
[+] opcode = 2B0A
[+] unk_byte_00 = 01
It tells the server that the client now accept encrypted packet.
Exemple of encrypted packet :
Code:
[+] Received (127.0.0.1 : 4242) : 4 (00000004) bytes
[0000] 00 00 00 2F ....
[+] Received (127.0.0.1 : 4242) : 47 (0000002F) bytes
[0000] 00 8A EA 6A CA 8E D1 43 90 32 4C 6B 6D 50 7B C5 .ŠêjÊŽÑC .2LkmP.Å
[0010] 14 AA 10 58 9D 4B FB 3F 8A CE 98 E0 A5 30 E8 71 .ª.X.Kû. ŠÎ.à.0èq
[0020] B9 C0 29 FD 8D F5 F2 09 24 A4 C5 23 1D 3E B2 ¹À.ý.õò. ..Å...²
FULL_SIZE = 0x0000002F
The packet of DATA start with a NULL byte, you have to ignore to be able to decrypt correctly the packet.
You must use the second instance of AES, the one with the setkey with sha256(SharedSecret2), method ProcessData from this class is your friend.
Code:
CTR_Mode<AES>::Decryption->ProcessData(output, buf + 1, len_buf - 1);
Have fun !