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:
Gered 2021-11-25 16:53:48 -05:00
parent 8e64e7059a
commit fa3b26285b

View file

@ -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(())