Control Flow Guard. Принцип роботи та методи обходу на прикладі Adobe Flash Player


Компанія Microsoft не залишає спроб перемогти в нескінченній війні з эксплоитописателями, раз за разом реалізуючи нові техніки по захисту додатків. На цей раз розробники операційної системи Windows підійшли до вирішення даного питання більш фундаментально, перевівши свій погляд на корінь проблеми. Робота майже кожного експлоїта так чи інакше націлена на перехоплення потоку виконання програми, отже, не завадило б "навчити" додатки стежити за цим моментом.
Концепия Control Flow Integrity (цілісність потоку виконання) була описана ще в 2005 році. І ось, 10 років, розробники з компанії Microsoft представили свою неповну реалізацію даного концепту — Control Flow Guard.
Що таке Control Flow Guard
Control Flow Guard (Guard CF, CFG) — відносно новий механізм захисту Windows (exploit mitigation), націлений на те, щоб ускладнити процес експлуатації бінарних вразливостей в користувацьких програмах і програмах режиму ядра. Робота даного механізму полягає в валідації неявних викликів (indirect calls), що запобігає перехоплення потоку виконання зловмисником (наприклад, за допомогою перезапису таблиці віртуальних функцій). У поєднанні з попередніми механізмами захисту (SafeSEH, ASLR, DEP і т. д.) являє собою додатковий головний біль для творців експлойтів.
Дана сек'юріті фіча доступна користувачам ОС Microsoft Windows 8.1 (Update 3, KB3000850) і Windows 10.
Компіляція програм з підтримкою CFG доступна в Microsoft Visual Studio 2015як включити?).
Аналогічна реалізація механізму захисту на основі концепції Control Flow Integrity для ОС сімейства Linux є в розширенні PaX.
Як працює Control Flow Guard
Розглянемо принцип роботи CFG в режимі користувача. Даний механізм має два основних компоненти: бітову карту адрес (управляється ядром) і процедуру перевірки покажчика викликається функції (використовується користувацькими додатками).
Вся службова інформація CFG заноситься в
IMAGE_LOAD_CONFIG_DIRECTORY
виконуваного файлу під час компіляції:

  • GuardCFCheckFunctionPointer
    — покажчик на процедуру перевірки
  • GuardCFFunctionTable
    — таблиця валідних адрес функцій (використовується ядром для ініціалізації бітової карти)
  • GuardCFFunctionCount
    — кількість функцій в таблиці
  • GuardFlags
    — прапори
заголовок
IMAGE_NT_HEADERS.OptionalHeader.DllCharacteristics
заноситься прапор
IMAGE_DLLCHARACTERISTICS_GUARD_CF
, який засвідчує, що даний виконуваний файл підтримує механізм CFG.
Всю службову інформацію можна подивитися за допомогою інструменту
dumpbin.exe
з Microsoft Visual Studio 2015 (Microsoft Visual Studio 14.0\VC\bin\dumpbin.exe), запустивши його з ключем
/loadconfig
.


GuardFlags
Заголовковий файл
winnt.h
для Windows 10 (1511) містить такі прапори CFG (останній є маскою, а не прапором):
  • IMAGE_GUARD_CF_INSTRUMENTED
    (0x00000100) — Модуль проводить перевірки потоку виконання при підтримці системи
  • IMAGE_GUARD_CFW_INSTRUMENTED
    (0x00000200) — Модуль проводить перевірки цілісності потоку виконання і запису
  • IMAGE_GUARD_CF_FUNCTION_TABLE_PRESENT
    (0x00000400) — Модуль містить таблицю валідних функцій
  • IMAGE_GUARD_SECURITY_COOKIE_UNUSED
    (0x00000800) — Модуль не використовує security cookie (/GS)
  • IMAGE_GUARD_PROTECT_DELAYLOAD_IAT
    (0x00001000) — Модуль підтримує Delay Load Import Table, доступну тільки для читання
  • IMAGE_GUARD_DELAYLOAD_IAT_IN_ITS_OWN_SECTION
    (0x00002000) — Delay Load Import Table знаходиться у своїй власній
    .didat
    секції
  • IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK
    (0xF0000000) — Крок одного елемента таблиці валідних функцій Guard CF кодуються в бітах даних (додаткову кількість байтів для кожного елемента)
