lots of rework. handles things way better now
- println output cleaned up a fair bit - now actually handles scenarios where multiple PSO packets are sent in single TCP packets. whoops! - removed intermediate PsoPacket struct ... just use GenericPacket - somewhat better way of managing changing client/server "peers" as the client connects to different servers within the same pcap capture
This commit is contained in:
parent
8e64e7059a
commit
fa3b26285b
|
@ -1,10 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::io::{Cursor, Read};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::io::Cursor;
|
||||||
use std::net::{IpAddr, SocketAddr};
|
use std::net::{IpAddr, SocketAddr};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use etherparse::{IpHeader, PacketHeaders};
|
use etherparse::{IpHeader, PacketHeaders};
|
||||||
use pcap::{Capture, Offline};
|
use pcap::{Capture, Offline};
|
||||||
use pretty_hex::*;
|
use pretty_hex::*;
|
||||||
|
@ -26,7 +27,6 @@ enum TcpDataPacketError {
|
||||||
NoTcpHeader,
|
NoTcpHeader,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct TcpDataPacket {
|
struct TcpDataPacket {
|
||||||
pub source: SocketAddr,
|
pub source: SocketAddr,
|
||||||
pub destination: SocketAddr,
|
pub destination: SocketAddr,
|
||||||
|
@ -106,53 +106,49 @@ impl<'a> TryFrom<PacketHeaders<'a>> for TcpDataPacket {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl Debug for TcpDataPacket {
|
||||||
struct PsoPacket {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
pub source: SocketAddr,
|
write!(
|
||||||
pub destination: SocketAddr,
|
f,
|
||||||
pub packet: GenericPacket,
|
"TcpDataPacket {{ source={}, destination={}, length={} }}",
|
||||||
}
|
self.source,
|
||||||
|
self.destination,
|
||||||
impl PsoPacket {
|
self.data.len()
|
||||||
pub fn new<T: Read>(
|
)?;
|
||||||
source: SocketAddr,
|
Ok(())
|
||||||
destination: SocketAddr,
|
|
||||||
header: PacketHeader,
|
|
||||||
reader: &mut T,
|
|
||||||
) -> Result<PsoPacket> {
|
|
||||||
let mut raw_data = vec![0u8; header.size as usize - PacketHeader::header_size()];
|
|
||||||
reader.read_exact(&mut raw_data)?;
|
|
||||||
|
|
||||||
Ok(PsoPacket {
|
|
||||||
source,
|
|
||||||
destination,
|
|
||||||
packet: GenericPacket::new(header, raw_data.into()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Peer {
|
struct Peer {
|
||||||
crypter: GCCrypter,
|
crypter: Option<GCCrypter>,
|
||||||
address: SocketAddr,
|
address: SocketAddr,
|
||||||
encrypted_buffer: Vec<u8>,
|
raw_buffer: Vec<u8>,
|
||||||
decrypted_buffer: Vec<u8>,
|
decrypted_buffer: Vec<u8>,
|
||||||
|
packets: Vec<GenericPacket>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Peer {
|
impl Peer {
|
||||||
pub fn new(crypt_key: u32, address: SocketAddr) -> Peer {
|
pub fn new(address: SocketAddr) -> Peer {
|
||||||
Peer {
|
Peer {
|
||||||
crypter: GCCrypter::new(crypt_key),
|
crypter: None,
|
||||||
address,
|
address,
|
||||||
encrypted_buffer: Vec::new(),
|
raw_buffer: Vec::new(),
|
||||||
decrypted_buffer: Vec::new(),
|
decrypted_buffer: Vec::new(),
|
||||||
|
packets: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn address(&self) -> &SocketAddr {
|
pub fn init_pso_session(&mut self, crypt_key: u32) {
|
||||||
&self.address
|
self.crypter = Some(GCCrypter::new(crypt_key));
|
||||||
|
self.raw_buffer.clear();
|
||||||
|
self.decrypted_buffer.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_packet(&mut self, packet: TcpDataPacket) -> Result<Option<PsoPacket>> {
|
pub fn push_pso_packet(&mut self, packet: GenericPacket) {
|
||||||
|
self.packets.push(packet)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_packet(&mut self, packet: TcpDataPacket) -> Result<()> {
|
||||||
if self.address != packet.source {
|
if self.address != packet.source {
|
||||||
return Err(anyhow!(
|
return Err(anyhow!(
|
||||||
"This Peer({}) cannot process TcpDataPacket originating from different source: {}",
|
"This Peer({}) cannot process TcpDataPacket originating from different source: {}",
|
||||||
|
@ -161,111 +157,160 @@ impl Peer {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// incoming bytes get added to the encrypted buffer first ...
|
// don't begin collecting data unless we're prepared to decrypt that data ...
|
||||||
self.encrypted_buffer.append(&mut packet.data.into_vec());
|
if let Some(crypter) = &mut self.crypter {
|
||||||
|
// incoming bytes get added to the raw (encrypted) buffer first ...
|
||||||
|
self.raw_buffer.append(&mut packet.data.into_vec());
|
||||||
|
|
||||||
// we should only be decrypting dword-sized bits of data (based on the way that the
|
// we should only be decrypting dword-sized bits of data (based on the way that the
|
||||||
// encryption algorithm works) so if we have that much data, lets go ahead and decrypt that
|
// encryption algorithm works) so if we have that much data, lets go ahead and decrypt that
|
||||||
// much and move those bytes over to the decrypted buffer ...
|
// much and move those bytes over to the decrypted buffer ...
|
||||||
if self.encrypted_buffer.len() >= 4 {
|
if self.raw_buffer.len() >= 4 {
|
||||||
let length_to_decrypt = self.encrypted_buffer.len() - (self.encrypted_buffer.len() & 3);
|
let length_to_decrypt = self.raw_buffer.len() - (self.raw_buffer.len() & 3); // dword-sized length only!
|
||||||
let mut bytes_to_decrypt: Vec<u8> =
|
let mut bytes_to_decrypt: Vec<u8> =
|
||||||
self.encrypted_buffer.drain(0..length_to_decrypt).collect();
|
self.raw_buffer.drain(0..length_to_decrypt).collect();
|
||||||
self.crypter.crypt(&mut bytes_to_decrypt);
|
crypter.crypt(&mut bytes_to_decrypt);
|
||||||
self.decrypted_buffer.append(&mut bytes_to_decrypt);
|
self.decrypted_buffer.append(&mut bytes_to_decrypt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to read a PacketHeader out of the decrypted buffer, and if successful, read out the
|
// try to extract as many complete packets out of the decrypted buffer as we can
|
||||||
// entire packet data if we have enough bytes available in the decrypted buffer
|
while self.decrypted_buffer.len() >= PacketHeader::header_size() {
|
||||||
// (if either of these fail, we need to leave the current decrypted buffer alone for now)
|
// if we have at least enough bytes for a PacketHeader available, read one out and figure
|
||||||
if self.decrypted_buffer.len() >= PacketHeader::header_size() {
|
// out if we have enough remaining bytes for the full packet that this header is for
|
||||||
let mut reader = Cursor::new(&self.decrypted_buffer);
|
let mut reader = &self.decrypted_buffer[0..PacketHeader::header_size()];
|
||||||
if let Ok(header) = PacketHeader::from_bytes(&mut reader) {
|
if let Ok(header) = PacketHeader::from_bytes(&mut reader) {
|
||||||
if self.decrypted_buffer.len() >= header.size as usize {
|
if self.decrypted_buffer.len() >= header.size as usize {
|
||||||
let pso_packet =
|
// the buffer has enough bytes for this entire packet. read it out and add it
|
||||||
PsoPacket::new(packet.source, packet.destination, header, &mut reader)?;
|
// to our internal list of reconstructed packets
|
||||||
// need to also remove the entire packet's bytes from the front of the buffer
|
let packet_length = header.size as usize;
|
||||||
self.decrypted_buffer.drain(0..header.size as usize);
|
let packet_bytes: Vec<u8> =
|
||||||
return Ok(Some(pso_packet));
|
self.decrypted_buffer.drain(0..packet_length).collect();
|
||||||
|
let mut reader = Cursor::new(packet_bytes);
|
||||||
|
self.packets.push(GenericPacket::from_bytes(&mut reader)?);
|
||||||
|
} else {
|
||||||
|
// unable to read the full packet with the bytes currently in the decrypted
|
||||||
|
// buffer ... so we'll need to try again later after receiving some more data
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(None)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Context {
|
impl Iterator for Peer {
|
||||||
is_pso_session_inited: bool,
|
type Item = GenericPacket;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
if self.packets.is_empty() {
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
Some(self.packets.remove(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Peer {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Peer {{ address={} }}", self.address)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Session {
|
||||||
peers: HashMap<SocketAddr, Peer>,
|
peers: HashMap<SocketAddr, Peer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Session {
|
||||||
pub fn new() -> Context {
|
pub fn new() -> Session {
|
||||||
Context {
|
Session {
|
||||||
is_pso_session_inited: false,
|
|
||||||
peers: HashMap::new(),
|
peers: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_pso_session_inited(&self) -> bool {
|
pub fn get_peer(&mut self, address: SocketAddr) -> Option<&mut Peer> {
|
||||||
self.is_pso_session_inited
|
self.peers.get_mut(&address)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process(&mut self, packet: TcpDataPacket) -> Result<Option<PsoPacket>> {
|
fn get_or_create_peer(&mut self, address: SocketAddr) -> &mut Peer {
|
||||||
if packet.tcp_rst {
|
if self.peers.contains_key(&address) {
|
||||||
println!("Encountered TCP RST. Resetting session. Expecting to encounter new peers and InitEncryptionPacket next ...");
|
self.peers.get_mut(&address).unwrap()
|
||||||
self.peers.clear();
|
|
||||||
self.is_pso_session_inited = false;
|
|
||||||
Ok(None)
|
|
||||||
} else if packet.tcp_fin {
|
|
||||||
println!("Peer {} sent TCP FIN. Resetting session. Expecting to encounter new peers and InitEncryptionPacket next ...", packet.source);
|
|
||||||
self.peers.clear();
|
|
||||||
self.is_pso_session_inited = false;
|
|
||||||
Ok(None)
|
|
||||||
} else if let Some(init_packet) = packet.as_init_encryption_packet() {
|
|
||||||
println!("Encountered InitEncryptionPacket. Starting new session ...");
|
|
||||||
// the "init packet" indicates the start of a PSO client/server session. this could occur
|
|
||||||
// multiple times within the same pcap file as a client moves between different servers
|
|
||||||
// (e.g. login server, ship server, ...)
|
|
||||||
|
|
||||||
self.peers.clear();
|
|
||||||
let server = Peer::new(init_packet.server_key, packet.source);
|
|
||||||
let client = Peer::new(init_packet.client_key, packet.destination);
|
|
||||||
self.peers.insert(packet.source, server);
|
|
||||||
self.peers.insert(packet.destination, client);
|
|
||||||
|
|
||||||
self.is_pso_session_inited = true;
|
|
||||||
|
|
||||||
Ok(Some(PsoPacket {
|
|
||||||
source: packet.source,
|
|
||||||
destination: packet.destination,
|
|
||||||
packet: init_packet.try_into()?,
|
|
||||||
}))
|
|
||||||
} else if !self.peers.is_empty() {
|
|
||||||
// otherwise, if we have a set of peers already (because of a previous init packet)
|
|
||||||
// then we can process this packet using the peer it was sent from
|
|
||||||
|
|
||||||
let peer = match self.peers.get_mut(&packet.source) {
|
|
||||||
None => return Err(anyhow!("No matching peer for {} ... ?", packet.source)),
|
|
||||||
Some(peer) => peer,
|
|
||||||
};
|
|
||||||
Ok(peer.process_packet(packet)?)
|
|
||||||
} else {
|
} else {
|
||||||
// this would occur only if no init packet has been found yet. as such, this is
|
println!("Encountered new peer: {}\n", address);
|
||||||
// probably non-PSO packet stuff we don't care about
|
let new_peer = Peer::new(address);
|
||||||
Ok(None)
|
self.peers.insert(address, new_peer);
|
||||||
|
self.get_or_create_peer(address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn process_packet(&mut self, packet: TcpDataPacket) -> Result<()> {
|
||||||
|
if packet.tcp_rst {
|
||||||
|
println!(
|
||||||
|
"Encountered TCP RST. Removing peers {} and {}.\n",
|
||||||
|
packet.source, packet.destination
|
||||||
|
);
|
||||||
|
self.peers.remove(&packet.source);
|
||||||
|
self.peers.remove(&packet.destination);
|
||||||
|
} else if packet.tcp_fin {
|
||||||
|
println!("Peer {} sent TCP FIN. Removing peer.\n", packet.source);
|
||||||
|
self.peers.remove(&packet.source);
|
||||||
|
} else if let Some(init_packet) = packet.as_init_encryption_packet() {
|
||||||
|
println!(
|
||||||
|
"Encountered InitEncryptionPacket sent from peer {}. Starting new session.",
|
||||||
|
packet.source
|
||||||
|
);
|
||||||
|
|
||||||
|
// the "init packet" indicates the start of a PSO client/server session. this could
|
||||||
|
// occur multiple times within the same pcap file as a client moves between different
|
||||||
|
// servers (e.g. from login server to ship server, switching between ships, etc).
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Treating peer {} as the client, setting client decryption key: {:#010x}",
|
||||||
|
packet.destination,
|
||||||
|
init_packet.client_key()
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = self.get_or_create_peer(packet.destination);
|
||||||
|
client.init_pso_session(init_packet.client_key);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Treating peer {} as the server, setting server decryption key: {:#010x}",
|
||||||
|
packet.source,
|
||||||
|
init_packet.server_key()
|
||||||
|
);
|
||||||
|
|
||||||
|
let server = self.get_or_create_peer(packet.source);
|
||||||
|
server.init_pso_session(init_packet.server_key);
|
||||||
|
server.push_pso_packet(
|
||||||
|
init_packet
|
||||||
|
.try_into()
|
||||||
|
.context("Failed to convert InitEncryptionPacket into GenericPacket")?,
|
||||||
|
);
|
||||||
|
|
||||||
|
println!();
|
||||||
|
} else {
|
||||||
|
// process the packet via the peer it was sent from
|
||||||
|
let peer = self.get_or_create_peer(packet.source);
|
||||||
|
peer.process_packet(packet)
|
||||||
|
.with_context(|| format!("Failed to process packet for peer {:?}", peer))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn analyze(path: &Path) -> Result<()> {
|
pub fn analyze(path: &Path) -> Result<()> {
|
||||||
println!("Opening capture file: {}", path.to_string_lossy());
|
println!("Opening capture file: {}", path.to_string_lossy());
|
||||||
|
|
||||||
let mut cap: Capture<Offline> = Capture::from_file(path)?.into();
|
let mut cap: Capture<Offline> = Capture::from_file(path)
|
||||||
cap.filter("tcp")?;
|
.with_context(|| format!("Failed to open capture file: {:?}", path))?
|
||||||
|
.into();
|
||||||
|
cap.filter("tcp")
|
||||||
|
.context("Failed to apply 'tcp' filter to opened capture")?;
|
||||||
|
|
||||||
let mut context = Context::new();
|
let mut session = Session::new();
|
||||||
|
|
||||||
let hex_cfg = HexConfig {
|
let hex_cfg = HexConfig {
|
||||||
title: false,
|
title: false,
|
||||||
|
@ -274,28 +319,39 @@ pub fn analyze(path: &Path) -> Result<()> {
|
||||||
..HexConfig::default()
|
..HexConfig::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
println!("Beginning analysis ...\n");
|
||||||
|
|
||||||
while let Ok(raw_packet) = cap.next() {
|
while let Ok(raw_packet) = cap.next() {
|
||||||
if let Ok(decoded_packet) = PacketHeaders::from_ethernet_slice(raw_packet.data) {
|
if let Ok(decoded_packet) = PacketHeaders::from_ethernet_slice(raw_packet.data) {
|
||||||
if let Ok(our_packet) = TcpDataPacket::try_from(decoded_packet) {
|
if let Ok(our_packet) = TcpDataPacket::try_from(decoded_packet) {
|
||||||
println!(
|
println!(
|
||||||
">>>> packet - ts: {}.{}, from: {:?}, to: {:?}, length: {}\n",
|
">>>> packet at ts: {}.{} - {:?}\n",
|
||||||
raw_packet.header.ts.tv_sec,
|
raw_packet.header.ts.tv_sec, raw_packet.header.ts.tv_usec, our_packet
|
||||||
raw_packet.header.ts.tv_usec,
|
|
||||||
our_packet.source,
|
|
||||||
our_packet.destination,
|
|
||||||
our_packet.data.len()
|
|
||||||
);
|
);
|
||||||
if let Some(pso_packet) = context.process(our_packet)? {
|
|
||||||
println!(
|
let peer_address = our_packet.source;
|
||||||
"id={:#04x}, flags={:#04x}, size={}",
|
|
||||||
pso_packet.packet.header.id(),
|
session.process_packet(our_packet).with_context(|| {
|
||||||
pso_packet.packet.header.flags(),
|
format!(
|
||||||
pso_packet.packet.header.size()
|
"Session failed to process packet at ts: {}.{}",
|
||||||
);
|
raw_packet.header.ts.tv_sec, raw_packet.header.ts.tv_usec
|
||||||
if pso_packet.packet.body.is_empty() {
|
)
|
||||||
println!("<No data>");
|
})?;
|
||||||
} else {
|
|
||||||
println!("{:?}", pso_packet.packet.body.hex_conf(hex_cfg));
|
if let Some(peer) = session.get_peer(peer_address) {
|
||||||
|
while let Some(pso_packet) = peer.next() {
|
||||||
|
println!(
|
||||||
|
"id={:#04x}, flags={:#04x}, size={} ({2:#x})",
|
||||||
|
pso_packet.header.id(),
|
||||||
|
pso_packet.header.flags(),
|
||||||
|
pso_packet.header.size()
|
||||||
|
);
|
||||||
|
if pso_packet.body.is_empty() {
|
||||||
|
println!("<No data>");
|
||||||
|
} else {
|
||||||
|
println!("{:?}", pso_packet.body.hex_conf(hex_cfg));
|
||||||
|
}
|
||||||
|
println!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -310,8 +366,6 @@ pub fn analyze(path: &Path) -> Result<()> {
|
||||||
raw_packet.header
|
raw_packet.header
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue