From 11a01b038fbefca750f8793c052581a3d57280b9 Mon Sep 17 00:00:00 2001 From: Mazo Date: Tue, 26 May 2020 21:17:19 +0100 Subject: [PATCH] Add some logging for suspicious HitMessages (#459) * Add some logging for suspicious HitMessages * Add HitPositionDiscrepancyThreshold config settings * Add hit position discrepancy check to LashMessage & SplashHitMessage * Whitespace --- .../psforever/packet/game/LashMessage.scala | 6 +++--- config/worldserver.ini.dist | 9 +++++++++ pslogin/src/main/scala/WorldConfig.scala | 3 +++ .../src/main/scala/WorldSessionActor.scala | 19 +++++++++++++++++-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/LashMessage.scala b/common/src/main/scala/net/psforever/packet/game/LashMessage.scala index 71137d7f1..cb4111f3c 100644 --- a/common/src/main/scala/net/psforever/packet/game/LashMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/LashMessage.scala @@ -12,14 +12,14 @@ import scodec.codecs._ * @param player na * @param victim na * @param bullet na - * @param pos na + * @param hit_pos na * @param unk1 na */ final case class LashMessage(seq_time : Int, player : PlanetSideGUID, victim : PlanetSideGUID, bullet : PlanetSideGUID, - pos : Vector3, + hit_pos : Vector3, unk1 : Int) extends PlanetSideGamePacket { type Packet = LashMessage @@ -33,7 +33,7 @@ object LashMessage extends Marshallable[LashMessage] { ("player" | PlanetSideGUID.codec) :: ("victim" | PlanetSideGUID.codec) :: ("bullet" | PlanetSideGUID.codec) :: - ("pos" | Vector3.codec_pos) :: + ("hit_pos" | Vector3.codec_pos) :: ("unk1" | uintL(3)) ).as[LashMessage] } diff --git a/config/worldserver.ini.dist b/config/worldserver.ini.dist index f60445f7a..4a623cd8f 100644 --- a/config/worldserver.ini.dist +++ b/config/worldserver.ini.dist @@ -248,3 +248,12 @@ NetSim.ReorderTime = 150 milliseconds # yes - (Enabled) Active = no + +[antihack] + +# HitPositionDiscrepancyThreshold (int) +# Description: The distance (squared) threshold that triggers if the reported hit location of a shot does not match the object being hit's location on the server +# Range: [1, 1000000] +# Default: 10000 (sqrt 10000 = ~100 ingame units) + +HitPositionDiscrepancyThreshold = 10000 diff --git a/pslogin/src/main/scala/WorldConfig.scala b/pslogin/src/main/scala/WorldConfig.scala index 493efae6b..a2f02a83c 100644 --- a/pslogin/src/main/scala/WorldConfig.scala +++ b/pslogin/src/main/scala/WorldConfig.scala @@ -45,6 +45,9 @@ object WorldConfig extends ConfigParser { ), ConfigSection("kamon", ConfigEntryBool("Active", false) + ), + ConfigSection("antihack", + ConfigEntryInt("HitPositionDiscrepancyThreshold", 10000, Constraints.min(1), Constraints.max(1000000)) ) ) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 6c2aa689a..7fe855545 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -5,7 +5,10 @@ import com.github.mauricio.async.db.general.ArrayRowData import com.github.mauricio.async.db.{Connection, QueryResult} import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger + +import net.psforever.WorldConfig import org.log4s.{Logger, MDC} + import scala.annotation.{switch, tailrec} import scala.collection.mutable.LongMap import scala.concurrent.{Await, Future, Promise} @@ -6213,6 +6216,7 @@ class WorldSessionActor extends Actor case Some(hitInfo) => ValidObject(hitInfo.hitobject_guid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + CheckForHitPositionDiscrepancy(projectile_guid, hitInfo.hit_pos, target) Some((target, hitInfo.shot_origin, hitInfo.hit_pos)) case _ => None @@ -6238,6 +6242,7 @@ class WorldSessionActor extends Actor //direct_victim_uid ValidObject(direct_victim_uid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { case Some(projectile) => HandleDealingDamage(target, projectile) @@ -6249,6 +6254,7 @@ class WorldSessionActor extends Actor targets.foreach(elem => { ValidObject(elem.uid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match { case Some(projectile) => HandleDealingDamage(target, projectile) @@ -6270,11 +6276,12 @@ class WorldSessionActor extends Actor case None => ; } - case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) => + case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, hit_pos, unk1) => log.info(s"Lash: $msg") ValidObject(victim_guid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, pos) match { + CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target) + ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, hit_pos) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; @@ -11397,6 +11404,14 @@ class WorldSessionActor extends Actor } } + def CheckForHitPositionDiscrepancy(projectile_guid: PlanetSideGUID, hitPos : Vector3, target : PlanetSideGameObject with FactionAffinity with Vitality): Unit = { + val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position) + if(hitPositionDiscrepancy > WorldConfig.Get[Int]("antihack.HitPositionDiscrepancyThreshold")) { + // If the target position on the server does not match the position where the projectile landed within reason there may be foul play + log.warn(s"Shot guid ${projectile_guid} has hit location discrepancy with target location. Target: ${target.Position} Reported: ${hitPos}, Distance: ${hitPositionDiscrepancy} / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect") + } + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose())