Шприц для bsd или функции на игле

Перехват функций не во сне, а наяву

Самое сложное в перехвате— это определить границы машинных инструкций, поверх которых записывается команда перехода на перехватчик (он же thunk, расположенный в нашем случае в теле функции gets). По-хорошему, для решения этой задачи требуется написать мини-дизассемблер, но… это же сколько всего писать придется! А можно ли без него обойтись? Можно!
В начале большинства библиотечных функций расположен стандартный пролог вида PUSH EBP/MOV EBP,ESP/SUB ESP,XXXh (55h/89h E5h/ 83h ECh XXh), дающий нам пять байт — необходимый минимум для внедрения! Встречаются и другие, слегка видоизмененные прологи, например: PUSH EBP/MOV EBP,ESP/PUSH EDI/PUSH ESI (55h/89h E5h/ 57h/ 56h); PUSH EBP/MOV EAX, 0FFFFFFFFh/MOV EBP, ESP (55h/B8h FFh FFh FFh FFh/89h E5h); PUSH EBP/XOR EAX, EAX/MOV EBP,ESP (55h/31h C0h/89h E5h). Хороший перехватчик должен их учитывать.
Перехват функций не во сне, а наяву
Рисунок 4 функция gettimer с прологом далеким от стандартного
Таким образом, наш перехватчик должен проверить первые 5 байтов перехватываемой функции и, если они совпадают со стандартным (или слегка оптимизированным) прологом, скопировать этот пролог в свое тело и выполнить его перед передачей управления оригинальной функции. А куда его можно скопировать? Сегмент данных, как уже говорилось, нам недоступен, стек трогать нельзя (перед передачей управления на функции он должен быть восстановлен), а сегмент кода запрещен от модификации.
Существует по меньше мере три решения: во-первых, мы можем вызывать функцию mprotect, присвоив кодовой странице атрибут writable, (но это некрасиво), во-вторых, трогать стек все-таки можно: забрасываем пролог на верхушку, забрасываем туда же копию всех аргументов (а сколько у функции аргументов? да хрен его знает, вот и приходится копировать с запасом) и передаем ей управление как ни в чем не бывало (но это уже не просто "некрасиво", это вообще уродство).


В-третьих, мы можем поступить так:

// "коллекция" разнообразных прологов для сравнения

unsigned char prolog_1[]={0x55h,0x89,0xE5,0x83,0xEC};

unsigned char prolog_2[]={0x55,0x89,0xE5,0x57,0x56};

// буфер в который будет записан сгенерированный код

unsigned char buf_code[1024];

// определяем адрес перехватываемой функции

p = msym(base, fnc_name);

// если в начале перехватываемой функции расположен prolog_1

// внедряем в ее начало call на prepare_prolog_1

