Developer Notes
Basically a dumping ground for random notes that may be useful.
Packet IDs
Code:
#define POE_C2S_HEARTBEAT 0x0d
#define POE_S2C_LOGIN 0x02
#define POE_S2C_CHARACTERS 0x04
#define POE_S2C_TRANSITION 0x05
#define POE_S2C_HEARTBEAT 0x22
ws2_32!send Hook Notes
Code analysis notes:
Code:
POE v3.1.1e Packet Sending Function:
------------------------------------
base @ 00007ff7`2faa0000
00007ff7`2fff11df 4881ecc0040000 sub rsp,4C0h
00007ff7`2fff11e6 48c745a0feffffff mov qword ptr [rbp-60h],0FFFFFFFFFFFFFFFEh
00007ff7`2fff11ee 48899c24f8040000 mov qword ptr [rsp+4F8h],rbx
00007ff7`2fff11f6 488bd9 mov rbx,rcx
00007ff7`2fff11f9 33f6 xor esi,esi
00007ff7`2fff11fb 89b5e0030000 mov dword ptr [rbp+3E0h],esi
00007ff7`2fff1201 488b9188010000 mov rdx,qword ptr [rcx+188h]
00007ff7`2fff1208 4c8b8180010000 mov r8,qword ptr [rcx+180h]
00007ff7`2fff120f 4c2bc2 sub r8,rdx
00007ff7`2fff1212 488b89f0000000 mov rcx,qword ptr [rcx+0F0h]
00007ff7`2fff1219 4885c9 test rcx,rcx
00007ff7`2fff121c 7413 je PathOfExile_x64+0x551231 (00007ff7`2fff1231)
00007ff7`2fff121e 4d85c0 test r8,r8
00007ff7`2fff1221 740e je PathOfExile_x64+0x551231 (00007ff7`2fff1231)
00007ff7`2fff1223 4c8b09 mov r9,qword ptr [rcx]
00007ff7`2fff1226 480393a8010000 add rdx,qword ptr [rbx+1A8h]
00007ff7`2fff122d 41ff5108 call qword ptr [r9+8] <- Encryption function
00007ff7`2fff1231 488b8380010000 mov rax,qword ptr [rbx+180h]
00007ff7`2fff1238 48898388010000 mov qword ptr [rbx+188h],rax
00007ff7`2fff123f 488bfe mov rdi,rsi
00007ff7`2fff1242 4885c0 test rax,rax
00007ff7`2fff1245 0f84ad040000 je PathOfExile_x64+0x5516f8 (00007ff7`2fff16f8)
00007ff7`2fff124b 0f1f440000 nop dword ptr [rax+rax]
00007ff7`2fff1250 488b93a8010000 mov rdx,qword ptr [rbx+1A8h]
00007ff7`2fff1257 448b8380010000 mov r8d,dword ptr [rbx+180h]
00007ff7`2fff125e 442bc7 sub r8d,edi
00007ff7`2fff1261 4803d7 add rdx,rdi
00007ff7`2fff1264 4533c9 xor r9d,r9d
00007ff7`2fff1267 488b0b mov rcx,qword ptr [rbx]
00007ff7`2fff126a ff1518099000 call qword ptr [PathOfExile_x64+0xe51b88 (00007ff7`308f1b88)] <- Call to ws2_32!send
00007ff7`2fff1270 83f8ff cmp eax,0FFFFFFFFh
00007ff7`2fff1273 741d je PathOfExile_x64+0x551292 (00007ff7`2fff1292)
00007ff7`2fff1275 4863c8 movsxd rcx,eax
POE v3.1.1e Packet Encryption Function
--------------------------------------
rdx = buffer, r8 = length
base @ 00007ff7`2faa0000
00007ff7`302f9b70 488b4110 mov rax, qword ptr[rcx + 10h] ds:0000022b`12f9fab0=00007ff730b03980
00007ff7`302f9b74 4883c110 add rcx, 10h <- Hook location
00007ff7`302f9b78 4d8bc8 mov r9, r8
00007ff7`302f9b7b 4c8bc2 mov r8, rdx
00007ff7`302f9b7e 48ff6038 jmp qword ptr[rax + 38h]
Signature: 48 8b 41 10 48 83 c1 10 4d 8b c8
WinDbg script exmaples:
Code:
In-debugger send hook:
bp 00007ff7`302f9b74 "db @rdx L8; r r8; g"
ws2_32!recv Hook Notes
The recv function only seems to be called during instance transition, likely with connection transition details although I haven't fully reversed it. These packets do not appear to be encrypted.
Code analysis notes:
Code:
POE v3.1.1e Packet Recv Function
--------------------------------
r9 = buffer, rax = length
base @ 00007ff7`2faa0000
00007ff7`2fff0d09 4889b42490000000 mov qword ptr [rsp+90h],rsi
00007ff7`2fff0d11 488bb3c8010000 mov rsi,qword ptr [rbx+1C8h]
00007ff7`2fff0d18 482bb3c0010000 sub rsi,qword ptr [rbx+1C0h]
00007ff7`2fff0d1f 482bf2 sub rsi,rdx
00007ff7`2fff0d22 48897c2468 mov qword ptr [rsp+68h],rdi
00007ff7`2fff0d27 488b93c0010000 mov rdx,qword ptr [rbx+1C0h]
00007ff7`2fff0d2e 483bce cmp rcx,rsi
00007ff7`2fff0d31 7716 ja PathOfExile_x64+0x550d49 (00007ff7`2fff0d49)
00007ff7`2fff0d33 448bc1 mov r8d,ecx
00007ff7`2fff0d36 4903d6 add rdx,r14
00007ff7`2fff0d39 488b0b mov rcx,qword ptr [rbx]
00007ff7`2fff0d3c 4533c9 xor r9d,r9d
00007ff7`2fff0d3f ff15630e9000 call qword ptr [PathOfExile_x64+0xe51ba8 (00007ff7`308f1ba8)] <- Call to ws2_32!recv
00007ff7`2fff0d45 8bf8 mov edi,eax <- Hook location
00007ff7`2fff0d47 eb78 jmp PathOfExile_x64+0x550dc1 (00007ff7`2fff0dc1)
00007ff7`2fff0d49 4a8d0432 lea rax,[rdx+r14]
00007ff7`2fff0d4d 4889542458 mov qword ptr [rsp+58h],rdx
00007ff7`2fff0d52 4889442448 mov qword ptr [rsp+48h],rax
00007ff7`2fff0d57 4c8d8c2480000000 lea r9,[rsp+80h]
00007ff7`2fff0d5f 33c0 xor eax,eax
00007ff7`2fff0d61 89742440 mov dword ptr [rsp+40h],esi
00007ff7`2fff0d65 4889442430 mov qword ptr [rsp+30h],rax
00007ff7`2fff0d6a 488d542440 lea rdx,[rsp+40h]
00007ff7`2fff0d6f 482bce sub rcx,rsi
00007ff7`2fff0d72 4889442428 mov qword ptr [rsp+28h],rax
00007ff7`2fff0d77 89842488000000 mov dword ptr [rsp+88h],eax
00007ff7`2fff0d7e 41b802000000 mov r8d,2
Signature: 8b f8 eb 78 4a 8d 04 32
WinDbg script examples:
Code:
bp 00007ff7`2fff122d "db @r9 L@rax; g"
ws2_32!WSARecv Hook Notes
This is the juicy one, where most of the interesting game traffic is received. It's also unfortunately the most complicated, because it allows for scatter-gather data transfer over multiple buffers. I've made some notes in the code to make it a bit more clear. It's worth looking up how WSARecv works online, too.
Code analysis notes:
Code:
POE v3.1.1e Packet WSARecv Function
-----------------------------------
base @ 00007ff7`2faa0000
00007ff7`2fff0e01 488b93c0010000 mov rdx,qword ptr [rbx+1C0h]
00007ff7`2fff0e08 4c8b09 mov r9,qword ptr [rcx]
00007ff7`2fff0e0b 4903d6 add rdx,r14
00007ff7`2fff0e0e 8bc7 mov eax,edi
00007ff7`2fff0e10 483bc6 cmp rax,rsi
00007ff7`2fff0e13 7709 ja PathOfExile_x64+0x550e1e (00007ff7`2fff0e1e)
00007ff7`2fff0e15 4c63c7 movsxd r8,edi
00007ff7`2fff0e18 41ff5110 call qword ptr [r9+10h]
00007ff7`2fff0e1c eb21 jmp PathOfExile_x64+0x550e3f (00007ff7`2fff0e3f)
00007ff7`2fff0e1e 4c8bc6 mov r8,rsi
00007ff7`2fff0e21 41ff5110 call qword ptr [r9+10h] <- Decryption function
00007ff7`2fff0e25 488b8bf0000000 mov rcx,qword ptr [rbx+0F0h]
00007ff7`2fff0e2c 488b93c0010000 mov rdx,qword ptr [rbx+1C0h]
00007ff7`2fff0e33 4c63c7 movsxd r8,edi
00007ff7`2fff0e36 4c2bc6 sub r8,rsi
00007ff7`2fff0e39 488b01 mov rax,qword ptr [rcx]
00007ff7`2fff0e3c ff5010 call qword ptr [rax+10h] <- Decryption function
00007ff7`2fff0e3f 4863c7 movsxd rax,edi <- Hook location
00007ff7`2fff0e42 48018398010000 add qword ptr [rbx+198h],rax
00007ff7`2fff0e49 488b83e8000000 mov rax,qword ptr [rbx+0E8h]
00007ff7`2fff0e50 017804 add dword ptr [rax+4],edi
00007ff7`2fff0e53 488b83c8010000 mov rax,qword ptr [rbx+1C8h]
00007ff7`2fff0e5a 482b83c0010000 sub rax,qword ptr [rbx+1C0h]
00007ff7`2fff0e61 48398398010000 cmp qword ptr [rbx+198h],rax
00007ff7`2fff0e68 7519 jne PathOfExile_x64+0x550e83 (00007ff7`2fff0e83)
00007ff7`2fff0e6a 488b93c8010000 mov rdx,qword ptr [rbx+1C8h]
00007ff7`2fff0e71 488bcb mov rcx,rbx
00007ff7`2fff0e74 482b93c0010000 sub rdx,qword ptr [rbx+1C0h]
00007ff7`2fff0e7b 4803d2 add rdx,rdx
00007ff7`2fff0e7e e88d0a0000 call PathOfExile_x64+0x551910 (00007ff7`2fff1910)
00007ff7`2fff0e83 488b7c2468 mov rdi,qword ptr [rsp+68h]
00007ff7`2fff0e88 488bb42490000000 mov rsi,qword ptr [rsp+90h]
Notes:
- At hook location for a 2-buffer setup, example stack looks as follows:
000000d3`5a8feee0 00000233`00000040 00000000`00000000
000000d3`5a8feef0 00000000`00000742 00000000`00000009
000000d3`5a8fef00 000000d3`5a8fef68 00000000`00000000
000000d3`5a8fef10 00000000`00000000 00000000`00000001
000000d3`5a8fef20 00000000`00000742 00000233`ab004f7e <- Buffer 1 size, buffer 1 pointer
000000d3`5a8fef30 00000000`000000be 00000233`ab004ec0 <- Buffer 2 size, buffer 2 pointer
000000d3`5a8fef40 ffffffff`ffffffff 00000233`406c5bf0
000000d3`5a8fef50 00000000`0e8f55af 00007ff7`3031fea4
- At hook location, RDI contains actual bytes received.
- Can reach first buffer size at RSP+0x40, buffer location at RSP+0x48.
- Can keep reading QWORDS until 0xffffffff`ffffffff.
- If RDI is greater than the size of buffer 1, must also read from buffer 2, etc.
Signature: 48 63 c7 48 01 83 98 01 00 00
WinDbg script examples:
Code:
Can be used at return address of ws2_32!WSARecv to get details about buffer count and first buffer.
r @r9; dd @r9; r @rdx; r @r8; dq @rdx; db poi(@rdx+0x8); db poi(@rdx+0x18)