Варто відзначити, що це неповний список існуючих прапорів. Найбільш повний список можна отримати з нутрощів файлу
link.exe
(компонувальник):

Також варто звернути увагу на присутність деяких цікавих прапорів, офіційної інформації про яких немає. Розробники Microsoft, по всій видимості, тестують додатковий механізм CFG для перевірки адреси запису (
IMAGE_GUARD_CFW_INSTRUMENTED
).
Бітова карта
Під час завантаження ОС ядром (функція
nt!MiInitializeCfg
) створюється бітова карта
nt!MiCfgBitMapSection
, яка є загальною (shared) секцією для всіх процесів. При запуску процесу, що підтримує CFG, відбувається відображення (mapping) бітової карти в адресний простір процесу. Після чого адреса і розмір бітової карти заносяться в структуру
ntdll!LdrSystemDllInitBlock
.
Зіставленням адрес функцій з бітами в бітовій карті займається завантажувач файлів (функція
nt!MiParseImageCfgBits
). Кожний біт в бітовій карті відповідає за 8 байт користувальницького адресного простору процесу. Адреси початку всіх валідних функцій співвідносяться з одиничним бітом за відповідного зміщення в бітовій карті, а все інше — 0.


Процедура перевірки покажчика викликається функції
Кожен неявний виклик у програмі на етапі компіляції обрамляється перевіркою адреси виклику функції. Адреса процедури перевірки встановлює завантажувач файлів, що виконуються, оскільки початково встановлений адреса порожній процедури, тим самим зберігаючи зворотну сумісність.
Для наочності подивимося на один і той же код, скомпільований без CFG і з ним.
Оригінальний код на C++:
class CSomeClass
{
public:
virtual void doSomething()
{
std::cout << "привіт";
}
};

int main()
{
CSomeClass *someClass = new CSomeClass();
someClass->doSomething();

return 0;
}

ASM лістинг (вирізка):
mov eax, [ecx] ; EAX = CSomeClass::vftable
call dword ptr [eax] ; [EAX] = CSomeClass::doSomething()

З ключем компіляції /guard:cf :
mov eax, [edi] ; EAX = CSomeClass::vftable
mov esi, [eax] ; ESI = CSomeClass::doSomething()
mov ecx, esi
call ds:___guard_check_icall_fptr ; checks that ECX is valid function pointer
mov ecx, edi
call esi

У першому випадку код піддається атаці з використанням техніки підміни таблиці віртуальних функцій. Якщо атакуючий при експлуатації уразливості здатний замінити дані об'єкта, то він може підмінити таблицю віртуальних функцій таким чином, що виклик функції
someClass->doSomething()
призведе до виконання контрольованого атакуючим коду, тим самим перехопивши потік виконання програми.
У випадку ж використання Control Flow Guard, адреса викликається функції попередньо буде звірений з бітовою картою. Якщо відповідний біт дорівнює нулю, відбудеться програмне виняток.
При запуску цього додатка на ОС, які підтримують механізм Guard CF, завантажувач файлів побудує бітову карту і перенаправить адреса перевіряє процедури на функцію
ntdll!LdrpValidateUserCallTarget
.
Дана функція в ОС Windows 10 (build 1511) реалізована наступним чином:

Вивчимо алгоритм даної функції на прикладі вхідного адреси 0x0B3385B0.
B3385B016 = 10110011001110000101101100002
Перевіряється адреса дана функція отримує через регістр
ecx
. У регістр
edx
заноситься адреса бітової карти. В моєму випадку бітова карта розташувалася за адресою 0x01430000.

Три байти (24 біта) старшого порядку (підкреслені) адреси відповідають зсуву в бітовій карті. У даному разі зсув буде дорівнює
0xB3385
. Одиниця виміру бітової карти дорівнює 4 байтам (32 біта), тому для отримання потрібної комірки необхідно обчислити
базовий адресу карти зсув * 4
. Для даного прикладу отримуємо
0x01430000 + 0xB3385 * 4 = 0x16FCE14
. Значення клітинки бітової карти записується в регістр
edx
.

