S>>Велком — рассказывайте, какие операции вы собираетесь поддерживать, и как получается итоговая разметка.
O>Любая операция принимает на вход некие данные, делает над ними некие вычисления, результат которых зависит от самой операции и от входных данных. В результате операции появляются новые данные. Причем совершенно независимо от того что там за операция — imul, xor или SSEэшное перемножение векторов, 'принадлежность' информации которая получается в ее результате однозначно определяется как объединение принадлежностей информации принимаемой на вход (всех операндов, включая регистры и IO порты) и самой операции.
В примере перечислено то, что покроет 95% случаев. А вот специальные 5% инструкций доставят 95% проблем. И эти 5% — условные переходы. Вот как вы собираетесь размечать результат инструкции условного перехода? Все данные после перехода будут помечены тегом данных, которые использовались в условии? А не учитывать условные переходы нельзя, иначе можно создавать информацию из воздуха.
var bankBits = bankData.asLongNumber(); //банковые данные
var myData = 0; //хакерские данные.
for (var i = 0; i < 10000; i++) {
if (bankBits % 2 == 0)
myData = myData * 2; //только хакерские данные
else
myData = myData * 2 + 1; //тоже хакерские данные
// Чисто банковские данные. Или нет? Хотя не важно.
bankBits /= 2;
}
myData.reverseBits();
Вот так на условных переходах теги теряются. Так что нужно бы все условные переходы трейсить и навешивать теги от условия перехода. Чую, так просто от этого не отделаться будет. Расползутся теги от условного перехода по всему приложению. Вот посмотрит браузер на clipping area для вывода чего-нибудь со странички банка, и все, на всем UI-выводе будет висеть тег этого банка. Даже после того, как я закрыл все окна от него.
Еще интересные темы с межпроцессным взаимодействием. Как тегируется новый процесс?
O>Теперь попробуйте придумать вектор атаки, которая это обойдет.
Запросто. И даже теги на IP (instruction pointer) нам не помешают. Будем неторопясь сливать данные в свой "хакерский" домен. Отсутствие информации тоже может быть использовано в качестве информации!
// То, что мы хотим украсть (файл/буфер в памяти).
// Банковский тег на данных.
// var bankBits = ...;
// Заведомо достаточно для данных, теги на некоторых ячейках будут меняться.
// Заполнен false'ами с хакерскими тегами.
var hLargeBuffer = new boolean[QUITE_BIG_BUFFER];
// Наш файл, в который мы пишем. Для простоты пишем биты, хакерский тег.
var hFile = openBitStream(...);
// Хакерские данные. Обязательно без банковского тега.
var hNextWriter = 0;
// Украсть банковские данные.
def stealData() {
for (var i = 0; i < QUITE_BIG_BUFFER; i++)
startThread(new KamikazeWriter(i).write);
for (var i = 0; i <= QUITE_BIG_BUFFER; i++)
startThread(new KamikazeReader(i).write);
//Позапускали каких-то потоков. Вроде бы все локально.
}
//Райтер данных из банка. Писатель хакерского буфера.
class KamikazeWriter(pos : Int) {
def write() = {
// на бите будет банковский тег.
val bit = bankBits.getBitAt(pos);
if (bit == 1)
hLargeBuffer[pos] = true;
// на hLargeBuffer[pos] будет банковский тег, но только если
// бит в позиции pos был установлен в 1. Иначе на
// hLargeBuffer[pos] не будет банковского тега (мы в него не пишем).
// При необходимости массив можно развернуть в гору отдельных переменных.
// Ячейки массива можно заменить на atomic для обеспечения потокобезопасности.
}
}
//Читатель данных из буфера. Пишет в наш файл очищенные от тегов данные.
class KamikazeReader(order : Int) {
def write() = {
//Спим. Интервал должен быть достаточен, чтобы даже с погрешностями
//планировщика читатели выполнялись в порядке order.
Thread.sleep(order * SUFFICIENT_INTERVAL);
// Предыдущий читатель наткнулся на коварно установленный банком бит
// и погиб смертью храбрых защищая чистоту hNextWriter. Почтим память
// того читателя установкой бита в наших чистых данных.
if (hNextWriter < order)
hFile.writeBit(1);
// Пытаемся скопировать свой бит в хакерский файл.
if (hLargeBuffer[order]) {
// Все пропало. На нас теперь будет
// проклятье банковских данных. Оно будет преследовать наш
// Instruction Pointer до самой смерти. Так что умрем прямо сейчас.
} else {
// А бит был false. Это значит, на нем не было метки банка
// (мы не пишем false после чтения данных банка). И наш
// Instruction Pointer еще не имеет ненужных тегов.
hFile.writeBit(0);
// Сигналим следующему читателю, что мы живы и единичку писать не надо.
hNextWriter = order + 1;
}
}
}
Вот вам и вектор. Каждый читатель получает информацию из "хакерских данных". Если вдруг он прочитал данные с банковским тегом, он просто умирает (ничего никуда не сигналя!). Так как другие читатели знают протокол обмена, они могут восстановить "ошибки протокола" (т.е. не вышедших на связь в нужное время читателей).
Немного черной магии и данные извлекаются из "отсутствия данных". Чтобы с этим бороться, нужно либо анализировать как-то program flow и возможные affected vairables. Либо радостно развешивать теги на все псевдоглобальное, включая thread.sleep, сетевые взаимодействия и прочие источники, которые могут быть использованы для кражи данных. Но даже там я реализую sleep через большой цикл. И буду с интервалом ридеров пускать, а не ждать в самом ридере.
Что-то мне кажется, что попытки бороться со всем этим приведут к решению "ставим теги на процесс". А это уже совсем не так интересно. Плюс результат будет похож на то, что уже есть сейчас. Всякие apparmor и прочие ограничители прав приложений (т.е. не только файловый доступ, а еще сокеты, системные ресурсы и т.п.).