среда, 29 апреля 2009 г.

Переменные цвета хаки

1. Введение в проблему


Разрабатывая очередное приложение/игру, задумываетесь ли вы о приватности ваших переменных? Что если переменные, которые служили вам верой и правдой в процессе отладки и тестирования, начнут вдруг ни с того ни с сего менять свои значения прямо на этапе выполнения? Сами по себе они вряд ли станут это делать. Кому-то это должно быть выгодно. В играх, например, всегда присутствует некий критерий продвинутости игрока (очки, баллы, монетки, время и т.д.). Всегда найдутся желающие "накрутить" себе баллов.

2. Какие у нас варианты?


Во-первых, можно защитить память своего приложения средствами ОС или, например, отслеживая присутствие в системе процессов читерских программ. Считаю этот способ крайне слабым. Всегда найдутся программы, которые раскурочат вас (запустят в режиме отладки, м.б. с заниженным приоритетом, прикинутся дровами и т.д.).

Во-вторых, существуют методы по шифрованию данных, хранимых в переменных. Если в шифровании нет проверки на валидность, то методом проб и ошибок всё равно подбирается желанное число очков. Если проверка на валидность данных присутствует, то очки на глаз уже не заполучить. А вот заморозить шифрованную переменную можно. А если это жизни игрока? Нехорошо получается . . .

В-третьих, есть методы для разделения переменных в пространстве. Суть метода состоит в том, чтобы не позволить читерской программе найти в памяти ваши переменные. Поиск в памяти основывается на отлове изменений значений переменных. Необходим метод для сокрытия значений переменных в пространстве. Один из таких методов я описываю далее.

3. "Беспалевно" меняем значение


Например, будем прятать от читеров целые числа. Создадим класс, в последующем он заменит нам тип int. Будем хранить необходимое значение в двух или трёх переменных. Их сумма и будет "значением". При изменении значения мы будем менять лишь одну из переменных. Изменяемую переменную выберем случайным образом.

// код не проверен, написан с телефона
class int_3
{
    int vect[3];
public:
    int_3(int value = 0) {
      operator = (value);
    }

    // Сумма всех элементов,
    // кроме указанного
    int sum(int exclude = -1) {
      int s = 0;
      for (int i=0; i<3; i++)
         if (i != exclude)
             s += vect[i];
      return s;
    }

    operator int() {
      return sum();
    }
    int operator = (int value) {
      int index = rand() % 3;
      vect[index] = value - sum(index);
      return value;
    }
    int operator + (int value) {
      return operator = (sum() + value);
    }
    int operator - (int value) {
      return operator = (sum() - value);
    }
    int operator * (int value) {
      return operator = (sum() * value);
    }
    int operator / (int value) {
      return operator = (sum() / value);
    }
    // ...
};


* This source code was highlighted with Source Code Highlighter.


При выполнении любой из операции изменяется значение ровно одной ячейки памяти. Но это ещё не всё: тот, кто знает наш способ, может придумать, как нас всё-таки обмануть.

- Например, при изменении значения, всегда меняется значение 12-ти байтового числа (смежные поля класса лежат рядом в памяти). Изменения 12-ти символьной строки можно отловить . . .

- Можно хранить три частичных значения в динамической памяти. Тогда у нас будет массив из указателей. Сам массиив не изменяется, изменяются ячейки памяти, которые располагаются отнюдь не рядом. При желании можно "специально" между выделениями памяти под наши переменные выделять память под относительно большие массивы, а потом просто её высвобождать, чтоб уж точно переменные лежали в разных местах.


- Масштабируемость алгоритма налицо - можно завести вектор подлиннее, а ещё лучше сделать длину динамической. Ну и, конечно же, оформить не классом, а шаблоном.

P.S.
Главное не обращать внимания на переполнение разрядности чисел при сложении и вычитании. Не верите? Вот Вам пример:
a,b,c - элементы вектора (0..9)
x - загружаемое значение
Будем считать что генератор случайных чисел выдаёт следующую последовательность:
0 1 2 0 1 2 0 1 2 . . .
a b c x
0 0 0 5
5 0 0 8
5 3 0 2
5 3 4 5
8 3 4 1
8 9 4 0
8 9 3 0
8 9 3 0
8 9 3 0
. . . .

4. Реализация всех выдуманных примочек


В результате у меня получился вот такой вот шаблончик:
// Copyright by [k06a] © 2009

#include <iostream>

using namespace std;