Цільову комірку отримали, тепер потрібно визначити номер цікавить нас біта. Номером є значення наступних 5 біт адреси (виділені жирним). Але потрібно враховувати, що якщо перевіряється адресу не вирівняно по кордоні 16 байт (
address & 0xf != 0
), то буде використовуватися непарний біт (
offset | 0x1
). В даному випадку адресу вирівняно і номер біта дорівнює 101102 = 2210.
Тепер залишається тільки перевірити значення біта, провівши bit test. Інструкція
bt
перевіряє значення першого біта регістра, порядковий номер якого береться з 5 молодших бітів (по модулю 32) другого регістра. У разі, якщо біт дорівнює 1, буде виставлений
Carry Flag (CF)
і програма продовжить своє виконання в звичайному режимі.
У іншому випадку буде викликана функція
ntdll!RtlpHandleInvalidUserCallTarget
і робота програми завершиться 29-го переривання з параметром 0xA на стеку, що означає
nt!_KiRaiseSecurityCheckFailure(FAST_FAIL_GUARD_ICALL_CHECK_FAILURE)
.

Перевіривши 22-ий біт, можна переконатися, що адреса викликається функції є валідним.
Реалізація даного алгоритму на Python виглядає наступним чином:
def calculate_bitmap_offset(addr):
offset = (addr >> 8) * 4
bit = (addr >> 3) % 32
aligned = (addr & 0xF == 0)
if not aligned:
bit = bit | 1
print "addr = 0x%08x, offset = 0x%x, bit index = %u, aligned? %s" % (addr, offset, bit, "так" if aligned else "ні")

calculate_bitmap_offset(0x0B3385B0)

Результат роботи скрипта:
addr = 0x0b3385b0, offset = 0x2cce14, bit index = 22, aligned? yes

Виключення
Не у всіх випадках виклик невалидной функції буде закінчуватися 29-им перериванням. У функції
ntdll!RtlpHandleInvalidUserCallTarget
відбуваються наступні перевірки:
  • Включений DEP для поточного процесу
  • Має цільового адресу необхідні права (
    PAGE_EXECUTE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_READ | PAGE_EXECUTE
    )
  • Дозволені "suppressed" виклики —
    ntdll!RtlGuardAllowSuppressedCalls
  • Є цільова адреса "suppressed" —
    ntdll!RtlpGuardIsSuppressedAddress
Псевдокод даної функції виглядає наступним чином:

