initial commit
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Cryptography;
|
||||
using Framework.IO;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
public abstract class Warden
|
||||
{
|
||||
public Warden()
|
||||
{
|
||||
_inputCrypto = new SARC4();
|
||||
_outputCrypto = new SARC4();
|
||||
_checkTimer = 10 * Time.InMilliseconds;
|
||||
}
|
||||
|
||||
public abstract void Init(WorldSession session, BigInteger k);
|
||||
public abstract ClientWardenModule GetModuleForClient();
|
||||
public abstract void InitializeModule();
|
||||
public abstract void RequestHash();
|
||||
public abstract void HandleHashResult(ByteBuffer buff);
|
||||
public abstract void RequestData();
|
||||
public abstract void HandleData(ByteBuffer buff);
|
||||
|
||||
public void SendModuleToClient()
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "Send module to client");
|
||||
|
||||
uint sizeLeft = _module.CompressedSize;
|
||||
int pos = 0;
|
||||
uint burstSize;
|
||||
while (sizeLeft > 0)
|
||||
{
|
||||
WardenModuleTransfer transfer = new WardenModuleTransfer();
|
||||
|
||||
burstSize = sizeLeft < 500 ? sizeLeft : 500u;
|
||||
transfer.Command = WardenOpcodes.Smsg_ModuleCache;
|
||||
transfer.DataSize = (ushort)burstSize;
|
||||
Buffer.BlockCopy(_module.CompressedData, pos, transfer.Data, 0, (int)burstSize);
|
||||
sizeLeft -= burstSize;
|
||||
pos += (int)burstSize;
|
||||
|
||||
WardenDataServer packet = new WardenDataServer();
|
||||
packet.Data = EncryptData(transfer.Write());
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestModule()
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "Request module");
|
||||
|
||||
// Create packet structure
|
||||
WardenModuleUse request = new WardenModuleUse();
|
||||
request.Command = WardenOpcodes.Smsg_ModuleUse;
|
||||
|
||||
request.ModuleId = _module.Id;
|
||||
request.ModuleKey = _module.Key;
|
||||
request.Size = _module.CompressedSize;
|
||||
|
||||
WardenDataServer packet = new WardenDataServer();
|
||||
packet.Data = EncryptData(request.Write());
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
uint currentTimestamp = Time.GetMSTime();
|
||||
uint diff = currentTimestamp - _previousTimestamp;
|
||||
_previousTimestamp = currentTimestamp;
|
||||
|
||||
if (_dataSent)
|
||||
{
|
||||
uint maxClientResponseDelay = WorldConfig.GetUIntValue(WorldCfg.WardenClientResponseDelay);
|
||||
if (maxClientResponseDelay > 0)
|
||||
{
|
||||
// Kick player if client response delays more than set in config
|
||||
if (_clientResponseTimer > maxClientResponseDelay * Time.InMilliseconds)
|
||||
{
|
||||
Log.outWarn(LogFilter.Warden, "{0} (latency: {1}, IP: {2}) exceeded Warden module response delay for more than {3} - disconnecting client",
|
||||
_session.GetPlayerInfo(), _session.GetLatency(), _session.GetRemoteAddress(), Time.secsToTimeString(maxClientResponseDelay, true));
|
||||
_session.KickPlayer();
|
||||
}
|
||||
else
|
||||
_clientResponseTimer += diff;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (diff >= _checkTimer)
|
||||
{
|
||||
RequestData();
|
||||
}
|
||||
else
|
||||
_checkTimer -= diff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] DecryptData(byte[] buffer)
|
||||
{
|
||||
_inputCrypto.ProcessBuffer(buffer, buffer.Length);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public ByteBuffer EncryptData(byte[] buffer)
|
||||
{
|
||||
_outputCrypto.ProcessBuffer(buffer, buffer.Length);
|
||||
return new ByteBuffer(buffer);
|
||||
}
|
||||
|
||||
public bool IsValidCheckSum(uint checksum, byte[] data, ushort length)
|
||||
{
|
||||
uint newChecksum = BuildChecksum(data, length);
|
||||
|
||||
if (checksum != newChecksum)
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "CHECKSUM IS NOT VALID");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "CHECKSUM IS VALID");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public uint BuildChecksum(byte[] data, uint length)
|
||||
{
|
||||
SHA1 sha = SHA1.Create();
|
||||
|
||||
var hash = sha.ComputeHash(data, 0, (int)length);
|
||||
uint checkSum = 0;
|
||||
for (byte i = 0; i < 5; ++i)
|
||||
checkSum = checkSum ^ BitConverter.ToUInt32(hash, i * 4);
|
||||
|
||||
return checkSum;
|
||||
}
|
||||
|
||||
public string Penalty(WardenCheck check = null)
|
||||
{
|
||||
WardenActions action;
|
||||
|
||||
if (check != null)
|
||||
action = check.Action;
|
||||
else
|
||||
action = (WardenActions)WorldConfig.GetIntValue(WorldCfg.WardenClientFailAction);
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case WardenActions.Log:
|
||||
return "None";
|
||||
case WardenActions.Kick:
|
||||
_session.KickPlayer();
|
||||
return "Kick";
|
||||
case WardenActions.Ban:
|
||||
{
|
||||
string duration = WorldConfig.GetIntValue(WorldCfg.WardenClientBanDuration) + "s";
|
||||
string accountName;
|
||||
Global.AccountMgr.GetName(_session.GetAccountId(), out accountName);
|
||||
string banReason = "Warden Anticheat Violation";
|
||||
// Check can be NULL, for example if the client sent a wrong signature in the warden packet (CHECKSUM FAIL)
|
||||
if (check != null)
|
||||
banReason += ": " + check.Comment + " (CheckId: " + check.CheckId + ")";
|
||||
|
||||
Global.WorldMgr.BanAccount(BanMode.Account, accountName, duration, banReason, "Server");
|
||||
return "Ban";
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "Undefined";
|
||||
}
|
||||
|
||||
internal WorldSession _session;
|
||||
internal byte[] _inputKey = new byte[16];
|
||||
internal byte[] _outputKey = new byte[16];
|
||||
internal byte[] _seed = new byte[16];
|
||||
internal SARC4 _inputCrypto;
|
||||
internal SARC4 _outputCrypto;
|
||||
internal uint _checkTimer; // Timer for sending check requests
|
||||
internal uint _clientResponseTimer; // Timer for client response delay
|
||||
internal bool _dataSent;
|
||||
internal uint _previousTimestamp;
|
||||
internal ClientWardenModule _module;
|
||||
internal bool _initialized;
|
||||
}
|
||||
|
||||
public class ClientWardenModule
|
||||
{
|
||||
public byte[] Id = new byte[16];
|
||||
public byte[] Key = new byte[16];
|
||||
public uint CompressedSize;
|
||||
public byte[] CompressedData;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
class SHA1Randx
|
||||
{
|
||||
public SHA1Randx(byte[] buff)
|
||||
{
|
||||
int halfSize = buff.Length / 2;
|
||||
|
||||
sh = SHA1.Create();
|
||||
o1 = sh.ComputeHash(buff, 0, halfSize);
|
||||
|
||||
sh = SHA1.Create();
|
||||
o2 = sh.ComputeHash(buff.Skip(halfSize).ToArray(), 0, buff.Length - halfSize);
|
||||
|
||||
FillUp();
|
||||
}
|
||||
|
||||
public void Generate(byte[] buf, uint sz)
|
||||
{
|
||||
for (uint i = 0; i < sz; ++i)
|
||||
{
|
||||
if (taken == 20)
|
||||
FillUp();
|
||||
|
||||
buf[i] = o0[taken];
|
||||
taken++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FillUp()
|
||||
{
|
||||
sh = SHA1.Create();
|
||||
sh.ComputeHash(o1, 0, 20);
|
||||
sh.ComputeHash(o0, 0, 20);
|
||||
o0 = sh.ComputeHash(o2, 0, 20);
|
||||
|
||||
taken = 0;
|
||||
}
|
||||
|
||||
SHA1 sh;
|
||||
uint taken;
|
||||
byte[] o0 = new byte[20];
|
||||
byte[] o1 = new byte[20];
|
||||
byte[] o2 = new byte[20];
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,455 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Cryptography;
|
||||
using Framework.IO;
|
||||
using Game.Network.Packets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
class WardenWin : Warden
|
||||
{
|
||||
public override void Init(WorldSession session, BigInteger k)
|
||||
{
|
||||
_session = session;
|
||||
// Generate Warden Key
|
||||
SHA1Randx WK = new SHA1Randx(k.ToByteArray());
|
||||
WK.Generate(_inputKey, 16);
|
||||
WK.Generate(_outputKey, 16);
|
||||
|
||||
_seed = WardenModuleWin.Seed;
|
||||
|
||||
_inputCrypto.PrepareKey(_inputKey);
|
||||
_outputCrypto.PrepareKey(_outputKey);
|
||||
Log.outDebug(LogFilter.Warden, "Server side warden for client {0} initializing...", session.GetAccountId());
|
||||
Log.outDebug(LogFilter.Warden, "C->S Key: {0}", _inputKey.ToHexString());
|
||||
Log.outDebug(LogFilter.Warden, "S->C Key: {0}", _outputKey.ToHexString());
|
||||
Log.outDebug(LogFilter.Warden, " Seed: {0}", _seed.ToHexString());
|
||||
Log.outDebug(LogFilter.Warden, "Loading Module...");
|
||||
|
||||
_module = GetModuleForClient();
|
||||
|
||||
Log.outDebug(LogFilter.Warden, "Module Key: {0}", _module.Key.ToHexString());
|
||||
Log.outDebug(LogFilter.Warden, "Module ID: {0}", _module.Id.ToHexString());
|
||||
RequestModule();
|
||||
}
|
||||
|
||||
public override ClientWardenModule GetModuleForClient()
|
||||
{
|
||||
ClientWardenModule mod = new ClientWardenModule();
|
||||
|
||||
uint length = (uint)WardenModuleWin.Module.Length;
|
||||
|
||||
// data assign
|
||||
mod.CompressedSize = length;
|
||||
mod.CompressedData = WardenModuleWin.Module;
|
||||
mod.Key = WardenModuleWin.ModuleKey;
|
||||
|
||||
// md5 hash
|
||||
System.Security.Cryptography.MD5 ctx = System.Security.Cryptography.MD5.Create();
|
||||
ctx.Initialize();
|
||||
ctx.TransformBlock(mod.CompressedData, 0, mod.CompressedData.Length, mod.CompressedData, 0);
|
||||
ctx.TransformBlock(mod.Id, 0, mod.Id.Length, mod.Id, 0);
|
||||
|
||||
return mod;
|
||||
}
|
||||
|
||||
public override void InitializeModule()
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "Initialize module");
|
||||
|
||||
// Create packet structure
|
||||
WardenInitModuleRequest Request = new WardenInitModuleRequest();
|
||||
Request.Command1 = WardenOpcodes.Smsg_ModuleInitialize;
|
||||
Request.Size1 = 20;
|
||||
Request.Unk1 = 1;
|
||||
Request.Unk2 = 0;
|
||||
Request.Type = 1;
|
||||
Request.String_library1 = 0;
|
||||
Request.Function1[0] = 0x00024F80; // 0x00400000 + 0x00024F80 SFileOpenFile
|
||||
Request.Function1[1] = 0x000218C0; // 0x00400000 + 0x000218C0 SFileGetFileSize
|
||||
Request.Function1[2] = 0x00022530; // 0x00400000 + 0x00022530 SFileReadFile
|
||||
Request.Function1[3] = 0x00022910; // 0x00400000 + 0x00022910 SFileCloseFile
|
||||
Request.CheckSumm1 = BuildChecksum(BitConverter.GetBytes(Request.Unk1), 20);
|
||||
|
||||
Request.Command2 = WardenOpcodes.Smsg_ModuleInitialize;
|
||||
Request.Size2 = 8;
|
||||
Request.Unk3 = 4;
|
||||
Request.Unk4 = 0;
|
||||
Request.String_library2 = 0;
|
||||
Request.Function2 = 0x00419D40; // 0x00400000 + 0x00419D40 FrameScript::GetText
|
||||
Request.Function2_set = 1;
|
||||
Request.CheckSumm2 = BuildChecksum(BitConverter.GetBytes(Request.Unk2), 8);
|
||||
|
||||
Request.Command3 = WardenOpcodes.Smsg_ModuleInitialize;
|
||||
Request.Size3 = 8;
|
||||
Request.Unk5 = 1;
|
||||
Request.Unk6 = 1;
|
||||
Request.String_library3 = 0;
|
||||
Request.Function3 = 0x0046AE20; // 0x00400000 + 0x0046AE20 PerformanceCounter
|
||||
Request.Function3_set = 1;
|
||||
Request.CheckSumm3 = BuildChecksum(BitConverter.GetBytes(Request.Unk5), 8);
|
||||
|
||||
WardenDataServer packet = new WardenDataServer();
|
||||
packet.Data = EncryptData(Request.Write());
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public override void RequestHash()
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "Request hash");
|
||||
|
||||
// Create packet structure
|
||||
WardenHashRequest Request = new WardenHashRequest();
|
||||
Request.Command = WardenOpcodes.Smsg_HashRequest;
|
||||
Request.Seed = _seed;
|
||||
|
||||
WardenDataServer packet = new WardenDataServer();
|
||||
packet.Data = EncryptData(Request.Write());
|
||||
_session.SendPacket(packet);
|
||||
}
|
||||
|
||||
public override void HandleHashResult(ByteBuffer buff)
|
||||
{
|
||||
// Verify key
|
||||
if (buff.ReadBytes(20) != WardenModuleWin.ClientKeySeedHash)
|
||||
{
|
||||
Log.outWarn(LogFilter.Warden, "{0} failed hash reply. Action: {0}", _session.GetPlayerInfo(), Penalty());
|
||||
return;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Warden, "Request hash reply: succeed");
|
||||
|
||||
// Change keys here
|
||||
_inputKey = WardenModuleWin.ClientKeySeed;
|
||||
_outputKey = WardenModuleWin.ServerKeySeed;
|
||||
|
||||
_inputCrypto.PrepareKey(_inputKey);
|
||||
_outputCrypto.PrepareKey(_outputKey);
|
||||
|
||||
_initialized = true;
|
||||
|
||||
_previousTimestamp = Time.GetMSTime();
|
||||
}
|
||||
|
||||
public override void RequestData()
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "Request data");
|
||||
|
||||
// If all checks were done, fill the todo list again
|
||||
if (_memChecksTodo.Empty())
|
||||
_memChecksTodo.AddRange(Global.WardenCheckMgr.MemChecksIdPool);
|
||||
|
||||
if (_otherChecksTodo.Empty())
|
||||
_otherChecksTodo.AddRange(Global.WardenCheckMgr.OtherChecksIdPool);
|
||||
|
||||
_serverTicks = Time.GetMSTime();
|
||||
|
||||
ushort id;
|
||||
WardenCheckType type;
|
||||
WardenCheck wd;
|
||||
_currentChecks.Clear();
|
||||
|
||||
// Build check request
|
||||
for (uint i = 0; i < WorldConfig.GetUIntValue(WorldCfg.WardenNumMemChecks); ++i)
|
||||
{
|
||||
// If todo list is done break loop (will be filled on next Update() run)
|
||||
if (_memChecksTodo.Empty())
|
||||
break;
|
||||
|
||||
// Get check id from the end and remove it from todo
|
||||
id = _memChecksTodo.Last();
|
||||
_memChecksTodo.Remove(id);
|
||||
|
||||
// Add the id to the list sent in this cycle
|
||||
_currentChecks.Add(id);
|
||||
}
|
||||
|
||||
ByteBuffer buffer = new ByteBuffer();
|
||||
buffer.WriteUInt8(WardenOpcodes.Smsg_CheatChecksRequest);
|
||||
|
||||
for (uint i = 0; i < WorldConfig.GetUIntValue(WorldCfg.WardenNumOtherChecks); ++i)
|
||||
{
|
||||
// If todo list is done break loop (will be filled on next Update() run)
|
||||
if (_otherChecksTodo.Empty())
|
||||
break;
|
||||
|
||||
// Get check id from the end and remove it from todo
|
||||
id = _otherChecksTodo.Last();
|
||||
_otherChecksTodo.Remove(id);
|
||||
|
||||
// Add the id to the list sent in this cycle
|
||||
_currentChecks.Add(id);
|
||||
|
||||
wd = Global.WardenCheckMgr.GetWardenDataById(id);
|
||||
|
||||
switch (wd.Type)
|
||||
{
|
||||
case WardenCheckType.MPQ:
|
||||
case WardenCheckType.LuaStr:
|
||||
case WardenCheckType.Driver:
|
||||
buffer.WriteUInt8(wd.Str.Length);
|
||||
buffer.WriteString(wd.Str);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
byte xorByte = _inputKey[0];
|
||||
|
||||
// Add TIMING_CHECK
|
||||
buffer.WriteUInt8(0x00);
|
||||
buffer.WriteUInt8((int)WardenCheckType.Timing ^ xorByte);
|
||||
|
||||
byte index = 1;
|
||||
|
||||
foreach (var checkId in _currentChecks)
|
||||
{
|
||||
wd = Global.WardenCheckMgr.GetWardenDataById(checkId);
|
||||
|
||||
type = wd.Type;
|
||||
buffer.WriteUInt8((int)type ^ xorByte);
|
||||
switch (type)
|
||||
{
|
||||
case WardenCheckType.Memory:
|
||||
{
|
||||
buffer.WriteUInt8(0x00);
|
||||
buffer.WriteUInt32(wd.Address);
|
||||
buffer.WriteUInt8(wd.Length);
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.PageA:
|
||||
case WardenCheckType.PageB:
|
||||
{
|
||||
buffer.WriteBytes(wd.Data.ToByteArray());
|
||||
buffer.WriteUInt32(wd.Address);
|
||||
buffer.WriteUInt8(wd.Length);
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.MPQ:
|
||||
case WardenCheckType.LuaStr:
|
||||
{
|
||||
buffer.WriteUInt8(index++);
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.Driver:
|
||||
{
|
||||
buffer.WriteBytes(wd.Data.ToByteArray());
|
||||
buffer.WriteUInt8(index++);
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.Module:
|
||||
{
|
||||
uint seed = RandomHelper.Rand32();
|
||||
buffer.WriteUInt32(seed);
|
||||
HmacHash hmac = new HmacHash(BitConverter.GetBytes(seed));
|
||||
hmac.Finish(wd.Str);
|
||||
buffer.WriteBytes(hmac.Digest);
|
||||
break;
|
||||
}
|
||||
/*case PROC_CHECK:
|
||||
{
|
||||
buff.append(wd.i.AsByteArray(0, false).get(), wd.i.GetNumBytes());
|
||||
buff << uint8(index++);
|
||||
buff << uint8(index++);
|
||||
buff << uint32(wd.Address);
|
||||
buff << uint8(wd.Length);
|
||||
break;
|
||||
}*/
|
||||
default:
|
||||
break; // Should never happen
|
||||
}
|
||||
}
|
||||
buffer.WriteUInt8(xorByte);
|
||||
|
||||
WardenDataServer packet = new WardenDataServer();
|
||||
packet.Data = EncryptData(buffer.GetData());
|
||||
_session.SendPacket(packet);
|
||||
|
||||
_dataSent = true;
|
||||
|
||||
string stream = "Sent check id's: ";
|
||||
foreach (var checkId in _currentChecks)
|
||||
stream += checkId + " ";
|
||||
|
||||
Log.outDebug(LogFilter.Warden, stream);
|
||||
}
|
||||
|
||||
public override void HandleData(ByteBuffer buff)
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "Handle data");
|
||||
|
||||
_dataSent = false;
|
||||
_clientResponseTimer = 0;
|
||||
|
||||
ushort Length = buff.ReadUInt16();
|
||||
uint Checksum = buff.ReadUInt32();
|
||||
|
||||
if (!IsValidCheckSum(Checksum, buff.GetData(), Length))
|
||||
{
|
||||
Log.outWarn(LogFilter.Warden, "{0} failed checksum. Action: {1}", _session.GetPlayerInfo(), Penalty());
|
||||
return;
|
||||
}
|
||||
|
||||
// TIMING_CHECK
|
||||
{
|
||||
byte result = buff.ReadUInt8();
|
||||
/// @todo test it.
|
||||
if (result == 0x00)
|
||||
{
|
||||
Log.outWarn(LogFilter.Warden, "{0} failed timing check. Action: {1}", _session.GetPlayerInfo(), Penalty());
|
||||
return;
|
||||
}
|
||||
|
||||
uint newClientTicks = buff.ReadUInt32();
|
||||
|
||||
uint ticksNow = Time.GetMSTime();
|
||||
uint ourTicks = newClientTicks + (ticksNow - _serverTicks);
|
||||
|
||||
Log.outDebug(LogFilter.Warden, "ServerTicks {0}", ticksNow); // Now
|
||||
Log.outDebug(LogFilter.Warden, "RequestTicks {0}", _serverTicks); // At request
|
||||
Log.outDebug(LogFilter.Warden, "Ticks {0}", newClientTicks); // At response
|
||||
Log.outDebug(LogFilter.Warden, "Ticks diff {0}", ourTicks - newClientTicks);
|
||||
}
|
||||
|
||||
BigInteger rs;
|
||||
WardenCheck rd;
|
||||
WardenCheckType type;
|
||||
ushort checkFailed = 0;
|
||||
|
||||
foreach (var id in _currentChecks)
|
||||
{
|
||||
rd = Global.WardenCheckMgr.GetWardenDataById(id);
|
||||
rs = Global.WardenCheckMgr.GetWardenResultById(id);
|
||||
|
||||
type = rd.Type;
|
||||
switch (type)
|
||||
{
|
||||
case WardenCheckType.Memory:
|
||||
{
|
||||
byte Mem_Result = buff.ReadUInt8();
|
||||
|
||||
if (Mem_Result != 0)
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK not 0x00, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
checkFailed = id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (buff.ReadBytes(rd.Length).Compare(rs.ToByteArray()))
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK fail CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
checkFailed = id;
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MEM_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.PageA:
|
||||
case WardenCheckType.PageB:
|
||||
case WardenCheckType.Driver:
|
||||
case WardenCheckType.Module:
|
||||
{
|
||||
byte value = 0xE9;
|
||||
if (buff.ReadUInt8() != value)
|
||||
{
|
||||
if (type == WardenCheckType.PageA || type == WardenCheckType.PageB)
|
||||
Log.outDebug(LogFilter.Warden, "RESULT PAGE_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
if (type == WardenCheckType.Module)
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MODULE_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
if (type == WardenCheckType.Driver)
|
||||
Log.outDebug(LogFilter.Warden, "RESULT DRIVER_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
checkFailed = id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == WardenCheckType.PageA || type == WardenCheckType.PageB)
|
||||
Log.outDebug(LogFilter.Warden, "RESULT PAGE_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
else if (type == WardenCheckType.Module)
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MODULE_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
else if (type == WardenCheckType.Driver)
|
||||
Log.outDebug(LogFilter.Warden, "RESULT DRIVER_CHECK passed CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.LuaStr:
|
||||
{
|
||||
byte Lua_Result = buff.ReadUInt8();
|
||||
|
||||
if (Lua_Result != 0)
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "RESULT LUA_STR_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
checkFailed = id;
|
||||
continue;
|
||||
}
|
||||
|
||||
byte luaStrLen = buff.ReadUInt8();
|
||||
if (luaStrLen != 0)
|
||||
Log.outDebug(LogFilter.Warden, "Lua string: {0}", buff.ReadString(luaStrLen));
|
||||
|
||||
Log.outDebug(LogFilter.Warden, "RESULT LUA_STR_CHECK passed, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
break;
|
||||
}
|
||||
case WardenCheckType.MPQ:
|
||||
{
|
||||
byte Mpq_Result = buff.ReadUInt8();
|
||||
|
||||
if (Mpq_Result != 0)
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK not 0x00 account id {0}", _session.GetAccountId());
|
||||
checkFailed = id;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!buff.ReadBytes(20).Compare(rs.ToByteArray())) // SHA1
|
||||
{
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK fail, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
checkFailed = id;
|
||||
continue;
|
||||
}
|
||||
|
||||
Log.outDebug(LogFilter.Warden, "RESULT MPQ_CHECK passed, CheckId {0} account Id {1}", id, _session.GetAccountId());
|
||||
break;
|
||||
}
|
||||
default: // Should never happen
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (checkFailed > 0)
|
||||
{
|
||||
WardenCheck check = Global.WardenCheckMgr.GetWardenDataById(checkFailed);
|
||||
Log.outWarn(LogFilter.Warden, "{0} failed Warden check {1}. Action: {2}", _session.GetPlayerInfo(), checkFailed, Penalty(check));
|
||||
}
|
||||
|
||||
// Set hold off timer, minimum timer should at least be 1 second
|
||||
uint holdOff = WorldConfig.GetUIntValue(WorldCfg.WardenClientCheckHoldoff);
|
||||
_checkTimer = (holdOff < 1 ? 1 : holdOff) * Time.InMilliseconds;
|
||||
}
|
||||
|
||||
uint _serverTicks;
|
||||
List<ushort> _otherChecksTodo = new List<ushort>();
|
||||
List<ushort> _memChecksTodo = new List<ushort>();
|
||||
List<ushort> _currentChecks = new List<ushort>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright (C) 2012-2017 CypherCore <http://github.com/CypherCore>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
using Framework.Constants;
|
||||
using Framework.Database;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace Game
|
||||
{
|
||||
public class WargenCheckManager : Singleton<WargenCheckManager>
|
||||
{
|
||||
WargenCheckManager() { }
|
||||
|
||||
public void LoadWardenChecks()
|
||||
{
|
||||
// Check if Warden is enabled by config before loading anything
|
||||
if (!WorldConfig.GetBoolValue(WorldCfg.WardenEnabled))
|
||||
{
|
||||
Log.outInfo(LogFilter.Warden, "Warden disabled, loading checks skipped.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 0 1 2 3 4 5 6 7
|
||||
SQLResult result = DB.World.Query("SELECT id, type, data, result, address, length, str, comment FROM warden_checks ORDER BY id ASC");
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Warden checks. DB table `warden_checks` is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
do
|
||||
{
|
||||
ushort id = result.Read<ushort>(0);
|
||||
WardenCheckType checkType = (WardenCheckType)result.Read<byte>(1);
|
||||
string data = result.Read<string>(2);
|
||||
string checkResult = result.Read<string>(3);
|
||||
uint address = result.Read<uint>(4);
|
||||
byte length = result.Read<byte>(5);
|
||||
string str = result.Read<string>(6);
|
||||
string comment = result.Read<string>(7);
|
||||
|
||||
WardenCheck wardenCheck = new WardenCheck();
|
||||
wardenCheck.Type = checkType;
|
||||
wardenCheck.CheckId = id;
|
||||
|
||||
// Initialize action with default action from config
|
||||
wardenCheck.Action = (WardenActions)WorldConfig.GetIntValue(WorldCfg.WardenClientFailAction);
|
||||
|
||||
if (checkType == WardenCheckType.PageA || checkType == WardenCheckType.PageB || checkType == WardenCheckType.Driver)
|
||||
{
|
||||
wardenCheck.Data = new BigInteger(data.ToByteArray());
|
||||
int len = data.Length / 2;
|
||||
|
||||
if (wardenCheck.Data.ToByteArray().Length < len)
|
||||
{
|
||||
byte[] temp = wardenCheck.Data.ToByteArray();
|
||||
Array.Reverse(temp);
|
||||
wardenCheck.Data = new BigInteger(temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkType == WardenCheckType.Memory || checkType == WardenCheckType.Module)
|
||||
MemChecksIdPool.Add(id);
|
||||
else
|
||||
OtherChecksIdPool.Add(id);
|
||||
|
||||
if (checkType == WardenCheckType.Memory || checkType == WardenCheckType.PageA || checkType == WardenCheckType.PageB || checkType == WardenCheckType.Proc)
|
||||
{
|
||||
wardenCheck.Address = address;
|
||||
wardenCheck.Length = length;
|
||||
}
|
||||
|
||||
// PROC_CHECK support missing
|
||||
if (checkType == WardenCheckType.Memory || checkType == WardenCheckType.MPQ || checkType == WardenCheckType.LuaStr || checkType == WardenCheckType.Driver || checkType == WardenCheckType.Module)
|
||||
wardenCheck.Str = str;
|
||||
|
||||
CheckStore[id] = wardenCheck;
|
||||
|
||||
if (checkType == WardenCheckType.MPQ || checkType == WardenCheckType.Memory)
|
||||
{
|
||||
BigInteger Result = new BigInteger(checkResult.ToByteArray());
|
||||
int len = checkResult.Length / 2;
|
||||
if (Result.ToByteArray().Length < len)
|
||||
{
|
||||
byte[] temp = Result.ToByteArray();
|
||||
Array.Reverse(temp);
|
||||
Result = new BigInteger(temp);
|
||||
}
|
||||
CheckResultStore[id] = Result;
|
||||
}
|
||||
|
||||
if (comment.IsEmpty())
|
||||
wardenCheck.Comment = "Undocumented Check";
|
||||
else
|
||||
wardenCheck.Comment = comment;
|
||||
|
||||
++count;
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} warden checks.", count);
|
||||
}
|
||||
|
||||
public void LoadWardenOverrides()
|
||||
{
|
||||
// Check if Warden is enabled by config before loading anything
|
||||
if (!WorldConfig.GetBoolValue(WorldCfg.WardenEnabled))
|
||||
{
|
||||
Log.outInfo(LogFilter.Warden, "Warden disabled, loading check overrides skipped.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 0 1
|
||||
SQLResult result = DB.Characters.Query("SELECT wardenId, action FROM warden_action");
|
||||
if (result.IsEmpty())
|
||||
{
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded 0 Warden action overrides. DB table `warden_action` is empty!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint count = 0;
|
||||
do
|
||||
{
|
||||
ushort checkId = result.Read<ushort>(0);
|
||||
WardenActions action = (WardenActions)result.Read<byte>(1);
|
||||
|
||||
// Check if action value is in range (0-2, see WardenActions enum)
|
||||
if (action > WardenActions.Ban)
|
||||
Log.outError(LogFilter.Warden, "Warden check override action out of range (ID: {0}, action: {1})", checkId, action);
|
||||
// Check if check actually exists before accessing the CheckStore vector
|
||||
else if (checkId > CheckStore.Count)
|
||||
Log.outError(LogFilter.Warden, "Warden check action override for non-existing check (ID: {0}, action: {1}), skipped", checkId, action);
|
||||
else
|
||||
{
|
||||
CheckStore[checkId].Action = action;
|
||||
++count;
|
||||
}
|
||||
}
|
||||
while (result.NextRow());
|
||||
|
||||
Log.outInfo(LogFilter.ServerLoading, "Loaded {0} warden action overrides.", count);
|
||||
}
|
||||
|
||||
public WardenCheck GetWardenDataById(ushort Id)
|
||||
{
|
||||
if (Id < CheckStore.Count)
|
||||
return CheckStore[Id];
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public BigInteger GetWardenResultById(ushort Id)
|
||||
{
|
||||
return CheckResultStore.LookupByKey(Id);
|
||||
}
|
||||
|
||||
public List<ushort> MemChecksIdPool = new List<ushort>();
|
||||
public List<ushort> OtherChecksIdPool = new List<ushort>();
|
||||
List<WardenCheck> CheckStore = new List<WardenCheck>();
|
||||
Dictionary<uint, BigInteger> CheckResultStore = new Dictionary<uint, BigInteger>();
|
||||
}
|
||||
|
||||
public enum WardenActions
|
||||
{
|
||||
Log,
|
||||
Kick,
|
||||
Ban
|
||||
}
|
||||
|
||||
public enum WardenCheckType
|
||||
{
|
||||
Memory = 0xF3, // 243: byte moduleNameIndex + uint Offset + byte Len (check to ensure memory isn't modified)
|
||||
PageA = 0xB2, // 178: uint Seed + byte[20] SHA1 + uint Addr + byte Len (scans all pages for specified hash)
|
||||
PageB = 0xBF, // 191: uint Seed + byte[20] SHA1 + uint Addr + byte Len (scans only pages starts with MZ+PE headers for specified hash)
|
||||
MPQ = 0x98, // 152: byte fileNameIndex (check to ensure MPQ file isn't modified)
|
||||
LuaStr = 0x8B, // 139: byte luaNameIndex (check to ensure LUA string isn't used)
|
||||
Driver = 0x71, // 113: uint Seed + byte[20] SHA1 + byte driverNameIndex (check to ensure driver isn't loaded)
|
||||
Timing = 0x57, // 87: empty (check to ensure GetTickCount() isn't detoured)
|
||||
Proc = 0x7E, // 126: uint Seed + byte[20] SHA1 + byte moluleNameIndex + byte procNameIndex + uint Offset + byte Len (check to ensure proc isn't detoured)
|
||||
Module = 0xD9 // 217: uint Seed + byte[20] SHA1 (check to ensure module isn't injected)
|
||||
}
|
||||
|
||||
public class WardenCheck
|
||||
{
|
||||
public WardenCheckType Type;
|
||||
public BigInteger Data;
|
||||
public uint Address; // PROC_CHECK, MEM_CHECK, PAGE_CHECK
|
||||
public byte Length; // PROC_CHECK, MEM_CHECK, PAGE_CHECK
|
||||
public string Str; // LUA, MPQ, DRIVER
|
||||
public string Comment;
|
||||
public ushort CheckId;
|
||||
public WardenActions Action;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user