Ние отдавна не вярваме на софтуера и това е причината за неговата непрекъсната проверка, обратно инженерство, дизасемблиране, тестване във виртуални машини и т.н. А какво да кажем за процесора, който изпълнява този софтуер? Ние сляпо и всеотдайно вярваме на този малък къс от силиций. Само че съвременният хардуер има същите проблеми, каквито има и софтуера: тайни недокументирани функции, грешки, уязвимости, малуер, троянци, рууткити, задни врати. Да се спрем по-подробно.
ISA (Instruction Set Architecture) на х86 архитектурата е един непрекъснато променящ се комплект от процесорни команди. Започвайки от дизайна на 8086, разработен през 1976 година, ISA претърпя и търпи непрекъснати промени и обновявания. Но през цялото това време се запази обратната съвместимост и поддръжката на оригиналните спецификации. За 40 години порастване, ISA инструкциите станаха изключително много, като продължават да излизат нови режими и нови инструкции, всяка от които добавя към предишния дизайн от машинни команди нов слой. Поради политиката на пълна обратна съвместимост, в съвременните х86 процесори присъстват дори и инструкции и режими, които към днешен ден са напълно забравени. В крайна сметkа, резултатът е процесорна архитектура, която е истински лабиринт от нови и антикварни машинни инструкции. Тази твърде сложна среда поражда редица проблеми с кибербезопасността на процесора. Ето защо, х86 процесорите не могат да претендират за ролята на доверени чипове на една критично важна киберинфраструктура.
Още ли вярвате на своя процесор?
Безопасността на програмите и операционната система се базира на безопасността на хардуера, на който са стартирани. Обикновено разработчиците и програмистите не се замислят над факта, че процесорът, на който ще бъде използван техния софтуер, може да бъде ненадежден и да има редица бъгове. Когато хардуерът допуска грешки (умишлено или не), софтуерните механизми за осигуряване на безопасността стават безсмислени. От много години се предлагат различни модели на специално защитени процесори: Intel SGX, AMD Pacifica и други. Но въпреки това, най-редовно се появява информация за критични хардуерни грешки – да си припомним Meltdown и Spectre. Нещо повече, честото откриване на недокументирани инструкции „за отстраняване на грешки“, навеждат на мисълта, че сляпото доверие към процесорите е съвсем безпочвено.
Съвременните х86 процесори са едно гигантско и объркано преплитане на най-новите и на антикварните технологии. Да си припомним, че 8086 има 29 000 транзистори, Pentium е вече с 3 милиона, Broadwell процесорите имат 3,2 милиарда, а Cannonlake прехвърлиха 10 милиарда.
При толкова много транзистори, не е за учудване, че съвременните х86 процесори са пълни с недокументирани инструкции и хардуерни уязвимости. Ето някои от тези недокументирани инструкции: ICEBP (0xF1), LOADALL (0x0F07), apicall (0x0FFFF0), даващи възможност за разблокиране на процесора за несанкциониран достъп до защитените области от паметта.
Що се отнася до многобройните хардуерни уязвимости (двете изображения по-надолу), то те дават на хакерите възможност за повишаване на привилегиите, извличане на криптографските ключове, създаване на нови процесорни инструкции, промяна на функционалността на вече съществуващите процесорни инструкции, поставянето на прекъсвания на инструкциите, поемане на контрола върху хардуерната виртуализация, вмъкване в „атомарните криптографски изчисления“. Най-сладкото е за накрая – „Божествен режим“ в Intel ME: подсигуряването на хардуерен бъг дори и в изключен компютър. И всичко това без да се оставят цифрови следи.
Да напомним накратко, че Intel Management Engine (Intel ME) е автономна подсистема, вграждана почти всички процесори на Intel от 2008 година до днешен ден. Тъй като чипът винаги получава напрежение от вградената малка акумулаторна батерия или от друг източник, Intel ME продължава да работи и когато компютърът е изключен. Intel твърди, че тази система е необходима за осигуряване на максимална производителност. Intel ME се пази в най-строга тайна. Точният принцип на работа не е документиран, а кодът е обфускиран (маскиран) чрез код на Хъфман, таблицата на който се съхранява в самия чип и липсва информация за декодирането.
Съвременните процесори са повече софтуер, отколкото хардуер
Строго погледнато, съвременните процесори дори не могат да бъдат наречени „хардуер“ в пълния смисъл на тази дума. Защото тяхната най-критична функционалност (включително ISA) се осигурява от микрокод, който редовно се обновява. Първоначално, микрокодът се използваше за декодиране и изпълнение на сложните процесорни инструкции: операциите с плаваща запетая, MMX примитивите, инструкциите за работа със стрингове REP и т.н. Но с течение на времето, на микрокода започнаха да се възлагат все повече отговорности за обработката на вътрешните процеси в чиповете. Така например, съвременните разширени инструкции за процесорите на Intel, като например AVX (Advanced Vector Extensions) и VT-d (хардуерната виртуализация) са изцяло реализирани с помощта на микрокод.
Освен това, към днешен ден микрокодът е отговорен и за запазване състоянието на процесора, за управлението на кеша и консумацията на електрическа енергия. Специално за икономичните режими на процесора е реализиран механизъм на прекъсвания, обработващ състоянията на режимите на консумация. С-състоянието (степента на хибернация), Р-състоянието (различните комбинации на волтажа и честотата) – всичко е микрокод. Обнуляването на L2 кеша при влизане в С4 състоянието, както и излизането от това състояние – също е работа на микрокода.
Защо производителите на процесори използват микрокод?
В х86 процесорите микрокодът се използва за разлагането на сложните процесорни инструкции, дължината на които понякога достига 15 байта, във верига от опростени микроинструкции. По този начин значително се облекчава хардуерната архитектура и се облекчава диагностиката. На практика, това е интерпретатор между външната видима за потребителя CISC архитектура и вътрешната хардуерна RISC архитектура.
При откриване на грешка в CISC архитектурата (преди всичко в ISA), производителят публикува обновяване на микрокода, което може да се зареди чрез BIOS/UEFI на дънната платка или чрез операционната система по време на нейното зареждане. Благодарение на тази система от обновявания, базирана на микрокодове, процесорните производители си осигуряват гъвкавост и намаляване на разходите при оправяне на грешките в своите чипове.
Нашумялата грешка с fdiv, която така зле се отрази на процесорите Intel Pentium през 1994 година много ясно показа, че във високотехнологичния хардуер има грешки съвсем като в софтуера. Този инцидент предизвика още по-голям интерес към архитектурите на процесори, базирани на микрокодове. Ето защо Intel и AMD изцяло преминаха към микрокодове. Intel през 1995 година с излизането на Pentium Pro (P6) и AMD през 1999 година със своя K7.
Всичко тайно става явно
Въпреки че процесорните производители държат архитектурата на микрокодовете и механизма на тяхното обновяване в най-строга тайна, понякога излиза нещо. Частички информация (основно от патентите, например AMD RISC86, и внимателно дизасемблиране на официалните обновявания за BIOS, постепенно проливат светлина върху тайните на микрокода. Това не е много добре. Благодарение на постоянното усъвършенстване на дизасемблирането с помощта на хардуерни средства, новите методи на фазинг, и появата на OpenSource инструменти като Microparse и Sandsifter, хакерът може да разбере всичко необходимо, за да напише малуер на микрокод.
Така например, на микрокод вече е създадено прекъсване, които може да прихваща една процесорна инструкция и да пресмята, колко пъти процесорът е използвал командата div. Това всъщност е инжектиране на микрокод в микрокода за изпълнение на процесорната инструкция div. Примерът е даден само с учебни цели.
Има по-усъвършенствани кодове от подобен тип, които си остават в процесорната инструкция и по никакъв начин не издават своето присъствие. Следващият учебен пример показва, че този сегмент от микрокода се активира само когато при изпълнение на div ebx се изпълняват конкретни условия: регистърът ebx има значението B, а в регистъра eax е със значение А. При активиране, този сегмент увеличава значението на регистъра eip (указателя на инструкциите) с единица. Ако някоя програма все пак се натъкне на променената инструкция div ebx и условията се изпълнят, изпълнението на машинния код ще се прехвърли към следващия след div ebx байт. Ако в eax и ebx са поставени някакви други числа, div ebx ще се изпълни нормално.
За какво е нужно това? По този начин е възможно да се активира скрита верига процесорни инструкции, като преходът към нея няма как да се открие. Това е начин за създаване на съвсем неразгадаеми машинни програми, при които стандартните инструкции по неизвестен за потребителя начин изпълняват нещо съвсем друго, за което той въобще не подозира.
Тези два примера демонстрират възможността в най-обикновените и стандартни процесорни инструкции да се вмъкне произволен троянски код. Разбира се, написан на микрокод.
И още, хакерът може да активира своя вреден код отдалечено. Например, когато условието за активиране на вредоносния сегмент се постави в уеб страница, контролирана от злоумишленика. Това е възможно благодарение на компилаторите Just-in-Time (JIT) и Ahead-of-Time (AOT), вградени в съвременните браузъри. Тези компилатори, подобно на много други, могат да вмъкват процесорни инструкции в езиците от високо ниво – например, JavaScript.