Офіційна інформація про "suppressed" виклики відсутня. Можна лише сказати, що ці виклики вимагають підтримку компілятора — повинна бути встановлена маска
IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_MASK
у прапорах
GuardFlags
і компілятор повинен згенерувати розширену таблицю. В байтах, відповідних даній масці, зберігається значення додаткового розміру для елементів таблиці
GuardCFFunctionTable
. Якщо адреса функції є "suppressed", то байт, наступний за адресою, повинен бути дорівнює одиниці.
Дозволити "suppressed" виклики можна, наприклад, за допомогою гілки реєстру
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\
, встановивши параметр
CFGOptions
для необхідного додатка значення 1.
Слабкі місця Control Flow Guard
Як і будь-який захисний механізм, CFG має деякі слабкі місця:
  • Вимкнений DEP процесу тягне за собою втрату сенсу перевірок CFG, оскільки функція
    ntdll!RtlpHandleInvalidUserCallTarget
    в такому випадку буде завжди дозволяти виконання невалидного адреси.
  • Адреса бітової карти зберігається за фіксованим адресою і може бути легко обчислений з користувальницького режиму. Користувачеві заборонено модифікувати дані в бітовій карті, але эксплоитописатели так чи інакше знайдуть спосіб обійти це обмеження.
  • Якщо виконуваний файл не скомпільовано з підтримкою CFG, то підтримка CFG в підвантажуваних їм модулів втрачає сенс. Бітова карта та адресу процедури перевірки заповнюються лише за умови, що виконуваний файл підтримує механізм CFG, тому перевірки всередині коду модулів будуть простими заглушками.
  • CFG залежить від процесу компіляції, тому модулі сторонніх розробників і навіть старі модулі Microsoft є вразливим місцем в закритому CFG виконуваному файлі. Оскільки бітова карта складається з таблиці адрес валідних функцій, яка генерується компілятором, весь код модуля без підтримки CFG буде позначений в бітовій карті валідним адресатом.
  • За кожні 8 байт адресного простору відповідає 1 біт, але, насправді, 1 вирівняна адреса співвідноситься з одним парних бітом, при цьому наступний непарний біт співвідноситься відразу з 15 байтами адресного простору. Переконатися в цьому можна, запустивши наведений вище Python скрипт в циклі і проаналізувавши результат:
    addr = 0x08f38480, offset = 0x23ce10, bit index = 16, aligned? yes
    addr = 0x08f38481, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38482, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38483, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38484, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38485, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38486, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38487, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38488, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38489, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f3848a, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f3848b, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f3848c, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f3848d, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f3848e, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f3848f, offset = 0x23ce10, bit index = 17, aligned? no
    addr = 0x08f38490, offset = 0x23ce10, bit index = 18, aligned? yes
    addr = 0x08f38491, offset = 0x23ce10, bit index = 19, aligned? no
    addr = 0x08f38492, offset = 0x23ce10, bit index = 19, aligned? no
    addr = 0x08f38493, offset = 0x23ce10, bit index = 19, aligned? no
    addr = 0x08f38494, offset = 0x23ce10, bit index = 19, aligned? no
    ...

    З цього випливає, що у атакуючого є можливість викликати недоверенную функцію в безпосередній близькості від довіреної функції, за умови, що остання не вирівнятись.
  • Динамічно генеруються функції (наприклад, JIT-функції, потребують особливої уваги від розробників, оскільки необхідно забезпечити перевірки неявних викликів на стадії генерування функцій. В добавок до цього, необхідно враховувати, що поведінка функцій
    ntdll!NtAllocVirtualMemory
    та
    ntdll!NtProtectVirtualMemory
    полягає в проставленні одиничного біта для всього регіону пам'яті в бітовій карті Control Flow Guard, якщо пам'ять стає виконується (
    PAGE_EXECUTE_*
    ).
  • CFG не здатний запобігти перехоплення потоку виконання при модифікації атакуючим адреси повернення функції.
  • Виклики бібліотечних функцій (наприклад, WinAPI) з точки зору CFG є валідними, але атакуючому належить вирішити завдання наповнення стека/регістрів необхідними параметрами.
Реалізація обходу Control Flow Guard на прикладі Adobe Flash Player
Починаючи з Windows 8 плагін Adobe Flash Player інтегрований Internet Explorer, а з Windows 8.1 (Update 3) він поставляється з підтримкою CFG. Існує декілька реалізацій обходу Control Flow Guard в эксплоитах під Adobe Flash Player, деякі з яких актуальні і донині.


Обхід за допомогою динамічного коду
В Adobe Flash Player активно використовується JIT-компіляції, яка дозволяє уникати виконання такої ресурсномісткою операції, як інтерпретація коду ActionScript. Але, як було сказано раніше, динамічно генеруються функції вимагають додаткової уваги з боку розробників. Два методу обходу, описані нижче, є наслідком упущення розробників щодо роботи з виділенням пам'яті.

Відсутність перевірок неявних викликів в динамічному коді

Даний метод був запропонований і реалізований дослідником Francisco Falcón з Core Security в своєму анализе експлоїта для уразливості CVE-2015-0311. Оригінальна стаття досить добре і детально описує процес реалізації обходу. Суть методу полягає в модифікації внутрішньої таблиці віртуальних функцій певного ActionScript класу. Після чого один з методів даного класу повинен бути викликаний з тіла динамічно згенерованої функції. Для цієї цілі добре підходить клас
ByteArray
.
Структура об'єкта
ByteArray
:

По зсуву $+8 знаходиться вказівник на об'єкт класу
VTable
:

Клас
VTable
є внутрішнім поданням віртуальної таблиці функцій (тобто не тієї, яку генерує C++) для класів ActionScript.
Об'єкт цього класу містить в собі покажчики на об'єкти класу
MethodEnv
:

Даний клас являє собою опис методу ActionScript і містить вказівник на тіло функції в пам'яті по зсуву $+4.
об'єкт
VTable
зміщення $+D4 знаходиться опис методу
ByteArray::toString()
. Маючи можливість довільно читати і писати в пам'ять, атакуючий здатний змінити покажчик функції на тіло функції (
MethodEnv + 4
) і благополучно перехопити потік виконання програми, виконавши
ByteArray::toString()
.
Таке стає можливим унаслідок того, що метод даного класу буде викликатися з JIT-коду:

