I'm capturing packets from Final Fantasy 14 and trying to decode some information!
I used ss
to see which TCP socket my ffxiv_dx11.exe
process was talking to (204.2.229.99:55024
)
❯ sudo ss -Otnp | grep ffxiv
ESTAB 0 0 192.168.1.232:55060 204.2.229.99:55024 users:(("ffxiv_dx11.exe",pid=1022957,fd=360),("wineserver",pid=1022873,fd=529))
ESTAB 0 0 192.168.1.232:55058 204.2.229.99:55024 users:(("ffxiv_dx11.exe",pid=1022957,fd=361),("wineserver",pid=1022873,fd=528))
I used tshark
to capture some packets:
sudo tshark -f 'tcp port 55024 and ip host 204.2.229.99' -w /tmp/ffxiv-packets.pcap -x
We can look at the data in the packets, using -T fields
output for tshark
with -e data
as the field selected. That gives a UTF-8 hex string as output, so we read this string with xxd -r
and then pass the bits back to hexdump -C
:
for packet in $(tshark -r /tmp/ffxiv-packets.pcap -Y 'ip.dst == 192.168.1.232' -T fields -e data); do
xxd -r -p <<< "$packet" | hexdump -C
echo
done
00000000 52 52 a0 41 ff 5d 46 e2 7f 2a 64 4d 7b 99 c4 75 |RR.A.]F..*dM{..u|
00000010 10 05 8b c9 83 01 00 00 88 00 00 00 00 00 02 00 |................|
00000020 01 00 00 00 00 00 00 00 30 00 00 00 25 82 10 40 |........0...%..@|
00000030 3a f5 93 10 03 00 00 00 14 00 9f 03 00 00 16 02 |:...............|
00000040 ea 06 46 63 00 00 00 00 25 03 42 00 14 7f 32 70 |..Fc....%.B...2p|
00000050 27 8a 73 5a dc 7f 00 00 30 00 00 00 17 82 10 40 |'.sZ....0......@|
00000060 3a f5 93 10 03 00 00 00 14 00 9f 03 00 00 16 02 |:...............|
00000070 ea 06 46 63 00 00 00 00 d2 6e 82 00 14 7f 1b 79 |..Fc.....n.....y|
00000080 3d 88 ee 58 dc 7f 00 00 |=..X....|
00000088
00000000 52 52 a0 41 ff 5d 46 e2 7f 2a 64 4d 7b 99 c4 75 |RR.A.]F..*dM{..u|
00000010 68 05 8b c9 83 01 00 00 5a 00 00 00 00 00 01 00 |h.......Z.......|
00000020 01 02 00 00 40 00 00 00 3f ff fc 00 35 b6 36 82 |....@...?...5.6.|
00000030 de 15 36 2f a9 9e ff d0 10 e0 c1 4b 95 2f f2 0e |..6/.......K./..|
00000040 62 98 f7 1c d1 99 d0 00 28 9f fd 80 94 0e 6f 18 |b.......(.....o.|
00000050 11 1f 5f 70 a5 50 b9 3d 97 21 |.._p.P.=.!|
0000005a
...
To get some information on how packets are structured, we can read ravahn/machina on GitHub. In FFXIVBundleDecoder.cs
, we see a check for a magic number in the header, 0x41a05252
:
if (header.magic0 != 0x41a05252)
And our packets start with 52 52 a0 41
- that's 0x41a05252
in little endian! We can use xxd
's -e
flag:
Switch to little-endian hexdump. This option treats byte groups as words in little-endian byte order. The default grouping of 4 bytes may be changed using -g.
00000000: 41a05252 e2465dff 4d642a7f 75c4997b RR.A.]F..*dM{..u
00000010: c98b0510 00000183 00000088 00020000 ................
00000020: 00000001 00000000 00000030 40108225 ........0...%..@
00000030: 1093f53a 00000003 039f0014 02160000 :...............
00000040: 634606ea 00000000 00420325 70327f14 ..Fc....%.B...2p
00000050: 5a738a27 00007fdc 00000030 40108217 '.sZ....0......@
00000060: 1093f53a 00000003 039f0014 02160000 :...............
00000070: 634606ea 00000000 00826ed2 791b7f14 ..Fc.....n.....y
00000080: 58ee883d 00007fdc =..X....
So there's a specification of some data that comes after the header in Server__BundleHeader.cs
:
[StructLayout(LayoutKind.Explicit)]
public struct Server_BundleHeader
{
[FieldOffset(0)]
public uint magic0;
[FieldOffset(4)]
public uint magic1;
[FieldOffset(8)]
public uint magic2;
[FieldOffset(12)]
public uint magic3;
[FieldOffset(16)]
private readonly ulong _epoch;
Sounds like there's a timestamp (private readonly ulong _epoch
) 16 bytes in ([FieldOffset(16)]
). What's the current time, in hex?
❯ fend "$(date +%s) in hex"
634aae36
Okay, let's see if these packets have a time around then, by seeking forward 16 bytes (-s $((16 * 4))
) and printing 4 bytes (-l 4
):
for packet in $(tshark -r /tmp/ffxiv-packets.pcap -Y 'ip.dst == 192.168.1.232' -T fields -e data); do
xxd -r -p <<< "$packet" | xxd -e -s $((16 * 4)) -l 4
done | head
00000040: 634606ea ..Fc
00000040: 1cf79862 b...
00000040: 634606eb ..Fc
00000040: 634606eb ..Fc
00000040: 634606eb ..Fc
00000040: 0cf76a63 cj..
00000040: 634606eb ..Fc
00000040: 0cf76a63 cj..
00000040: 634606eb ..Fc
00000040: fcf63c64 d<..
Nice! We can even interpret these times:
for packet in $(tshark -r /tmp/ffxiv-packets.pcap -Y 'ip.dst == 192.168.1.232' -T fields -e data); do
timestamp=$(xxd -r -p <<< "$packet" | xxd -e -s $((16 * 4)) -l 4 | cut -d ' ' -f 2)
plaintext="$(date -d "@$(fend "0x${timestamp} in dec")")"; echo "${timestamp} -> ${plaintext}"
done | head
634606ea -> Tue Oct 11 08:14:34 PM EDT 2022
1cf79862 -> Sun May 26 04:47:30 PM EDT 1985
634606eb -> Tue Oct 11 08:14:35 PM EDT 2022
634606eb -> Tue Oct 11 08:14:35 PM EDT 2022
634606eb -> Tue Oct 11 08:14:35 PM EDT 2022
0cf76a63 -> Mon Nov 22 03:06:59 PM EST 1976
634606eb -> Tue Oct 11 08:14:35 PM EDT 2022
0cf76a63 -> Mon Nov 22 03:06:59 PM EST 1976
634606eb -> Tue Oct 11 08:14:35 PM EDT 2022
fcf63c64 -> Fri Jun 27 03:42:28 AM EDT 2104
Looks like not all of these are valid dates, but many of them look close enough :)
zzzzzzz okay i'm going back to playing the game
I got curious and tried out pcap
in Rust:
use std::io::Write;
const CAPTURE_FILTER: &str = "tcp port 55024 and ip host 204.2.229.99";
fn main() {
let mut cap = pcap::Capture::from_device("any")
.expect("failed to find device")
.immediate_mode(true)
.open()
.expect("failed to open device");
cap.filter(CAPTURE_FILTER, true)
.expect("failed to apply filter");
let packet = cap.next_packet().expect("failed to get packet");
dbg!(packet.header);
std::io::stdout()
.write_all(packet.data)
.expect("failed to write");
}
❯ cargo build && sudo ./target/debug/ffxiv-lifestream | xxd
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
[src/main.rs:17] packet.header = PacketHeader { ts: 1665856980.727584, caplen: 155, len: 155 }
00000000: 0000 0001 0006 e8fc aff8 8f20 0000 0800 ........... ....
00000010: 4500 008b 4765 4000 3706 8811 cc02 e563 E...Ge@.7......c
00000020: c0a8 01e8 d6f0 8022 7661 02be 927d 8468 ......."va...}.h
00000030: 8018 06af 89a2 0000 0101 080a 338f 3245 ............3.2E
00000040: fbad cce1 5252 a041 ff5d 46e2 7f2a 644d ....RR.A.]F..*dM
00000050: 7b99 c475 d846 d0dc 8301 0000 5700 0000 {..u.F......W...
00000060: 0000 0100 0102 0000 4000 0000 3fff fc00 ........@...?...
00000070: 0c21 0cec 9430 0cd8 8a6e ffd0 10e0 c14b .!...0...n.....K
00000080: 9530 196c aa51 29b5 e159 d000 289f 7c83 .0.l.Q)..Y..(.|.
00000090: d4d9 cf47 a0a5 a880 e42b 3e ...G.....+>
In this case the payload (starting with 5252 a041
) starts out at offset 0x44
. Hmm, is the data preceding that the captured TCP header?