مرحبا! لا يخفى على أحد أن هناك العديد من البرامج لاختراق الألعاب والتطبيقات. هناك أيضًا طرق عديدة للاختراق. على سبيل المثال ، إلغاء ترجمة وتعديل شفرة المصدر (مع النشر اللاحق لملفات APK المخصصة ، على سبيل المثال ، مع الذهب اللانهائي وجميع المشتريات المدفوعة). أو الطريقة الأكثر تنوعًا هي مسح وتصفية وتحرير القيم في ذاكرة الوصول العشوائي. كيف تتعامل مع هذا الأخير ، سأخبرك تحت الخفض.
بشكل عام ، لدينا ملف تعريف لاعب مع مجموعة من المعلمات ، والتي يتم تسلسلها في اللعبة المحفوظة وتحميلها / حفظها عند بدء / انتهاء اللعبة. وإذا كان من السهل جدًا إضافة التشفير أثناء التسلسل ، فإن حماية نفس ملف التعريف في ذاكرة الوصول العشوائي تكون أكثر صعوبة إلى حد ما. سأحاول إعطاء مثال بسيط:
var money = 100; // "100" is present in RAM now (as four-byte integer value). Cheat apps can find, filter and replace it since it was declared.
money += 20; // Cheat apps can scan RAM for "120" values, filter them and discover the RAM address of our "money" variable.
Debug.Log(money); // We expect to see "120" in console. But cheat apps can deceive us!
ProtectedInt experience = 500; // four XOR-encrypted bytes are present in RAM now. Cheat apps can't find our value in RAM.
experience += 100;
Debug.Log(experience); // We can see "600" in console;
Debug.Log(JsonUtility.ToJson(experience)); // We can see four XOR-encrypted bytes here: {"_":[96,96,102,53]}. Our "experience" is hidden.
النقطة الثانية الجديرة بالاهتمام هي أن إدخال حماية جديدة يجب أن يتم بأدنى حد من التغييرات في الكود المصدري للعبة ، حيث يعمل كل شيء بالفعل بشكل جيد وتم اختباره عدة مرات. في طريقتي ، سيكون كافيًا استبدال أنواع int / long / float بـ ProtectedInt / ProtectedLong / ProtectedFloat . بعد ذلك سأقدم التعليقات والرمز.
تخزن الفئة الأساسية Protected مجموعة مشفرة من البايتات في الحقل "_" ، كما أنها مسؤولة عن تشفير البيانات وفك تشفيرها. التشفير بدائي - XOR مع المفتاح . هذا التشفير سريع ، لذا يمكنك العمل مع المتغيرات حتى في التحديث... تعمل الفئة الأساسية مع مصفوفات البايت. تعتبر الفئات الفرعية مسؤولة عن تحويل نوعها من وإلى مصفوفة بايت. ولكن الأهم من ذلك ، أنها "متخفية" على أنها أنواع بسيطة باستخدام عامل التشغيل الضمني ، لذلك قد لا يلاحظ المطور حتى أن نوع المتغيرات قد تغير. قد تلاحظ أيضًا السمات في بعض الطرق والخصائص اللازمة للتسلسل مع JsonUtility و Newtonsoft.Json (كلاهما مدعوم في نفس الوقت). إذا كنت لا تستخدم Newtonsoft.Json ، فأنت بحاجة إلى إزالة #define NEWTONSOFT_JSON .
#define NEWTONSOFT_JSON
using System;
using UnityEngine;
#if NEWTONSOFT_JSON
using Newtonsoft.Json;
#endif
namespace Assets
{
[Serializable]
public class ProtectedInt : Protected
{
#if NEWTONSOFT_JSON
[JsonConstructor]
#endif
private ProtectedInt()
{
}
protected ProtectedInt(byte[] bytes) : base(bytes)
{
}
public static implicit operator ProtectedInt(int value)
{
return new ProtectedInt(BitConverter.GetBytes(value));
}
public static implicit operator int(ProtectedInt value) => value == null ? 0 : BitConverter.ToInt32(value.DecodedBytes, 0);
public override string ToString()
{
return ((int) this).ToString();
}
}
[Serializable]
public class ProtectedFloat : Protected
{
#if NEWTONSOFT_JSON
[JsonConstructor]
#endif
private ProtectedFloat()
{
}
protected ProtectedFloat(byte[] bytes) : base(bytes)
{
}
public static implicit operator ProtectedFloat(int value)
{
return new ProtectedFloat(BitConverter.GetBytes(value));
}
public static implicit operator float(ProtectedFloat value) => value == null ? 0 : BitConverter.ToSingle(value.DecodedBytes, 0);
public override string ToString()
{
return ((float) this).ToString(System.Globalization.CultureInfo.InvariantCulture);
}
}
public abstract class Protected
{
#if NEWTONSOFT_JSON
[JsonProperty]
#endif
[SerializeField]
private byte[] _;
private static readonly byte[] Key = System.Text.Encoding.UTF8.GetBytes("8bf5b15ffef1f485f673ceb874fd6ef0");
protected Protected()
{
}
protected Protected(byte[] bytes)
{
_ = Encode(bytes);
}
private static byte[] Encode(byte[] bytes)
{
var encoded = new byte[bytes.Length];
for (var i = 0; i < bytes.Length; i++)
{
encoded[i] = (byte) (bytes[i] ^ Key[i % Key.Length]);
}
return encoded;
}
protected byte[] DecodedBytes
{
get
{
var decoded = new byte[_.Length];
for (var i = 0; i < decoded.Length; i++)
{
decoded[i] = (byte) (_[i] ^ Key[i % Key.Length]);
}
return decoded;
}
}
}
}
إذا نسيت أو ارتكبت خطأ في مكان ما ، فاكتب في التعليقات =) حظًا سعيدًا في التطوير!
ملاحظة. القطة ليست لي ، مؤلف الصورة هو CatCosplay.
محدث. في التعليقات أبديت الملاحظات التالية على القضية:
- من الأفضل الانتقال إلى الهيكلة لجعل الكود أكثر قابلية للتنبؤ (أكثر من ذلك إذا أخفنا أنفسنا بأنواع قيم بسيطة).
- لا يمكن إجراء البحث في ذاكرة الوصول العشوائي بقيم محددة ، ولكن من خلال جميع المتغيرات المتغيرة. لن يساعد XOR هنا. بدلاً من ذلك ، أدخل المجموع الاختباري.
- BitConverter بطيء (على نطاق صغير بالطبع). من الأفضل التخلص منه (لأنه اتضح أنه من أجل تعويم - أنا في انتظار اقتراحاتك).
يوجد أدناه نسخة محدثة من الكود. ProtectedInt و ProtectedFloat هي الآن هياكل. لقد تخلصت من مصفوفات البايت. بالإضافة إلى ذلك ، قدم المجموع الاختباري _h كحل للمشكلة الثانية. لقد اختبرت التسلسل في كلا الاتجاهين.
[Serializable]
public struct ProtectedInt
{
#if NEWTONSOFT_JSON
[JsonProperty]
#endif
[SerializeField]
private int _;
#if NEWTONSOFT_JSON
[JsonProperty]
#endif
[SerializeField]
private byte _h;
private const int XorKey = 514229;
private ProtectedInt(int value)
{
_ = value ^ XorKey;
_h = GetHash(_);
}
public static implicit operator ProtectedInt(int value)
{
return new ProtectedInt(value);
}
public static implicit operator int(ProtectedInt value) => value._ == 0 && value._h == 0 || value._h != GetHash(value._) ? 0 : value._ ^ XorKey;
public override string ToString()
{
return ((int) this).ToString();
}
private static byte GetHash(int value)
{
return (byte) (255 - value % 256);
}
}
[Serializable]
public struct ProtectedFloat
{
#if NEWTONSOFT_JSON
[JsonProperty]
#endif
[SerializeField]
private int _;
#if NEWTONSOFT_JSON
[JsonProperty]
#endif
[SerializeField]
private byte _h;
private const int XorKey = 514229;
private ProtectedFloat(int value)
{
_ = value ^ XorKey;
_h = GetHash(_);
}
public static implicit operator ProtectedFloat(float value)
{
return new ProtectedFloat(BitConverter.ToInt32(BitConverter.GetBytes(value), 0));
}
public static implicit operator float(ProtectedFloat value) => value._ == 0 && value._h == 0 || value._h != GetHash(value._) ? 0f : BitConverter.ToSingle(BitConverter.GetBytes(value._ ^ XorKey), 0);
public override string ToString()
{
return ((float) this).ToString(CultureInfo.InvariantCulture);
}
private static byte GetHash(int value)
{
return (byte) (255 - value % 256);
}
}