Як видно на скріншоті вище, неявний виклик відбувається без попередньої перевірки викликається адреси, оскільки ця функція була сгенерирована динамічно.
Даний метод обходу CFG був виправлений з виходом Adobe Flash Player версії 18.0.0.160 (KB3065820, Червень 2015). Виправлення полягає в наступному: якщо JIT-функція містить неявний виклик, то JIT-компілятор вставить виклик процедури перевірки безпосередньо перед неявним викликом.

Будь-яку адресу в межах тіла динамічної функції є валідним

Попередній метод обходу був можливий із-за недоліку в функції, яка виробляє неявний виклик. А цей метод можливий із-за недоліку в функції, яку неявно вызывют.
Дослідники Юрій Дроздов і Людмила Дроздова з Center of Vulnerability Research представили даний метод обходу CFG на конференції Defcon Russia (Санкт-Петербург, 2015) (презентация стаття). Їх метод заснований на тому факті, що при виділенні виконуваної пам'яті ядро виставляє одиничний біт в бітовій карті CFG для усієї виділеної пам'яті. Подивимося, до чого може призвести така поведінка.
Припустимо, що існує якась JIT-функція за адресою 0x69BC9080, в тілі якої знаходиться наступний код:

Що саме ця функція робить, нас не цікавить, потрібно лише звернути увагу на 2 байти
FF D0
інструкції за адресою 0x69BC90F0. Що буде, якщо початок функції раптом зрушиться в середину цієї інструкції? Ось що:

FF D0
— не що інше, як
call eax
! Ось так нешкідлива на перший погляд функція перетворилася в прекрасну мета для атакуючого — неявний виклик без перевірки Control Flow Guard. Треба лише розібратися з двома питаннями: як домогтися потрібної послідовності байтів і як записати в регістр необхідну адресу.
Згенерувати послідовність можна, просто експериментуючи з ActionScript-кодом. Варто лише враховувати той факт, що Nanojit (JIT-компілятор AVM) обфусцирует генерований код, тому легкого шляху не буде. Подивимося, у що перетворить Nanojit дану функцію:
public static function useless_func():uint
{
return 0xD5EC;
}

Результат:

Зовсім не те, що ми очікували. Досвідченим шляхом можна прийти, наприклад, до такого варіанту коду:
public static function useless_func():void
{
useless_func2(0x11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26);
}

public static function useless_func2(arg1:uint, arg2:uint, arg3:uint, a, b, c, d, e, f, g, h, i, j, k, l, m, n, p, q, r, s, t, u, v, w, x, y, z):void
{
}

Тіло першої функції буде містити наступні інструкції:

нас Цікавлять байти
FF 11
є інструкцією
call [ecx]
:

Неявний виклик отримали, тепер потрібно занести в регістр
ecx
контрольований адресу. З'ясуємо, що зберігається в даному регістрі в момент виклику функції
useless_func()
.

В момент виклику функції, в регістрі
ecx
знаходиться об'єкт класу
MethodEnv
. Перший DWORD даного класу є покажчиком на віртуальну таблицю функцій (ту, яку генерує компілятор C++). Ця таблиця в момент виклику методу
useless_func()
не використовується, тому нічого не заважає атакуючому безпосередньо перед викликом методу замінити вказівник на свій.
Реалізація даного алгоритму буде наступною:
var class_addr:uint = read_addr(UselessClass);
var vtable:uint = read_dword(class_addr + 8);
var methodenv:uint = read_dword(vtable + 0x54); // $+54 = useless_func
var func_ptr:uint = read_dword(methodenv + 4);

write_dword(methodenv + 4, func_ptr + offset_to_call_ecx);
write_dword(methodenv, rop_gadget); // ecx <- pointer to rop gadgets

UselessClass.useless_func(); // call [ecx]

