Comments 43
developers.google.com/games/services/android/antipiracy
Il2cpp не спасает от frida, как и обфускация кода.
Пока из хитрожопого встречал защиту pokemon go, которая роняет аппку при попытке заинжектить код.
А может, я просто что-то не понял.
#define NEWTONSOFT_JSON
, если в настройках есть специального для этого Scripting Define Symbols?BitConverter
— пожалуй, самый неторопливый способ интерпретировать массив как long
.
Код, опять же, повторяется.
Возможно, стоит переписать так:
[Serializable]
public class ProtectedValue<T> : Protected where T : struct
{
#if NEWTONSOFT_JSON
[JsonConstructor]
#endif
private ProtectedValue() { }
protected ProtectedValue(ReadOnlySpan<byte> bytes) : base(bytes) { }
public static implicit operator ProtectedValue<T>(T value)
{
return new ProtectedValue<T>(MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1)));
}
public static implicit operator T(ProtectedValue<T> value) => value == null ? default(T) : MemoryMarshal.Cast<byte, T>(value.DecodedBytes)[0];
public override string ToString()
{
return ((T)this).ToString();
}
}
public abstract class Protected
{
#if NEWTONSOFT_JSON
[JsonProperty]
#endif
[SerializeField]
private readonly byte[] _;
private static readonly byte[] Key = System.Text.Encoding.UTF8.GetBytes("8bf5b15ffef1f485f673ceb874fd6ef0");
protected Protected()
{
}
protected Protected(ReadOnlySpan<byte> bytes)
{
_ = Encode(bytes);
}
private static byte[] Encode(ReadOnlySpan<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;
}
}
}
А можно вообще не страдать и сделать вот так.
А если игра сетевая — то, логично, что всю механику нужно просчитывать на сервере. Пользователь как угодно может ломать свой клиент — на сервере его параметры здоровья, золота и прочего никак не изменятся.
Есть другая проблема онлайн игр (которая в одних жанрах проявляется слабее, например в шутерах, в других — сильнее, например в mmorpg), которая до конца не решена никем — как защитить игру от автоматизации (ботов и скриптов). Вот на эту тему было бы намного интереснее почитать статьи и варианты решений.
Если честно, мне кажется множество ломающих игры, и множество покупающих игровую валюту если и пересекаются, в очень небольшой площади.
Игровые покупки, рейтинги и достижения. Иногда ограниченное взаимодействие пользователей, например, рынок (обмениваются игроки на сервере, но добывают вещи в оффлайне).
Вот вроде бы задумка хорошая, но превращать value type (int) в reference type (ProtectedInt) — ужасная идея. При передаче числа в функцию вы не ожидаете, что оно может измениться в результате выполнения функции.
Поменяйте class
на struct
, и храните результат вычислений в ubyte/uint/ulong — и будет вам счастье.
А чтобы быстрее работало, используйте math.asuint
/ math.asulong
для конвертации. Массив байт вам явно не нужен.
Ничего умного и не надо:) Делаешь то же самое, что и с int32, но при записи делаешь math.asuint, а при чтении делаешь math.asfloat. По сути это reinterpetcast.
Я уточнил в ЛС, это отдельный пакет для Unity. Я решил обойтись без него, хотя с его помощью можно немного ускорить код после замены вызовов BitConverter.
https://docs.unity3d.com/Manual/com.unity.mathematics.html
public static unsafe int SingleToInt32Bits(float value)
{
return *(int*) &value;
}
public static unsafe float Int32BitsToSingle(int value)
{
return *(float*) &value;
}
Думаю под капотом math.asfloat скрывается тоже самое.
Всё верно.
Не очень понятно почему. Unity топит сейчас за ECS и все больше фич переезжает под него, а движок ECS как раз на unsafe и построен.
Сколько раз я в детстве ломал игры через ArtMoney поиском неизвестного значения, по принципу изменилось, не изменилось, не изменилось, изменилось, не изменилось… А потом поиск указателя на начало блока, и в 90% даже искать заново потом не надо. И xor тут не помогает...
Причем они должны идти не подряд что бы не выглядеть например как 64 разрядное число вместе. Или периодически перекодироваться вне зависимости от изменения данных.
И все равно защита будет так себе, ибо переводить на такой бред все ресурсы это хмм. А иначе всегда можно будет взломать не деньги, но 100500 алмазов например.
Достаточно будет рандомно менять "ключ" xor'а при записи значения. И при этом рандомно решать, менять ли "ключ" или использовать старый (для отсечения ситуаций когда после фильтраций остаются два неизвестных значения во всём адресном пространстве, и их оба одновременно замораживают).
Рандомно решать не поможет, если взломщик сможет их интерпретировать как одно значение (например int32 key + int32 value = int64 view). Просто процесс поиска станет вероятностным.
Например: поиск, десять разный действий с другими данными и поисков на не изменение. Потом 10 разных покупок на деньги (что бы 50%^10 что ключ не поменялся) и 1 поиск. Повторить раза 3...
Промахнулся веткой — del.
Как защитить данные игры на Unity в оперативной памяти?