Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #88, АПРЕЛЬ 2006 г.

Системный шпионаж в *nix часть 2

Крис Касперски ака мыщъх

Хакер, номер #088, стр. 088-098-4


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);

Теперь заносим номер "нашего" пролога в регистр EAX, чтобы перехватчик знал, какой ему пролог эмулировать.

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

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

Программа-инсталлятор анализирует пролог перехватываемой функции и в зависимости от результата внедряет в ее начало либо 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, а на верхушку стека предварительно положить адрес для перехода.

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

[проблемы стабильности]

Сконструированный нами перехватчик довольно капризен по натуре и периодически падает без всяких видимых причин. Почему это происходит? Рассмотрим функцию func со стандартным прологом вида PUSH EBP/MOV EBP,ESP. Допустим, процесс A выполнил команду PUSH EBP и только собирался приступить к выполнению MOV EBP,ESP, как был прерван системным планировщиком, и управление получил наш процесс B, осуществляющий перехват функции func путем внедрения в ее начало инструкции call. Когда процесс A возобновит свое выполнение, команды MOV EBP ESP там уже не окажется, а будет торчать хвостовая часть от call, при выполнении которой все пойдет вразнос. Конечно, вероятность такого события крайне мала, но в особо ответственных случаях с ней все-таки стоит считаться.

Назад на стр. 088-098-3  Содержание  Вперед на стр. 088-098-5