if (!memcmp(p,prolog_1,sizeof(prolog_1))

       call_r(base, fnc_name, "gets", 0);

// если в начале перехватываемой функции расположен prolog_2

// внедряем в ее начало call на prepare_prolog_2

if (!memcmp(p,prolog_1,sizeof(prolog_2))

       call_r(base,fnc_name,"gets", offset prapare_prolog_2-offset prepare_prolog_1);

Листинг 3 фрагмент программы-инсталлятора, анализирующей пролог перехватываемой функции и устанавливающей обработчик с соответствующим прологом

; // заносим номер "нашего" пролога в регистр EAX,

; // чтобы перехватчик знал какой ему пролог эмулировать

; // ВНИМАНИЕ! этот код засирает EAX

и не работает на fastcall-функциях,

; // для поддержки которых регистры трогать нельзя, а номер пролога класть на стек,

; // восстанавливая его перед передачей управления оригинальной функции

prepare_prolog_1:

       MOV EAX, 0x1

       JMP short do_begin

      

prepare_prolog_2:

       MOV EAX, 0x2

       JMP short do_begin

      

prepare_prolog_n:

       MOV EAX, 0x2

       JMP do_begin

do_begin:

       // ОСНОВНОЙ КОД ПЕРЕХВАТЧИКА

       // ДЕЛАЕМ ЧТО ЗАДУМАНО

       // [ESP+4]+5 содержит адрес вызванной функции

       // это поможет нам отличить перехваченные функции друг от друга

       …

       …

       …

       // ПЕРЕДАЧА УПРАВЛЕНИЯ ПЕРЕХВАЧЕННОЙ ФУНКЦИИ

       // С ЭМУЛЯЦИЕЙ ЕЕ "РОДНОГО" ПРОЛОГА

       DEC EAX

       JZ prolog_1

       DEC EAX

       JZ prolog_2


       …

      

prolog_1: ; // эмулируем выполнение пролога типа PUSH EBP/MOV EBP,ESP/SUB ESP,XXX

       PUSH EBP

       MOV  EBP,ESP

       SUB ESP, byte ptr [EAX]    ; берем XXh из памяти

       INC EAX                           ; на след. машинную команду

       JMP EAX

      

prolog_2: ;// эмулируем

выполнение пролога

типа PUSB EBP/MOV EBP,ESP/PUSH EDI/PUSH ESI

       PUSH EBP

       MOV EBP, ESP

       PUSH EDI

       PUSH ESI

       JMP EAX

Листинг 4 базовый код перехватчика (расположенный в gets), поддерживающий несколько различных прологом

Программа-инсталлятор анализирует пролог перехватываемой функции и, в зависимости от результата, внедряет в ее начало либо call prepare_prolog_1 либо call prepare_prolog_2, где prepare_prolog_x – метка, расположенная внутри thunk-кода, помещенного нами в функцию gets. Команда call занимает 5 байт и потому в аккурат накладывается на команду SUB ESP,XXh так, что XXh оказывается прямо за ее концом. Поэтому, сохранять XXh в теле самого перехватчика не нужно!!! Команда SUB ESP, byte ptr [EAX], вызываемая из thunk-кода эмулирует выполнение SUB ESP,XXh на ура!

Приведенный пример портит регистр EAX и работает только с cdecl и stdcall функциями. Перехват fastcall-функции, передающих аргументы через EAX, по этой схеме невозможен. Однако, оригинальный EAX можно сохранять в стеке и восстанавливать непосредственно перед передачей управления перехваченной функции, но в этом случае JMP EAX придется заменить на RETN, а на верхушку стека предварительно положить адрес для перехода.

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

Шприц для *bsd или функции на игле

крис касперски ака мыщъх, no fucking e-mail
внедриться в адресное пространство чужого процесса — проще простого! в первой части статьи мы показали как сконструировать универсальный шприц-баян, теперь остается только замутить тот фармацевтический ### магический раствор, который будет введен внутрь чужеродных клеток машинного кода. навстречу нашей вакцине тут же устремится батальон иммунных тел, готовых сожрать ее в один момент (и ведь сожрут же!), но мыщщъх знает один хитрый рецепт…

>>> Врезка проблемы стабильности

Сконструированный нами перехватчик довольно капризен по натуре и периодически падает без всяких видимых причин. Почему это происходит? Рассмотрим функцию func со стандартным прологом вида PUSH EBP/MOV EBP,ESP. Допустим, процесс A выполнил команду PUSH EBP и только собирался приступить к выполнению MOV EBP,ESP как был прерван системным планировщиком и управление получил наш процесс B, осуществляющий перехват функции func путем внедрения в ее начало инструкции call. Когда процесс A возобновит свое выполнение, команды MOV EBP ESP там уже и не окажется, а будет торчать хвостовая часть от call при выполнении которой все пойдет в разнос. Конечно, вероятность такого события исчезающе мала, но в особо ответственных случаях с ней все-таки стоит считаться.
>>> Врезка проблемы стабильности
Рисунок 5 стандартный пролог функции getaddrinfo, испорченный несвоевременным переключением планировщика
Чтобы "обезопасить" перехват, необходимо сократить длину внедряемой инструкции до одного байта, но… таких инструкций просто нет! То есть как это нет? А INT 03h (CCh) на что? Традиционно она используется для организации точек останова и на прикладном уровне защищенного режима возбуждает исключение, которое легко перехватить из ядра, а точнее из загружаемого модуля. Об этом уже писалось в статье "Handling Interrupt Descriptor Table for fun and profit", опубликованной в 59 номере phrack, так что не будем повторяться.
Заметим, что CCh конфликтует с некоторыми защитными механизмами и, естественно, с отладчиками, поэтому лучше внедрять не INT 03h, а какую-нибудь "запрещенную" однобайтовую команду типа CLI (FAh), возбуждающую исключение, которое мы будем отлавливать.

>>> Врезка точки останова

Отладчики могут устанавливать программные точки останова на библиотечные функции, внедряя в их начало команду INT03h (CCh). В этом случае наш перехватчик не сможет распознать пролог, что не есть хорошо.
>>> Врезка точки останова
Рисунок 6 стандартный пролог функции lchmod, искаженной программной точной останова (CCh), установленной отладчиком
Выход очевиден — сравнивать только 2й, 3й, 4й и 5й байты пролога, игнорируя 1й байт. А что делать с точкой останова? Если записать call поверх нее, то она будет затерта и отладчик потеряет контроль за функцией, что в некоторых случаях неприемлемо и тогда необходимо внедряться со 2го байта, но в этом случае команда call полностью затрет SUB ESP,XXh и XXh придется сохранять где-то в другом месте.

функций под windows хорошо исследованы

Механизмы перехвата API- функций под windows хорошо исследованы и предложить радикально новый трюк довольно трудно. UNIX-системы исследованы намного хуже и таят множество нераскрытых возможностей, притягивающий хакеров и прочих творческих людей. Описанный мыщъх'ем способ — не единственный и, вероятно, не самый удобный, к тому же до "промышленного применения" ему еще расти и расти. Тем не менее, в мыщъхиных утилитах, написанный на скорый хвост, он вполне нормально работает — как под linux'ом, так и под BSD.



    Работа с информацией: Безопасность - Защита - Софт - Криптография