Таким чином, нам вдалося перехопити потік виконання програми і перейти, в даному випадку, до виконання ROP-гаджета.
Цей метод обходу CFG було виправлено у версії 18.0.0.194 (KB3074219, Червень 2015). Виправлення полягає у використанні нового прапора
PAGE_TARGETS_INVALID/PAGE_TARGETS_NO_UPDATE
(0x40000000) для функцій
VirtualAlloc
та
VirtualProtect
в зв'язці з новою функцією WinAPI —
SetProcessValidCallTargets
.
Прапор
PAGE_TARGETS_INVALID
при виділенні виконуваної пам'яті виставляє нульовий біт для всієї ділянки пам'яті, а прапор
PAGE_TARGETS_NO_UPDATE
при зміні типу пам'яті на виконувану запобігає зміна бітової карти для цільового ділянки пам'яті.
Дане виправлення можна спостерігати у функції
AVMPI_makeCodeMemoryExecutable
в вихідному коді ядра AVM (AVMPI/MMgcPortWin.cpp):

Виклик функції
SetProcessValidCallTargets
реалізований
AVMPI_makeTargetValid
(AVMPI/MMgcPortWin.cpp):

З цього можна зробити висновок, що правильна послідовність дій при розміщенні в пам'яті динамічно генерованого коду в умовах роботи CFG повинна бути наступною:
  • VirtualAlloc(PAGE_READWRITE)
  • Запис коду у виділену ділянку
  • VirtualProtect(PAGE_EXECUTE_READ |
    PAGE_TARGETS_NO_UPDATE
    )
  • SetProcessValidCallTargets()
І, звичайно ж, не варто забувати про неявні виклики всередині динамічного коду.
Обхід за допомогою функцій WinAPI
Перевірка Control Flow Guard полягає в валідації викликається адреси, але валідними адресами є не тільки початку користувацьких функцій. Всі функції WinAPI, як і будь-які інші функції з таблиці імпорту, є валідними адресатами для неявного виклику. Атакуючому нічого не заважає перевести потік виконання безпосередньо в бібліотечну функцію, оминаючи тим самим виконання шеллкода (shellcode) або ROP-гаджетів. Гарним кандидатом для такої мети є WinAPI функція
kernel32!WinExec
.
Ця ідея вперше була озвучена дослідником Yuki Chen з Qihoo 360 Vulcan Team на конференції SyScan (Сінгапур, 2015) презентации, присвяченій експлуатації уразливості в Internet Explorer 11 з обходом нових механізмів захисту. Також на конференції BlackHat (США, 2015) дослідник Francisco Falcón описав реалізацію даного методу стосовно до Adobe Flash Player.
У своїй реалізації Francisco Falcón оперував методом
toString()
об'єкта класу
Vector
, але ми спробуємо реалізувати даний метод, користуючись напрацюваннями з попереднього.
Основна складність цього методу полягає в тому, щоб передати параметри
WinExec
через стек. Дана функція, згідно довідці, приймає 2 значення:
LPCSTR lpCmdLine
та
UINT uCmdShow
.
  • lpCmdLine
    — покажчик на рядок, яку потрібно виконати (повинна закінчуватися нульовим байтом).
  • uCmdShow
    — режим відображення запущеного додатку.
Звернемося до скріншоту з попереднього методу:

У момент виклику нашої функції на стеку виявляється 3 параметра. Третій параметр нас не цікавить. З другим все відмінно, оскільки 0 =
SW_HIDE
(додаток запуститься приховано). Першим параметром є вказівник на об'єкт
MethodEnv
.

Як ми вже з'ясували раніше, перші 4 байти даного об'єкта є покажчиком на віртуальну таблицю функцій, і в момент виклику ActionScript-методу вона ніяк не задіюється. Наступні 4 байти вказують на тіло функції, і саме їх потрібно змінити на покажчик функції
WinExec
.
Оскільки псування вказівника на тіло функції не призведе ні до чого хорошого, в нашому розпорядженні є лише перші 4 байти. У цей розмір можна, наприклад, розмістити рядок
cmd\0
(командний рядок Windows). Даною командою, звичайно, не домогтися повної компрометації системи, але для демонстрації підійде.
Модифікуємо алгоритм з попереднього методу і отримаємо наступний код:
var class_addr:uint = read_addr(UselessClass);
var vtable:uint = read_dword(class_addr + 8);
var methodenv:uint = read_dword(vtable + 0x50); // $+50 = useless_func

