Уязвимость классов C++ или Как получить доступ к запрещенным элементам

в 9:21, , рубрики: accessibility, c++, метки: ,

Привет!

В процессе написания очередного файлового упаковщика, задался вопросом: «Каким же образом происходит деление на частные, защищенные и публичные объекты в классах?». Как оказалось, никакого деления нет — это чистая абстракция.

Ниже описано строение и функционирование классов на языке C++, а так же способы обходов запретов частных полей доступа.

Классы

И так, что же такое класс. Классы – это некая абстракция, представляющая собой совокупность атрибутов и методов класса или, как говорит нам Интернет:

Класс — разновидность абстрактного типа данных в объектно-ориентированном программировании, характеризуемый способом своего построения.

Класс, на самом деле, – это простая структура, не хранящая в себе никаких методов, полей доступа и конструкторов – вот здесь и проявляется, та самая абстракция.

Рассмотрим некий класс:

class _Class {
private:
	int Value;
public:
	_Class() {
	}
};

Внутри данный класс представляет собой структуру данных, которая не делится, ни на частные, ни на публичные поля доступа, а структура, в свою очередь, является «массивом» элементов разной величины. Т.е. ключевое слово struct просто говорит компилятору о том, что необходимо зарезервировать N байт памяти.

struct _Class {
	int Value;
};

Значит, что адрес объекта класса или структуры – есть адрес первого атрибута. Получается, что для получения значения любого атрибута класса, необходимо иметь его адрес, который с легкость можно получить сдвигом указателя объекта класса на N >= 0 байт.
Например, поменяем значение частного атрибута класса _Class:

class _Class {
private:
	int Value;
public:
	_Class() {
	}
};
_Class Object;
__asm {
	lea eax, Object
	mov ebx, 123
	mov [eax + 0], ebx
}

lea eax, Object

– создаем указатель на объект класса и помещаем в регистр EAX.

mov [eax + 0], ebx

– помещаем в атрибут Value, значение регистра EBX = 123. (0 – байтовый сдвиг, соответственно для второго элемента он будет равен 4 т.к. sizeof(Value) = 4).
Вот таким вот, достаточно простым и понятным способом можно получать и менять значения всех атрибутов, не зависимо от принадлежности к полю доступа.

Получается, что все «запреты» в языке C++ происходят исключительно на уровне компилятора. Аналогичным образом, используя Inline Assembler, можно менять значения константных переменных, например:

const int Value = 7;
__asm {
	mov ebx, 123
	mov Value, ebx
}

Методы

Но как же тогда устроены методы класса, если структура не хранит в себе указатели на функции?
Функции в классах работаю почти так же как и обычные функции. Именуются методы классов следующим образом _Class::_Class. Но основное отличие, разумеется, не в этом – это просто имя, а в 2 ключевых строках дизассемблированного кода.
1. Создается указатель на объект класса и помещается в регистр ECX
2. При вызове метода класса регистр ECX копируется в локальный указатель, именуемый this.

Пример полноценной реализации вызова метода класса на языке C++:

class _Class {
private:
	int Value;
public:
	_Class() {
	}
public:
	void Function() {
		this->Value = 7;
	}
};

_Class Object;
Object.Function();

А вот как это выглядит на самом деле:

struct _Class {
	int Value;
};

void _Class::_Class() {
	_Class* this;
	__asm {
		mov this, ecx
	}
}

void _Class::Function() {
	_Class* this;
	__asm {
		mov this, ecx
	}
	this->Value = 7;
}

_Class Object;
__asm {
	lea ecx, Object
	call _Class::Function
}

И о чем нам это говорит – независимо от поля, метод класса – это просто функция, которая при запуске инициализирует указатель на наш элемент, а класс — это просто структура с публичными атрибутами. Получается, что даже частные методы вызываются с помощью Inline Assembler, без каких либо запретов.

Вот и все, что я хотел рассказать. Спасибо за внимание!

Автор: DmitrySavonin

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js