Несетевая защита Крис Касперски ака мыщъх Спецвыпуск: Хакер, номер #051, стр. 051-100-6 Дерево процессов В Linux при нормальном исполнении программы идентификатор родительского процесса (Ppid) всегда равен и идентификатору сессии (Sid), а при запуске под отладчиком Ppid и Sid различны (см. таблицу). Однако в других операционных системах (например, во FreeBSD) это не так, и Sid отличается от Ppid даже вне отладчика. Как следствие, программа, защищенная по этой методике, дает течь, отказывается выполняться даже у честных пользователей. Личное наблюдение: при нормальном исполнении программы под FreeBSD идентификатор текущего процесса существенно отличается от идентификатора родительского, а при запуске из-под отладчика идентификатор родительского процесса оказывается меньше ровно на единицу. Таким образом, законченный пример реализации может выглядеть так: main () { if ( (getppid() != getsid(0)) && ((getppid() + 1) != getppid()) printf("get out, debugger!\n"); else printf("all ok!\n"); } Сигналы, дампы и исключения Следующий прием основан на том факте, что большинство отладчиков жестко держат SIGTRAP-сигналы (trace/breakpoint trap) и не позволяют отлаживаемой программе устанавливать свои собственные обработчики. Как можно использовать это для защиты? Устанавливаем обработчик исключительной ситуации посредством вызова Signal (SIGTRAP, handler) и спустя некоторое время выполняем инструкцию INT 03. При нормальном развитии событий управление получает Handler, а при прогоне программы под GDB происходит аварийный останов с возвращением в отладчик. При возобновлении выполнения программа продолжает исполняться с прерванного места, при этом Handler так и не получает управления. Имеет смысл повесить на него расшифровщик или любую другую "отпирающую" процедуру. Это очень мощный антиотладочный прием, единственный недостаток которого заключается в привязанности к конкретной аппаратной платформе – в данном случае к платформе Intel. Конкретный пример реализации выглядит так: #include <signal.h> void handler(int n) { /* обработчик исключения */ } main() { // устанавливаем обработчик на INT 03 signal(SIGTRAP, handler); //… // вызываем INT 03, передавая управление handler'у // или отладчику (если он есть) __asm__("int3"); // зашифрованная часть программы, // расшифровываемая handler'ом printf("hello, world!\n") } Распознавание программных точек останова Программные точки останова (машинная команда INT 03h с опкодом CCh) распознаются обычным подсчетом контрольной суммы собственного кода программы. Поскольку порядок размещения функций в памяти в общем случае совпадает с порядком их объявления в исходном тесте, адрес конца функции равен указателю на начало следующей функции. Контроль целостности своего кода как средство обнаружения программных точек останова foo() {/* контролируемая функция 1 */ } bar() {/* контролируемая функция 1 */ } main() { int a; unsigned char *p; a = 0; for (p = (unsigned char*)foo; p < (unsigned char*)main; p++) a += *p; if (a != _MY_CRC) printf ("get out, debugger!\n"); else printf ("all ok\n"); } Мы трассируем, нас трассируют |