var winexec:uint = get_proc_addr("kernel32.dll", "WinExec");

write_dword(methodenv + 4, winexec); // useless_func() --> WinExec()
write_dword(methodenv, 0x00646d63); // '\0', 'd', 'm', 'c'

UselessClass.useless_func();

Пошук WinAPI функції в умовах сучасних Flash-експлойтів є тривіальною задачею. Дану реалізацію ми опустимо, але з нею можна ознайомитися завжди, вивчивши пакет Flash Exploiter з фреймворку Metasploit.
Виконавши наведений вище алгоритм при експлуатації уразливості, можна переконатися в працездатності даного методу:

Дана реалізація, незважаючи на свою працездатність і лаконічність, представляє малий інтерес для атакуючого, оскільки дає невеликий спектр можливостей.
Зразок для наслідування
Всі сучасні Flash-експлоїти так чи інакше використовують метод запуску корисного навантаження (payload) з витекли исходников експлойтів компанії HackingTeam. Автором даного методу є Віталій Торопов. Його метод заснований на виклик WinAPI функції
kernel32!VirtualProtect
, завдяки чому досягається обхід всіх механізмів захисту, в тому числі, Control Flow Guard.
Метою даного методу є метод
apply()
класу
Function
(core/FunctionClass.cpp)

У вихідному коді даного методу відбувається нативний виклик
core->exec->apply(get_callEnv(), thisArg, (ArrayObject*)AvmCore::atomToScriptObject(argArray));
, який можна перехопити, оперуючи об'єктами ActionScript.

Докладний опис цього методу вимагає окремої статті, але з реалізацією можна ознайомитися на GitHub. Також є хороший матеріал з розбором даного методу в умовах 64-бітного Flash блозі Metasploit.
Інші роботи з обходу Control Flow Guard
У даній статті були розглянуті методи обходу CFG при експлуатації вразливостей Adobe Flash Player. Але світ не крутиться навколо Flash, тому рекомендуємо ознайомитися з наступними дослідженнями, в яких зачіпається питання обходу Control Flow Guard в Internet Explorer 11.
  • Zhang Yunhai @ Black Hat 2015
    Перезапис read-only вказівника
    ___guard_check_icall_fptr
    використовуючи
    CustomHeap::Heap
    бібліотеки
    Jscript9
    .
  • Yuki Chen @ SyScan 2015
    Виклик функції WinAPI
    kernel32!LoadLibraryA
  • Rafal Wojtczuk & Jared DeMott @ DerbyCon 2015 (video), замітка в блозі Bromium Labs
    Цікаве дослідження, в якому представлена нова техніка — "десинхронізація стека" (stack desync). Ця техніка заснована на тому, що Control Flow Guard не здатний контролювати валідність адреси повернення функції. Модифікація адреси повернення досягається за рахунок виклику функції з невідповідним угодою про виклику (calling convention).
Висновок
Незважаючи на свої недоліки, Control Flow Guard при належній увазі з боку розробників є гарним доповненням в арсеналі борців з експлойтів в середовищі ОС Windows. Компанії Microsoft вдалося, хай і не повністю, реалізувати концепцію Control Flow Integrity, мінімально вплинувши на продуктивність додатків, і зберігши зворотну сумісність. Цей механізм ще не досяг межі своїх можливостей, і розробники з Microsoft, напевно, в найближчому майбутньому зможуть посилити захист додатків.
Хочеться сподіватися, що всі розробники додатків задумаються над сучасною захистом від експлуатації вразливостей і додадуть в свої продукти підтримку CFG.
Подібні механізми захисту з'являються і на рівні заліза. Компанія Intel, наприклад, випустила специфікацію їх нової технології, націлену на протидію ROP-атак — CET (Control-flow Enforcement Technology) стаття на хабре). В добавок до кращої продуктивності, CET позбавлений багатьох недоліків Control Flow Guard.
Джерела
Jack Tang, Trend Micro Threat Solution Team. Exploring Control Flow in Windows Guard 10.
mj0011, Qihoo 360 Vulcan Team. Windows 10 Control Flow Guard Internals.
Source code for the Actionscript virtual machine, GitHub.
Francisco Falcon, Core Security. Exploiting Adobe Flash Player in the era of Control Flow Guard.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.