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.
This commit is contained in:
JustArchi
2019-07-20 00:22:50 +02:00
parent d5c3897b1f
commit 535ce04a47
4 changed files with 181 additions and 7 deletions

View File

@@ -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));

View File

@@ -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<IClanChatRooms> UnifiedClanChatRoomsService;
private readonly SteamUnifiedMessages.UnifiedService<IEcon> UnifiedEconService;
private readonly SteamUnifiedMessages.UnifiedService<IFriendMessages> UnifiedFriendMessagesService;
private readonly SteamUnifiedMessages.UnifiedService<IParental> UnifiedParentalService;
private readonly SteamUnifiedMessages.UnifiedService<IPlayer> UnifiedPlayerService;
internal DateTime LastPacketReceived { get; private set; }
@@ -57,6 +59,7 @@ namespace ArchiSteamFarm {
UnifiedClanChatRoomsService = steamUnifiedMessages.CreateService<IClanChatRooms>();
UnifiedEconService = steamUnifiedMessages.CreateService<IEcon>();
UnifiedFriendMessagesService = steamUnifiedMessages.CreateService<IFriendMessages>();
UnifiedParentalService = steamUnifiedMessages.CreateService<IParental>();
UnifiedPlayerService = steamUnifiedMessages.CreateService<IPlayer>();
}
@@ -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<CParental_GetParentalSettings_Response>();
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));

View File

@@ -59,6 +59,7 @@
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="CryptSharpStandard" Version="1.0.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.10" />
<PackageReference Include="Humanizer" Version="2.6.2" />
<PackageReference Include="JetBrains.Annotations" Version="2019.1.3" />

View File

@@ -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);