template<class T> class int_x
{
  T **vect;
  int size;

public:
  // Конструкторы
  int_x( int value_ = 0, int size_ = 2, int scrambler = 0 ) {
     init( value_, size_, scrambler );
  }
  int_x( int_x & var ) {
     init( var.sum(), var.size );
  }
  // Для работы: int_x<int> var = 5;
  int_x( int_x const & var ) {
     init( var.sum(), var.size );
  }
  ~int_x() {
     deinit();
  }

  int_x & operator = (int_x var) {
     deinit();
     init( var.sum(), var.size );
  }

  // [Де]Инициализация
  void init(int value_ = 0, int size_ = 2, int scrambler = 0)
  {
     size = size_;
     vect = new T* [size_];
     char *buf = NULL;

     for (int i=0; i<size; i++)
     {
         if (scrambler)
            buf = new char [rand()%scrambler];
         vect[i] = new T;
         if (scrambler)
            delete [] buf;
     }
     operator = (value_);
  }
  void deinit()
  {
     for (int i=0; i<size; i++)
         delete vect[i];
     delete [] vect;
     vect = NULL;
  }

  // Сумма всех эллементов, кроме указанного
  T sum(int exclude = -1) const
  {
     T summa = 0;
     for (int i=0; i<size; i++)
         if (i != exclude)
            summa += *vect[i];
     return summa;
  }

  // Приведение типа
  operator T () {
     return sum();
  }

  // Основной оператор присваивания
  T operator = (T value) {
     int index = rand() % size;
     *vect[index] = value - sum(index);
     return value;
  }

  T operator + (T value) { return operator = (sum() + value); }
  T operator - (T value) { return operator = (sum() - value); }
  T operator * (T value) { return operator = (sum() * value); }
  T operator / (T value) { return operator = (sum() / value); }
 
  T operator += (T value) { return operator = (sum() + value); }
  T operator -= (T value) { return operator = (sum() - value); }
  T operator *= (T value) { return operator = (sum() * value); }
  T operator /= (T value) { return operator = (sum() / value); }

  T operator ++ () { return operator = (sum()+1); }
  T operator -- () { return operator = (sum()-1); }
  T operator ++ (int unused) { return operator = (sum()+1); }
  T operator -- (int unused) { return operator = (sum()-1); }

  void print()
  {
     for (int i=0; i<size; i++)
         cout << *vect[i] << " ";
     cout << endl;
  }
  void print_where()
  {
     for (int i=0; i<size; i++)
         cout << vect[i] << endl;
  }
};


* This source code was highlighted with Source Code Highlighter.



Пользуемся нашим шаблоном вот так:
int_x<int> a = 0, b = 7;
// Далее a и b используются как
// обычные переменные типа int

int_x<int> x(0,10,8192);
// Переменная x имеет значение 0,
// хранится в 10 различных переменных,
// между созданием каждого из элементов вектора
// в памяти выделялось от 0 до 8192 байт.
// Далее переменной x пользуемся как int-ом


* This source code was highlighted with Source Code Highlighter.



5. Наблюдаем за работой схемы


Выполним следующий код:
int_x<unsigned short int>
  ab(0,5,4096), a = 3, b(5,3);
ab = 1;

cout << "Address of a : " << endl; a.print_where();
cout << "[ a == " << a << " ]: "; a.print(); cout << endl;
cout << "Address of b : " << endl; b.print_where();
cout << "[ b == " << b << " ]: "; b.print(); cout << endl;
cout << "Address of a^b : " << endl; ab.print_where();
cout << "[ a^b == " << ab << " ]: "; ab.print(); cout << endl;

for (int i=0; i<b; i++)
{
  ab *= a;
  cout << "[ a^b == " << ab << " ]: "; ab.print();
}
cout << ab << endl;


* This source code was highlighted with Source Code Highlighter.



В результате выполнения видим:
Address of a :
0x3e4810
0x3e4820
[ a == 3 ]: 65147 392

Address of b :
0x3e5098
0x3e50a8
0x3e4138
[ b == 5 ]: 64693 400 448

Address of a^b :
0x3e4760
0x3e50c0
0x3e5280
0x3e4798
0x3e40d0
[ a^b == 1 ]: 20672 21120 1 392 23352

[ a^b == 3 ]: 20672 21120 1 392 23354
[ a^b == 9 ]: 20678 21120 1 392 23354
[ a^b == 27 ]: 20696 21120 1 392 23354
[ a^b == 81 ]: 20696 21174 1 392 23354
[ a^b == 243 ]: 20696 21174 163 392 23354
243


P.S.
Вместо операции сложения можно использовать операцию XOR.
Будет чуть быстрее работать и немного запутанней.
Я пожалуй остановлюсь на сложении))

Ну вот и всё. Ничего страшного.
Есть замечания, дополнения, идеи, комментарии?

Комментариев нет: