Топ контрибуторов
loading
loading
Знаете ли Вы, что

После прохождения теста можно комментировать вопросы теста, а Ваши комментарии увидят модераторы теста и пользователи, которым когда-либо эти вопросы попадались.

Лента обновлений
ссылка Oct 22 23:24
Комментарий от alexcei88:
В вопросе переменная s даже не выводиться, а выводитьс...
ссылка Oct 22 17:46
Комментарий от AlexFurm:
Это UB, так можно вызывать только статические функции ч...
ссылка Oct 22 17:43
Комментарий от AlexFurm:
Любые битовые операции с signed это UB
ссылка Oct 21 20:30
Комментарий от yoori:
Любой вариант скомпилируется если компилировать не в конеч...
ссылка Oct 21 16:53
Добавлен вопрос в тест QA (Quality Assurance)
Статистика

Тестов: 153, вопросов: 8596. Пройдено: 443367 / 2177507.

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

head tail Статья
категория
C++
дата24.08.2010
авторk06a
голосов16

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

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

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

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

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

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

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

Например, будем прятать от читеров целые числа. Создадим класс, в последующем он заменит нам тип 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);
	}
	
	// ...
};

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

Главное не обращать внимания на переполнение разрядности чисел при сложении и вычитании.
Не верите? Вот Вам пример:

  • a,b,c - элементы вектора (числа в диапазоне 0..9)
  • x - загружаемое значение
  • Будем считать что генератор случайных чисел выдаёт
    следующую последовательность: 0 1 2 0 1 2 0 1 2 . . .
abcx
0005
5008
5302
5345
8341
8940
8930
8930
8930
Реализация всех выдуманных примочек

В результате у меня получился вот такой вот шаблончик:

// Copyright by [k06a] © 2010
#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_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;
	}
};

Пользуемся нашим шаблоном вот так:

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

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

Выполним следующий код:

int_x 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;

В результате выполнения видим:

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. Будет чуть быстрее работать и немного запутанней. Я пожалуй остановлюсь на сложении))

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

С уважением, k06a.

Если Вам понравилась статья, проголосуйте за нее

Голосов: 16  loading...
admin   Lavroff   zZoMROT   Maxwe11   ByteMaster   kriolyth   vovs   AkaiRain   Stalker   Antoxa   ooops   ITcrusader   inspir3   SunDrop   nephrael   Gurbych