From 535ce04a47b919e1e283154917589e926d8245e7 Mon Sep 17 00:00:00 2001 From: JustArchi Date: Sat, 20 Jul 2019 00:22:50 +0200 Subject: [PATCH] Implement enhanced support for SteamParental - Detect if SteamParental is active - Verify if the code supplied by user is valid - If the code is not valid or not supplied, generate it automatically (through local bruteforcing, pending further speed enhancements) It's really sad seeing how this is possible, Valve seriously, you couldn't dedicate one single request in the backend to verify that? You could easily implement rate-limiting on invalid attempts and make parental more secure, but with the current implementation it's a security joke. --- ArchiSteamFarm/ArchiCryptoHelper.cs | 82 +++++++++++++++++++++++++++ ArchiSteamFarm/ArchiHandler.cs | 83 ++++++++++++++++++++++++++++ ArchiSteamFarm/ArchiSteamFarm.csproj | 1 + ArchiSteamFarm/Bot.cs | 22 +++++--- 4 files changed, 181 insertions(+), 7 deletions(-) diff --git a/ArchiSteamFarm/ArchiCryptoHelper.cs b/ArchiSteamFarm/ArchiCryptoHelper.cs index b0712f198..611b5266a 100644 --- a/ArchiSteamFarm/ArchiCryptoHelper.cs +++ b/ArchiSteamFarm/ArchiCryptoHelper.cs @@ -20,15 +20,27 @@ // limitations under the License. using System; +using System.Linq; using System.Security.Cryptography; using System.Text; using ArchiSteamFarm.Localization; +using CryptSharp.Utility; using SteamKit2; namespace ArchiSteamFarm { public static class ArchiCryptoHelper { private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm)); + internal static string BruteforceSteamParentalCode(byte[] passwordHash, byte[] salt, bool derivedKey = true) { + if ((passwordHash == null) || (salt == null)) { + ASF.ArchiLogger.LogNullError(nameof(passwordHash) + " || " + nameof(salt)); + + return null; + } + + return derivedKey ? BruteforceSteamParentalCodeDerived(passwordHash, salt) : BruteforceSteamParentalCodePbkdf2(passwordHash, salt); + } + internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) { if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod) || string.IsNullOrEmpty(encrypted)) { ASF.ArchiLogger.LogNullError(nameof(cryptoMethod) + " || " + nameof(encrypted)); @@ -81,6 +93,76 @@ namespace ArchiSteamFarm { EncryptionKey = Encoding.UTF8.GetBytes(key); } + private static string BruteforceSteamParentalCodeDerived(byte[] passwordHash, byte[] salt) { + if ((passwordHash == null) || (salt == null)) { + ASF.ArchiLogger.LogNullError(nameof(passwordHash) + " || " + nameof(salt)); + + return null; + } + + byte[] password = new byte[4]; + + for (char a = '0'; a <= '9'; a++) { + password[0] = (byte) a; + + for (char b = '0'; b <= '9'; b++) { + password[1] = (byte) b; + + for (char c = '0'; c <= '9'; c++) { + password[2] = (byte) c; + + for (char d = '0'; d <= '9'; d++) { + password[3] = (byte) d; + + byte[] passwordHashTry = SCrypt.ComputeDerivedKey(password, salt, 8192, 8, 1, null, passwordHash.Length); + + if (passwordHashTry.SequenceEqual(passwordHash)) { + return Encoding.UTF8.GetString(password); + } + } + } + } + } + + return null; + } + + private static string BruteforceSteamParentalCodePbkdf2(byte[] passwordHash, byte[] salt) { + if ((passwordHash == null) || (salt == null)) { + ASF.ArchiLogger.LogNullError(nameof(passwordHash) + " || " + nameof(salt)); + + return null; + } + + byte[] password = new byte[4]; + + using (KeyedHashAlgorithm hmacAlgorithm = KeyedHashAlgorithm.Create()) { + for (char a = '0'; a <= '9'; a++) { + password[0] = (byte) a; + + for (char b = '0'; b <= '9'; b++) { + password[1] = (byte) b; + + for (char c = '0'; c <= '9'; c++) { + password[2] = (byte) c; + + for (char d = '0'; d <= '9'; d++) { + password[3] = (byte) d; + + byte[] passwordHashTry = Pbkdf2.ComputeDerivedKey(hmacAlgorithm, salt, 10000, passwordHash.Length); + + if (passwordHashTry.SequenceEqual(passwordHash)) { + return Encoding.UTF8.GetString(password); + } + } + } + } + } + } + + return null; + } + private static string DecryptAES(string encrypted) { if (string.IsNullOrEmpty(encrypted)) { ASF.ArchiLogger.LogNullError(nameof(encrypted)); diff --git a/ArchiSteamFarm/ArchiHandler.cs b/ArchiSteamFarm/ArchiHandler.cs index 8aaa53842..8016ec717 100644 --- a/ArchiSteamFarm/ArchiHandler.cs +++ b/ArchiSteamFarm/ArchiHandler.cs @@ -29,6 +29,7 @@ using System.Threading.Tasks; using ArchiSteamFarm.CMsgs; using ArchiSteamFarm.Localization; using ArchiSteamFarm.NLog; +using CryptSharp.Utility; using JetBrains.Annotations; using SteamKit2; using SteamKit2.Internal; @@ -43,6 +44,7 @@ namespace ArchiSteamFarm { private readonly SteamUnifiedMessages.UnifiedService UnifiedClanChatRoomsService; private readonly SteamUnifiedMessages.UnifiedService UnifiedEconService; private readonly SteamUnifiedMessages.UnifiedService UnifiedFriendMessagesService; + private readonly SteamUnifiedMessages.UnifiedService UnifiedParentalService; private readonly SteamUnifiedMessages.UnifiedService UnifiedPlayerService; internal DateTime LastPacketReceived { get; private set; } @@ -57,6 +59,7 @@ namespace ArchiSteamFarm { UnifiedClanChatRoomsService = steamUnifiedMessages.CreateService(); UnifiedEconService = steamUnifiedMessages.CreateService(); UnifiedFriendMessagesService = steamUnifiedMessages.CreateService(); + UnifiedParentalService = steamUnifiedMessages.CreateService(); UnifiedPlayerService = steamUnifiedMessages.CreateService(); } @@ -629,6 +632,86 @@ namespace ArchiSteamFarm { Client.Send(request); } + internal async Task<(bool IsSteamParentalEnabled, string SteamParentalCode)?> ValidateSteamParental(string steamParentalCode = null) { + if (!Client.IsConnected) { + return null; + } + + CParental_GetParentalSettings_Request request = new CParental_GetParentalSettings_Request { steamid = Client.SteamID }; + + SteamUnifiedMessages.ServiceMethodResponse response; + + try { + response = await UnifiedParentalService.SendMessage(x => x.GetParentalSettings(request)); + } catch (Exception e) { + ArchiLogger.LogGenericWarningException(e); + + return null; + } + + if (response == null) { + ArchiLogger.LogNullError(nameof(response)); + + return null; + } + + if (response.Result != EResult.OK) { + return null; + } + + CParental_GetParentalSettings_Response body = response.GetDeserializedResponse(); + + if (!body.settings.is_enabled) { + return (false, null); + } + + bool derivedKey; + + switch (body.settings.passwordhashtype) { + case 4: + derivedKey = false; + + break; + case 6: + derivedKey = true; + + break; + default: + ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(body.settings.passwordhashtype), body.settings.passwordhashtype)); + + return (false, null); + } + + if ((steamParentalCode != null) && (steamParentalCode.Length > 4)) { + byte i = 0; + byte[] password = new byte[steamParentalCode.Length]; + + foreach (char character in steamParentalCode) { + if ((character < '0') || (character > '9')) { + break; + } + + password[i++] = (byte) character; + } + + if (i >= steamParentalCode.Length) { + byte[] passwordHash = derivedKey ? SCrypt.ComputeDerivedKey(password, body.settings.salt, 8192, 8, 1, null, body.settings.passwordhash.Length) : SCrypt.GetEffectivePbkdf2Salt(password, body.settings.salt, 8192, 8, 1, null); + + if (passwordHash.SequenceEqual(body.settings.passwordhash)) { + return (true, steamParentalCode); + } + } + } + + ArchiLogger.LogGenericInfo(Strings.PleaseWait); + + steamParentalCode = ArchiCryptoHelper.BruteforceSteamParentalCode(body.settings.passwordhash, body.settings.salt, derivedKey); + + ArchiLogger.LogGenericInfo(Strings.Done); + + return (true, steamParentalCode); + } + private void HandleItemAnnouncements(IPacketMsg packetMsg) { if (packetMsg == null) { ArchiLogger.LogNullError(nameof(packetMsg)); diff --git a/ArchiSteamFarm/ArchiSteamFarm.csproj b/ArchiSteamFarm/ArchiSteamFarm.csproj index 557d50b4b..aa208e057 100644 --- a/ArchiSteamFarm/ArchiSteamFarm.csproj +++ b/ArchiSteamFarm/ArchiSteamFarm.csproj @@ -59,6 +59,7 @@ all + diff --git a/ArchiSteamFarm/Bot.cs b/ArchiSteamFarm/Bot.cs index d496856bb..2833931a3 100755 --- a/ArchiSteamFarm/Bot.cs +++ b/ArchiSteamFarm/Bot.cs @@ -2417,16 +2417,24 @@ namespace ArchiSteamFarm { } } - if (!string.IsNullOrEmpty(BotConfig.SteamParentalCode) && (BotConfig.SteamParentalCode.Length != 4)) { - string steamParentalCode = await Logging.GetUserInput(ASF.EUserInputType.SteamParentalCode, BotName).ConfigureAwait(false); + (bool isSteamParentalEnabled, string steamParentalCode)? steamParental = await ArchiHandler.ValidateSteamParental(BotConfig.SteamParentalCode).ConfigureAwait(false); - if (string.IsNullOrEmpty(steamParentalCode) || (steamParentalCode.Length != 4)) { - Stop(); + if (steamParental?.isSteamParentalEnabled == true) { + if (!string.IsNullOrEmpty(steamParental.Value.steamParentalCode)) { + if (BotConfig.SteamParentalCode != steamParental.Value.steamParentalCode) { + SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParental.Value.steamParentalCode); + } + } else { + string steamParentalCode = await Logging.GetUserInput(ASF.EUserInputType.SteamParentalCode, BotName).ConfigureAwait(false); - break; + if (string.IsNullOrEmpty(steamParentalCode) || (steamParentalCode.Length != 4)) { + Stop(); + + break; + } + + SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParentalCode); } - - SetUserInput(ASF.EUserInputType.SteamParentalCode, steamParentalCode); } ArchiWebHandler.OnVanityURLChanged(callback.VanityURL);