456 lines
19 KiB
C#
456 lines
19 KiB
C#
/*
|
|
* 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>();
|
|
}
|
|
}
|