diff --git a/.gitignore b/.gitignore index 263b422e..77011717 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ Bin/ Intermediate/ Research/ vcpkg_installed/ -.vs/ \ No newline at end of file +.vs/ +Temp/ +vcpkg/ \ No newline at end of file diff --git a/Source/Server/Core/Crypto/CWCCipher.cpp b/Source/Server/Core/Crypto/CWCCipher.cpp index f71ea31e..0cdd8ebb 100644 --- a/Source/Server/Core/Crypto/CWCCipher.cpp +++ b/Source/Server/Core/Crypto/CWCCipher.cpp @@ -4,6 +4,8 @@ #include "Core/Utils/Logging.h" #include "Core/Utils/Random.h" +#include "Core/Utils/Endian.h" +#include "Core/Utils/Strings.h" CWCCipher::CWCCipher(const std::vector& InKey) : Key(InKey) @@ -13,12 +15,11 @@ CWCCipher::CWCCipher(const std::vector& InKey) bool CWCCipher::Encrypt(const std::vector& Input, std::vector& Output) { - std::vector IV(12, 0); - std::vector Tag(17, 0); + std::vector IV(11, 0); + std::vector Tag(16, 0); std::vector Payload = Input; FillRandomBytes(IV); - IV[11] = '\0'; if (cwc_encrypt_message(IV.data(), 11, IV.data(), 11, (unsigned char*)Payload.data(), Payload.size(), Tag.data(), 16, &CwcContext) == RETURN_ERROR) { @@ -36,9 +37,9 @@ bool CWCCipher::Encrypt(const std::vector& Input, std::vector& bool CWCCipher::Decrypt(const std::vector& Input, std::vector& Output) { - std::vector IV(12); - std::vector Tag(17); - + std::vector IV(11); + std::vector Tag(16); + // Actually enough data for any data? if (Input.size() < 11 + 16 + 1) { diff --git a/Source/Server/Core/Crypto/CWCUDPCipher.cpp b/Source/Server/Core/Crypto/CWCUDPCipher.cpp new file mode 100644 index 00000000..d6971ff4 --- /dev/null +++ b/Source/Server/Core/Crypto/CWCUDPCipher.cpp @@ -0,0 +1,88 @@ +// Dark Souls 3 - Open Server + +#include "Core/Crypto/CWCUDPCipher.h" + +#include "Core/Utils/Logging.h" +#include "Core/Utils/Random.h" +#include "Core/Utils/Endian.h" +#include "Core/Utils/Strings.h" + +CWCUDPCipher::CWCUDPCipher(const std::vector& InKey, uint64_t InAuthToken) + : Key(InKey) + , AuthToken(InAuthToken) +{ + cwc_init_and_key(InKey.data(), InKey.size(), &CwcContext); + + // Auth token bytes to encode in the header are the reversed auth token. + uint8_t* InAuthTokenBytes = reinterpret_cast(&InAuthToken); + AuthTokenHeaderBytes.assign(InAuthTokenBytes, InAuthTokenBytes + 8); +} + +bool CWCUDPCipher::Encrypt(const std::vector& Input, std::vector& Output) +{ + std::vector IV(11, 0); + std::vector Tag(16, 0); + std::vector Payload = Input; + std::vector PacketType = { 0 }; + + FillRandomBytes(IV); + + // TODO: I have the distinct feeling this is different when replying as the packet type + // doesn't get sent when going server->client ... + std::vector Header; + Header.resize(20); + memcpy(Header.data(), IV.data(), 11); + memcpy(Header.data() + 11, AuthTokenHeaderBytes.data(), 8); + memcpy(Header.data() + 19, PacketType.data(), 1); + + if (cwc_encrypt_message(IV.data(), 11, Header.data(), Header.size(), (unsigned char*)Payload.data(), Payload.size(), Tag.data(), 16, &CwcContext) == RETURN_ERROR) + { + return false; + } + + Output.resize(Payload.size() + 11 + 16 + 1); + + memcpy(Output.data(), IV.data(), 11); + memcpy(Output.data() + 11, Tag.data(), 16); + memcpy(Output.data() + 11 + 16, PacketType.data(), 1); + memcpy(Output.data() + 11 + 16 + 1, Payload.data(), Payload.size()); + + Log("Encrypt: PayloadSize=%i IV=%s Tag=%s Header=%s", Payload.size(), BytesToHex(IV).c_str(), BytesToHex(Tag).c_str(), BytesToHex(Header).c_str()); + + return true; +} + +bool CWCUDPCipher::Decrypt(const std::vector& Input, std::vector& Output) +{ + std::vector IV(11); + std::vector Tag(16); + std::vector PacketType(1); + + // Actually enough data for any data? + if (Input.size() < 11 + 16 + 1 + 1) + { + return false; + } + + Output.resize(Input.size() - 11 - 16 - 1); + + memcpy(IV.data(), Input.data(), 11); + memcpy(Tag.data(), Input.data() + 11, 16); + memcpy(PacketType.data(), Input.data() + 11 + 16, 1); + memcpy(Output.data(), Input.data() + 11 + 16 + 1, Output.size()); + + std::vector Header; + Header.resize(20); + memcpy(Header.data(), IV.data(), 11); + memcpy(Header.data() + 11, AuthTokenHeaderBytes.data(), 8); + memcpy(Header.data() + 19, PacketType.data(), 1); + + Log("Decrypt: PayloadSize=%i IV=%s Tag=%s Header=%s", Output.size(), BytesToHex(IV).c_str(), BytesToHex(Tag).c_str(), BytesToHex(Header).c_str()); + + if (cwc_decrypt_message(IV.data(), 11, Header.data(), Header.size(), (unsigned char*)Output.data(), Output.size(), Tag.data(), 16, &CwcContext) == RETURN_ERROR) + { + return false; + } + + return true; +} diff --git a/Source/Server/Core/Crypto/CWCUDPCipher.h b/Source/Server/Core/Crypto/CWCUDPCipher.h new file mode 100644 index 00000000..32c9b739 --- /dev/null +++ b/Source/Server/Core/Crypto/CWCUDPCipher.h @@ -0,0 +1,29 @@ +// Dark Souls 3 - Open Server + +#pragma once + +#include "Core/Crypto/Cipher.h" + +#include "cwc.h" + +#include + +class CWCUDPCipher + : public Cipher +{ +public: + + CWCUDPCipher(const std::vector& key, uint64_t AuthToken); + + bool Encrypt(const std::vector& input, std::vector& Output) override; + bool Decrypt(const std::vector& input, std::vector& Output) override; + +private: + std::vector Key; + + cwc_ctx CwcContext; + + uint64_t AuthToken; + std::vector AuthTokenHeaderBytes; + +}; diff --git a/Source/Server/Core/Crypto/RSACipher.cpp b/Source/Server/Core/Crypto/RSACipher.cpp index 65298aa8..d9476664 100644 --- a/Source/Server/Core/Crypto/RSACipher.cpp +++ b/Source/Server/Core/Crypto/RSACipher.cpp @@ -30,7 +30,7 @@ bool RSACipher::Encrypt(const std::vector& Input, std::vector& } int EncryptedLength = RSA_private_encrypt((int)Input.size(), Input.data(), Output.data(), RsaInstance, OpenSSLPaddingMode); - if (!EncryptedLength) + if (EncryptedLength < 0) { std::vector buffer; buffer.resize(1024); @@ -66,7 +66,7 @@ bool RSACipher::Decrypt(const std::vector& Input, std::vector& } int DecryptedLength = RSA_private_decrypt((int)Input.size(), Input.data(), Output.data(), RsaInstance, OpenSSLPaddingMode); - if (!DecryptedLength) + if (DecryptedLength < 0) { std::vector buffer; buffer.resize(1024); diff --git a/Source/Server/Core/Utils/Strings.cpp b/Source/Server/Core/Utils/Strings.cpp new file mode 100644 index 00000000..03b9fc52 --- /dev/null +++ b/Source/Server/Core/Utils/Strings.cpp @@ -0,0 +1,18 @@ +// Dark Souls 3 - Open Server + +#include "Core/Utils/Strings.h" + +#include +#include + +std::string BytesToHex(const std::vector& Bytes) +{ + std::stringstream ss; + for (uint8_t Value : Bytes) + { + ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (int)Value; + //ss << " "; + } + + return ss.str(); +} \ No newline at end of file diff --git a/Source/Server/Core/Utils/Strings.h b/Source/Server/Core/Utils/Strings.h new file mode 100644 index 00000000..9379d9f4 --- /dev/null +++ b/Source/Server/Core/Utils/Strings.h @@ -0,0 +1,8 @@ +// Dark Souls 3 - Open Server + +#pragma once + +#include +#include + +std::string BytesToHex(const std::vector& Bytes); \ No newline at end of file diff --git a/Source/Server/Server.vcxproj b/Source/Server/Server.vcxproj index ba7e8cea..87facd1e 100644 --- a/Source/Server/Server.vcxproj +++ b/Source/Server/Server.vcxproj @@ -91,6 +91,7 @@ + @@ -101,6 +102,7 @@ + @@ -121,6 +123,7 @@ + @@ -128,6 +131,7 @@ + diff --git a/Source/Server/Server.vcxproj.filters b/Source/Server/Server.vcxproj.filters index fa137c3a..276f2cff 100644 --- a/Source/Server/Server.vcxproj.filters +++ b/Source/Server/Server.vcxproj.filters @@ -135,6 +135,12 @@ Server\Streams + + Core\Utils + + + Core\Crypto + @@ -201,6 +207,12 @@ Server\Streams + + Core\Utils + + + Core\Crypto + diff --git a/Source/Server/Server/AuthService/AuthClient.cpp b/Source/Server/Server/AuthService/AuthClient.cpp index d09d544e..9f5ad10c 100644 --- a/Source/Server/Server/AuthService/AuthClient.cpp +++ b/Source/Server/Server/AuthService/AuthClient.cpp @@ -12,6 +12,7 @@ #include "Core/Utils/Logging.h" #include "Core/Utils/Random.h" +#include "Core/Utils/Strings.h" #include "Core/Network/NetConnection.h" #include "Config/BuildConfig.h" @@ -75,14 +76,14 @@ bool AuthClient::Poll() return true; } - Log("[%s] Recieved handshake request.", GetName().c_str()); - // Covert the CWC key to a byte buffer. std::string string = Request.aes_cwc_key(); uint8_t* string_ptr = reinterpret_cast(string.data()); CwcKey.assign(string_ptr, string_ptr + string.length()); + Log("[%s] Recieved handshake request, Key=%s", GetName().c_str(), BytesToHex(CwcKey).c_str()); + // Disable cipher while we send this "hardcoded" message. MessageStream->SetCipher(nullptr, nullptr); @@ -157,18 +158,20 @@ bool AuthClient::Poll() return true; } - Log("[%s] Recieved key exchange bytes.", GetName().c_str()); + // This is our authentication key for the game session. + Frpg2Message KeyResponse; + KeyResponse.Payload.resize(16); + // Lower 8 bytes are what the client sent, upper 8 bytes are random data we fill in. + FillRandomBytes(KeyResponse.Payload); + memcpy(KeyResponse.Payload.data(), Message.Payload.data(), 8); - // Response is the 8 bytes the client sent us plus another random 8 bytes? - // This I think is our authentication key for the game session.. - Frpg2Message Response; - Response.Payload.resize(16); - FillRandomBytes(Response.Payload); - memcpy(Response.Payload.data(), Message.Payload.data(), 8); + Log("[%s] Recieved key exchange bytes, game session key = %s", GetName().c_str(), BytesToHex(KeyResponse.Payload).c_str()); - GameCwcKey = Response.Payload; + // Note: Supposedly the "normal" cwc key should work for the game session - it doesn't seem to though. + // This key however does output plaintext, but with a message tag failure. Huuum + GameCwcKey = KeyResponse.Payload; - if (!MessageStream->Send(Response, Message.Header.request_index)) + if (!MessageStream->Send(KeyResponse, Message.Header.request_index)) { Warning("[%s] Disconnecting client as failed to send key exchange.", GetName().c_str()); return true; diff --git a/Source/Server/Server/GameService/GameService.cpp b/Source/Server/Server/GameService/GameService.cpp index 6230bb96..735592b5 100644 --- a/Source/Server/Server/GameService/GameService.cpp +++ b/Source/Server/Server/GameService/GameService.cpp @@ -8,6 +8,7 @@ #include "Core/Network/NetConnection.h" #include "Core/Network/NetConnectionUDP.h" #include "Core/Utils/Logging.h" +#include "Core/Utils/Strings.h" #include "Config/BuildConfig.h" #include "Config/RuntimeConfig.h" @@ -110,6 +111,9 @@ void GameService::HandleClientConnection(std::shared_ptr ClientCo GameClientAuthenticationState& AuthState = (*AuthStateIter).second; + + Log("[%s] Client will use cipher key %s", ClientConnection->GetName().c_str(), BytesToHex(AuthState.CwcKey).c_str()); + std::shared_ptr Client = std::make_shared(this, ClientConnection, AuthState.CwcKey, AuthState.AuthToken); Clients.push_back(Client); } diff --git a/Source/Server/Server/Streams/Frpg2UdpPacketStream.cpp b/Source/Server/Server/Streams/Frpg2UdpPacketStream.cpp index 5ad3a2ac..1ed96274 100644 --- a/Source/Server/Server/Streams/Frpg2UdpPacketStream.cpp +++ b/Source/Server/Server/Streams/Frpg2UdpPacketStream.cpp @@ -7,15 +7,17 @@ #include "Core/Utils/Logging.h" -#include "Core/Crypto/CWCCipher.h" +#include "Core/Crypto/CWCUDPCipher.h" Frpg2UdpPacketStream::Frpg2UdpPacketStream(std::shared_ptr InConnection, const std::vector& InCwcKey, uint64_t InAuthToken) : Connection(InConnection) , CwcKey(InCwcKey) , AuthToken(InAuthToken) { - EncryptionCipher = std::make_shared(InCwcKey); - DecryptionCipher = std::make_shared(InCwcKey); + // Udp cwc cipher seems to leave one byte padding between the iv/tag and the payload. + // TODO: This is a hacky fix, do this a better way. + EncryptionCipher = std::make_shared(InCwcKey, AuthToken); + DecryptionCipher = std::make_shared(InCwcKey, AuthToken); RecieveBuffer.resize(64 * 1024); } @@ -62,7 +64,9 @@ bool Frpg2UdpPacketStream::Pump() if (DecryptionCipher) { + // TODO: Hum we are getting -something- that looks like plaintext, but tags are wrong? std::vector EncryptedBuffer = Packet.Payload; + if (!DecryptionCipher->Decrypt(EncryptedBuffer, Packet.Payload)) { Warning("[%s] Failed to decrypt packet payload.", Connection->GetName().c_str()); diff --git a/Source/ThirdParty/aes_modes/aes_modes.vcxproj b/Source/ThirdParty/aes_modes/aes_modes.vcxproj index 49e07b32..f700e75e 100644 --- a/Source/ThirdParty/aes_modes/aes_modes.vcxproj +++ b/Source/ThirdParty/aes_modes/aes_modes.vcxproj @@ -98,7 +98,7 @@ Level3 true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -112,7 +112,7 @@ true true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -126,7 +126,7 @@ Level3 true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -140,7 +140,7 @@ true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true diff --git a/Source/ThirdParty/aes_modes/cwc.c b/Source/ThirdParty/aes_modes/cwc.c index 91d776f2..0abc3e5c 100644 --- a/Source/ThirdParty/aes_modes/cwc.c +++ b/Source/ThirdParty/aes_modes/cwc.c @@ -233,9 +233,18 @@ void do_cwc(uint32_t in[], cwc_ctx ctx[1]) /* in truncate to zero mode */ set_FPU; - data[2] = bswap_32(in[2]); - data[1] = bswap_32(in[1]); - data[0] = bswap_32(in[0]); + if (PLATFORM_BYTE_ORDER == IS_BIG_ENDIAN) + { + data[2] = bswap_32(in[2]); + data[1] = bswap_32(in[1]); + data[0] = bswap_32(in[0]); + } + else + { + data[2] = in[2]; + data[1] = in[1]; + data[0] = in[0]; + } /* split input data into 24 bit double values */ a[0] = data[2] & 0x00ffffff; @@ -334,9 +343,18 @@ void do_cwc(uint32_t in[], cwc_ctx ctx[1]) /* in truncate to zero mode */ set_FPU; - data[2] = bswap_32(in[2]); - data[1] = bswap_32(in[1]); - data[0] = bswap_32(in[0]); + if (PLATFORM_BYTE_ORDER == IS_BIG_ENDIAN) + { + data[2] = bswap_32(in[2]); + data[1] = bswap_32(in[1]); + data[0] = bswap_32(in[0]); + } + else + { + data[2] = in[2]; + data[1] = in[1]; + data[0] = in[0]; + } /* split input data into 24 bit double values */ a[0] = data[2] & 0x00ffffff; @@ -961,6 +979,24 @@ ret_type cwc_decrypt_message( /* decrypt an entire message */ cwc_auth_header(hdr, hdr_len, ctx); cwc_decrypt(msg, msg_len, ctx); rr = cwc_compute_tag(local_tag, tag_len, ctx); + /* + printf("expected_tag="); + for (int i = 0; i < CBLK_LEN; i++) + { + printf("%02x", local_tag[i]); + } + printf("\n"); + printf("input_tag ="); + for (int i = 0; i < tag_len; i++) + { + printf("%02x", tag[i]); + } + printf("\n"); + printf("hdr_cnt=%i\n", ctx->hdr_cnt); + printf("txt_acnt=%i\n", ctx->txt_acnt); + printf("txt_ccnt=%i\n", ctx->txt_ccnt); + printf("\n"); + */ return (rr != RETURN_GOOD || memcmp(tag, local_tag, tag_len)) ? RETURN_ERROR : RETURN_GOOD; } diff --git a/Tools/cwc-tag-logger/cwc-tag-logger.ct b/Tools/cwc-tag-logger/cwc-tag-logger.ct new file mode 100644 index 00000000..4755bdb5 --- /dev/null +++ b/Tools/cwc-tag-logger/cwc-tag-logger.ct @@ -0,0 +1,119 @@ + + + + + 0 + "Log CWC tags to disk" + + Auto Assembler Script + [ENABLE] +{$lua} +if syntaxcheck then return end + +local output_folder_dialog = createSelectDirectoryDialog() +output_folder_dialog.execute() + +local output_folder = output_folder_dialog.FileName +local cwc_compute_tag_start_hook = 0x1422adb50 +local cwc_compute_tag_end_hook = 0x1422add97 +local cwc_auth_header_hook = 0x1422ad9c0 +local message_counter = 0 +local current_tag_addr = 0 +local current_ctx_addr = 0 +local auth_header_counter = 0 + +function string.fromhex(str) + return (str:gsub('..', function (cc) + return string.char(tonumber(cc, 16)) + end)) +end + +function string.tohex(str) + return (str:gsub('.', function (c) + return string.format('%02X', string.byte(c)) + end)) +end + +function on_cwc_compute_tag_start() + current_tag_addr = RCX + current_ctx_addr = R8 + print("Called: on_cwc_compute_tag_start") + debug_continueFromBreakpoint(co_run) +end + +function on_cwc_compute_tag_end() + print("Called: on_cwc_compute_tag_end") + + local hdr_cnt = readInteger(current_ctx_addr + 340) + local txt_ccnt = readInteger(current_ctx_addr + 344) + local txt_acnt = readInteger(current_ctx_addr + 348) + + local tag_bytes = readBytes(current_tag_addr, 16, true) + local tag_string = string.char(unpack(tag_bytes)):tohex() + + local name = string.format("cwc_tag_%06d.bin", message_counter) + print("Writing new tag to: " .. name); + + local file, err = io.open(output_folder .. "/" .. name, "wb") + + if (err) then + print(err) + return + end + + file:write("hdr_cnt:" .. hdr_cnt .. "\n") + file:write("txt_ccnt:" .. txt_ccnt .. "\n") + file:write("txt_acnt:" .. txt_acnt .. "\n") + + file:write("Tag:") + file:write(tag_string) + file:write("\n") + + file:close() + + message_counter = message_counter + 1 + auth_header_counter = 0 + debug_continueFromBreakpoint(co_run) +end + +function on_cwc_auth_header() + print("Called: on_cwc_auth_header") + + local hdr_len = EDX + local hdr_bytes = readBytes(RCX, hdr_len, true) + local hdr_string = string.char(unpack(hdr_bytes)):tohex() + + local name = string.format("cwc_auth_header_%06d_%06d.bin", message_counter, auth_header_counter) + print("Writing new auth header to: " .. name); + + local file, err = io.open(output_folder .. "/" .. name, "wb") + + if (err) then + print(err) + return + end + + file:write("hdr_len:" .. hdr_len .. "\n") + file:write("hdr:" .. hdr_string .. "\n") + + file:close() + + auth_header_counter = auth_header_counter + 1 + debug_continueFromBreakpoint(co_run) +end + +debug_setBreakpoint(cwc_compute_tag_start_hook, "on_cwc_compute_tag_start") +debug_setBreakpoint(cwc_compute_tag_end_hook, "on_cwc_compute_tag_end") +debug_setBreakpoint(cwc_auth_header_hook, "on_cwc_auth_header") + +[DISABLE] +{$lua} +if syntaxcheck then return end + +debug_removeBreakpoint(cwc_compute_tag_start_hook) +debug_removeBreakpoint(cwc_compute_tag_end_hook) +debug_removeBreakpoint(cwc_auth_header_hook) + + + +