ZLYNX

Blog

3 min read

Hook packet game client — dump client rồi dịch packet

Share:

Dump client → hiểu logic → dịch packet

Hai nguồn bổ sung cho nhau:

1[Dump SWF/AIR client] → đọc class encode/decode, tên field, thứ tự write/read 2[Hook send/recv runtime] → xác nhận byte thực tế trên dây khớp với dump

Dump client (JPEXS, FFDec, decompiler ActionScript…) cho bạn logic: packet nào gọi hàm nào, body gồm những field gì, kiểu int / Boolean / UTF ra sao.

Hook Frida cho bạn bằng chứng: khi bạn di chuyển trong game, byte nào đổi, opcode nào bắn ra. Đối chiếu hai phía thì mới dịch packet ổn — chỉ dump mà không hook dễ sai offset; chỉ hook hex mà không dump thì đoán mò lâu.


Client nói với server qua socket

1[Game logic] → encode → send() → Server 2[Game logic] ← decode ← recv() ← Server

Frida attach process (ví dụ FlashPlayerApp.exe), hook send / recv / WSASend / WSARecv trong WS2_32.dll — đọc buffer ngay trước khi byte ra mạng hoặc vừa vào process.


Cấu trúc packet (khái niệm)

Sau khi cắt được một gói hoàn chỉnh, layout thường kiểu:

1[magic 2B] [length 2B] [... header cố định ...] [opcode] [clientId ...] [body]

Một số opcode chỉ là envelope. Ví dụ opcode PACKET_CODE_EXAMPLE bọc body — byte đầu body là sub-command (move, turn, wind…). Dump client sẽ chỉ ra class PacketAnalyzer / GameSocket viết sub-command thế nào; hook log giúp verify từng offset.


Example: cắt TCP stream thành packet

TCP là stream — một lần recv có thể dính nửa gói hoặc hai gói. Cần buffer theo chiều send / recv, quét magic header, đọc length, cắt đúng số byte:

1const HEADER = 0xabcd; // magic — tìm từ hex log thực tế 2const HDR_SIZE = 666; 3const PACKET_CODE_EXAMPLE = 42; 4 5const stream = []; // buffer tích lũy 6 7function u16(buf, off) { 8 return ((buf[off] << 8) | buf[off + 1]) & 0xffff; 9} 10 11function i16(buf, off) { 12 let v = u16(buf, off); 13 return v & 0x8000 ? v - 0x10000 : v; 14} 15 16function i32(buf, off) { 17 return ( 18 (buf[off] << 24) | 19 (buf[off + 1] << 16) | 20 (buf[off + 2] << 8) | 21 buf[off + 3] 22 ); 23} 24 25function processBytes(incoming) { 26 for (const b of incoming) stream.push(b & 0xff); 27 28 let off = 0; 29 while (off + HDR_SIZE <= stream.length) { 30 // sync magic 31 while (off + 1 < stream.length && u16(stream, off) !== HEADER) off++; 32 if (off + HDR_SIZE > stream.length) break; 33 34 const len = u16(stream, off + 2); 35 if (len < HDR_SIZE || len > 8192) { 36 off++; 37 continue; 38 } 39 if (off + len > stream.length) break; // chưa đủ byte — chờ recv sau 40 41 const pkt = stream.slice(off, off + len); 42 const code = i16(pkt, 6); 43 44 if (code === PACKET_CODE_EXAMPLE && pkt.length > HDR_SIZE) { 45 const subCmd = pkt[HDR_SIZE]; 46 console.log('PACKET_CODE_EXAMPLE subCmd=' + subCmd); 47 } 48 49 off += len; 50 } 51 stream.splice(0, off); // giữ phần dư cho lần sau 52}

Đoạn trên là khung — magic, HDR_SIZE, offset opcode lấy từ dump + vài lần test in hex. Khi khớp, mở rộng parser body theo sub-command từng bước.


Ví dụ: state đọc từ packet đã parse

Khi hook + parser chạy ổn, log Frida có thể feed ra overlay debug — tọa độ player/NPC, hướng, gió, góc UI so với góc packet:

State game đọc từ packet — tọa độ, gió, góc, entity trên map


Đối chiếu dump ↔ hook

Từ dump clientTừ hook runtime
writeInt(x), writeBoolean(b)offset +4, giá trị đổi khi di chuyển
Tên packet / handler classopcode xuất hiện đúng lúc vào phòng
Sub-command enum MOVE = 1byte đầu body = 1 khi chỉ bấm đi

Làm một hành động trong game → xem một dòng log → mở dump tìm hàm tương ứng → ghi offset → field. Lặp lại đến khi body của PACKET_CODE_EXAMPLE đọc được hết.

Share: