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