Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

articles:core-linux [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== core-linux ======
 +<​sub>​{{core-linux.odt|Original file}}</​sub>​
 +
 +====== разбор коры в Linux и xBSD ======
 +
 +крис касперски ака мыщъх, no-email
 +
 +**как умру, похороните,​ на могильной плите напишите:​ ****"​segmentation fault (core dumped)"​****… когда никсовая программа умирает,​ ее кора отделяется от эльфа и попадает в специальный файл, хранящий последний вздох усопшей. это словно "​черный ящик",​ устанавливаемый на борту самолета и позволяющий реконструировать причины катастрофы при падении. записанная на древнем языке машинных кодов, кора подвластна только гуру, магам и чародеям. однако,​ с помощью магического свитка этой статьи даже простые смертные юзеры смогут приобщится к тайне, выучив пару-тройку волшебных заклинаний**
 +
 +===== введение =====
 +
 +При возникновении необрабатываемого исключения внутри прикладной программы,​ центральный процессор возбуждает исключение и операционная система завершает работу приложения в аварийном режиме,​ сопровождая это знаменитой надписью "​segmentation fault"​и (при правильно выставленных лимитах) сбрасывает дамп памяти в специальный core-файл,​ в просторечии называемый "​корой"​. Кора содержит все сегменты ELF-файла (код, данные),​ содержимое стековой и динамической памяти. (//​примечание:​ некоторые источники утверждают — см. напр. en.wikipedia.org/​wiki/​Core_dump,​ что кора содержит все пользовательское пространство процесса,​ но это не совсем верно, точнее совсем неверно. кора обладает собственным форматом и состоит из секций,​ в которых попадают лишь _значимые_ данные,​ в частности _выделенные_ блоки динамической памяти,​ в результате чего размер коры приблизительно равен объему памяти,​ занятым процессом — от сотен килобайт до нескольких мегабайт,​ в то время как адресное пространство занимает от 2х до 3х гигабайт на ////​x86-////​машинах).//​
 +
 +Большинство пользователей удаляют кору не задумываясь,​ некоторые отсылают ее разработчикам,​ в надежде,​ что это поможет им выловить баг и как следует его прищемить,​ чтобы он не беспедельничал,​ однако,​ надеяться на разработчиков — все равно, что ждать у моря погоды (особенно в Open-Source проектах,​ поддерживаемых на голом энтузиазме при полной нехватке времени и финансов).
 +
 +На самом деле, покопавшись в коре можно определить причины сбоя и самостоятельно,​ а в некоторых случаях и восстановить не сохраненные оперативные данные. Для этого нам потребуется дизассемблер IDA Pro в последнюю версию которого встроен мощный декомпилятор,​ превращающий машинный код в структурированный листинг на Си, существенно снижающий планку требований,​ предъявляемых к кодокопателю. Знание ассемблера становится необязательным,​ а сам анализ радикально упрощается и ускоряется.
 +
 +Где взять IDA Pro? Странный вопрос,​ не правда ли? Напоминает ситуацию при загнивающем социализме. Все жалуется,​ что мяса нет, но на содержимом холодильника это обстоятельство никак не сказывается. Короче,​ тот кто ищет, тот всегда найдет… ну, а если не найдет,​ так позаимствует. Теоретически,​ для анализа коры можно воспользоваться утилитой objdump, входящей практически в каждый дистрибутив,​ однако,​ она не отображает символьные имена библиотечных функций и форматирует неудобочитаемый листинг. Словом,​ мыщъх категорически не рекомендует ее начинающим.
 +
 +Отладчик gdb представляет собой компромиссный вариант. Это уже не objdump, но и не IDA Pro. Если IDA Pro предоставляет оконный интерфейс,​ с которым можно разобраться и методом тыка, то с gdb этот номер уже не пройдет,​ а многочисленные графические "​морды"​ (недостатка в которых не ощущается) в основном ориентированы на отладчку файлов с исходными текстами и для работы с корой неудобны.
 +
 +Существуют так же и специальные анализаторы коры – как коммерческие (фу, сразу на фиг), так и бесплатные (например,​ Introspector,​ созданный James'​ом Michael'​ем из корпорации DuPont — http://​introspector.sourceforge.net).
 +
 +Вот эти инструменты мы и будем использовать! Но довольно лясы точить! А разгребать кору кто будет?
 +
 +===== подготовка к магическому ритуалу — создание алтаря =====
 +
 +Для экспериментов с корой нам потребуется стендовая программа,​ с умышленной ошибкой фатального типа — попытке открытия заведомо несуществующего файла с последущим чтением его содержимого без проверки успешности выполнения операции,​ в результате чего переменная f оказывается равной нулю и при обращении к ней произойдет исключение,​ приводящее к аварийному завершению программы и возможно к сбросу коры.
 +
 +#include <​stdio.h>​
 +
 +#include <​malloc.h>​
 +
 +#define S"​nezumi-souriz-elraton-"​
 +
 +#define N0x666
 +
 +#define M(N*sizeof(S)+1)
 +
 +main()
 +
 +{
 +
 +int a; char buf[N]; char *p; FILE *f;
 +
 +
 +
 +// heap-test (must be found in core)
 +
 +p = malloc(M); for(a=0;​a<​N;​a++) strcpy(p+a*strlen(S),​S);​
 +
 +
 +
 +// stack-test (must be found in core)
 +
 +printf("​tell me your name, plz!\n"​);​ fgets(buf, N - 1, stdin);
 +
 +
 +
 +// non-exsisten file
 +
 +f = fopen("​kpnc.dat","​rw"​);​
 +
 +
 +
 +// crash! f == 0
 +
 +fread(buf, N, 1, f);
 +
 +
 +
 +return 0;
 +
 +}
 +
 +Листинг 1 стендовая программа test-core-usr.c
 +
 +Набираем ее в любом текстовом редакторе (мыщъх рекомендует vi — см. рис. 1,​ — хотя, о вкусах,​ как говориться не спорят).
 +
 +{{core-linux_Image_0.png?​552}}
 +
 +Рисунок 1 набивка программы в самом хакерском редакторе всех времен и народов — в знаменитом vi
 +
 +Компилируем ("​$gcc test-core-usr.c -o test-core-usr"​) и запускаем образовавшийся файл ./​test-core.usr на выполнение,​ который спрашивает наше имя и тут же грохается с сообщением "​segmentation fault"​ – "​ошибка сегментации"​ или "​segmentation fault (code dumped)"​ – "​ошибка сегментации (кора сброшена)"​ — (см. рис. 2).
 +
 +Политика сброса коры определяется настройками,​ о которых мы поговорим позднее,​ а пока удалим исходный текст и попытается реконструировать ход событий,​ приведший к обрушению программы. Это совсем не сложно сделать!
 +
 +{{core-linux_Image_1.png?​552}}
 +
 +Рисунок 2 реакция системы на критическую ошибку в прикладной программе:​ аварийное завершение без сброса (слева) и со сбросом коры (справа)
 +
 +===== охота на исключения с последующим допросом =====
 +
 +В штатную поставку большинства дистрибутивов входит утилита "​catchsegv"​ (своеобразный аналог "​Доктора Ватсона"​ в Windows), которой мы сейчас и воспользуемся. Просто запускаем catchsegv с именем подопытной программы в качестве аргумента и, дождавшись ее запуска,​ вводим свое имя, нажимаем <​ENTER>​ и… все!!! Баста! Процессор генерирует исключение доступа по нулевому указателю (которым в данном случае является переменная f), передавая бразды правления утилите catchsegv, выводящей на экран содержимое стека вызовов,​ регистров,​ карту памяти и т. д. (см. листинг 2).
 +
 +root@9[core-gdb]#​ catchsegv ./​test-core-usr
 +
 +tell me your name, plz!
 +
 +KPNC%69
 +
 +*** Segmentation fault
 +
 +Register dump:
 +
 + ​EAX:​00000000EBX:​4015c620ECX:​08056bc0EDX:​4015d0a0
 +
 + ​ESI:​00000001EDI:​00000666EBP:​bffff3d8ESP:​bffff3ac
 +
 + ​EIP:​4008d3e3EFLAGS:​00000206
 +
 + ​CS:​0023DS:​002bES:​002bFS:​0000
 +
 + ​GS:​0000SS:​002b
 +
 + ​Trap:​0000000eError00000004OldMask:​80000000
 +
 + ​ESP/​signal:​bffff3acCR2:​00000000
 +
 + 
 +
 +** Backtrace:​**
 +
 +** /​lib/​libc.so.6(_IO_fread+0x33)[0x4008d3e3]**
 +
 +** ??:​0(main)[0x80485a5]**
 +
 +** /​lib/​libc.so.6(__libc_start_main+0xc6)[0x40042dc6]**
 +
 +** ../​sysdeps/​i386/​elf/​start.S:​105(_start)[0x8048431]**
 +
 + 
 +
 + ​Memory map:
 +
 + 
 +
 + ​08048000-08049000 r-xp 00000000 08:01 115530 /​home/​core-gdb/​test-core-usr
 +
 + ​08049000-0804a000 rw-p 00000000 08:01 115530 /​home/​core-gdb/​test-core-usr
 +
 + ​0804a000-0806b000 rwxp 00000000 00:00 0
 +
 + ​40000000-40016000 r-xp 00000000 08:01 222502 /​lib/​ld-2.3.2.so
 +
 + ​40016000-40017000 rw-p 00015000 08:01 222502 /​lib/​ld-2.3.2.so
 +
 + ​40017000-40018000 rw-p 00000000 00:00 0
 +
 + ​40018000-4001b000 r-xp 00000000 08:01 222506 /​lib/​libSegFault.so
 +
 + ​4001b000-4001c000 rw-p 00002000 08:01 222506 /​lib/​libSegFault.so
 +
 + ​4001c000-4001e000 rw-p 00000000 00:00 0
 +
 + ​4002d000-40155000 r-xp 00000000 08:01 222518 /​lib/​libc-2.3.2.so
 +
 + ​40155000-4015d000 rw-p 00127000 08:01 222518 /​lib/​libc-2.3.2.so
 +
 + ​4015d000-40160000 rw-p 00000000 00:00 0
 +
 + ​bfffe000-c0000000 rwxp fffff000 00:00 0
 +
 +Листинг 2 информация,​ отловленная утилитой catchsegv в момент возникновения исключения
 +
 +От обилия информации можно и растеряться,​ но мы же хакеры,​ а не пионеры там какие-то,​ так что не будет паниковать и обратим свой взор к строке "​backtrace",​ содержащей стек обратных вызовов. Самая верхняя строчка была выполнена последней. В нашем случае это: "/​lib/​libc.so.6(_IO_fread+0x33)[**0x4008d3e3**]"​ — команда,​ расположенная по адресу 4008d3E3h, что соответствует смещению 33h байт от начала функции _IO_fread(),​ реализованной в библиотеке /​lib/​libc.so.6.
 +
 +Ковыряться в библиотечных функциях утомительно,​ да и бесполезно. Во-первых,​ они уже давно вылизаны,​ а во-вторых (и это более важно),​ функция _IO_fread с вероятностью близкой к единице тут совсем не причем. Скорее всего, вызывающий ее код, передал ей неправильные аргументы. А где этот код расположен?​
 +
 +Смотрим на вторую строчку стека вызовов:​ "??:​0(main)[0x80485A5]"​. Ага! Интересующий нас код расположен по адресу 80485A5h, в который легко заглянуть дизассемблером. Берем IDA Pro, загружаем test-core-usr и нажимаем <G> для перехода к инструкции 80485A5h, которой оказывается машинная команда movsx, следующая за "call _fread"​ (см. рис. 3#​1).
 +
 +{{core-linux_Image_2.png?​553}}
 +
 +Рисунок 3 поиск причины сбоя в IDA Pro
 +
 +Мы знаем, что исключение произошло в функции _fread (являющейся оберткой вокруг _IO_fread), поэтому анализировать необходимо код, непосредственно предшествующий ее вызову,​ при просмотре которого в глаза бросается вызов _fopen (см. рис. 3#​2),​ пытающийся открыть несуществующий файл "​kpnc.dat"​ (см. рис. 3#​3),​ причем результат,​ возвращенный _fopen,​никак не проверяется,​ что и приводит к падению. Создание файла kpnc.dat в текущем каталоге восстанавливает работоспособность программы (см. рис 4). Пример,​ конечно,​ слегка надуманный,​ но большинство приложений ремонтируются аналогичным образом (с той лишь разницей,​ что на анализ уходит намного больше времени).
 +
 +{{core-linux_Image_3.png?​550}}
 +
 +Рисунок 4 после создания файла "​kpnc.dat"​ падения программы прекращаются
 +
 +А что делать тем, у кого нету IDA Pro и навряд ли появится?​ Что ж, попробуйте использовать штатную утилиту objdump, запущенную с ключом -d и именем анализируемого файла, только учтите,​ что в листинге не окажется ни символьных имен функций,​ ни содержимого ASCIIZ-строк,​ ни… прочих прелестей прогресса,​ делающих жизнь удобных в мелочах,​ поэтому все недостающую информацию придется добывать вручную (см. листинг 3),​ теряя на это огромное количество времени (более подробно о дизассемблировании программ под UNIX рассказывается во втором издании моей книги "​hacker disassembling uncovered",​ которая сейчас готовится к печати).
 +
 +**$objdump -d ./​test-code-usr**
 +
 +test-core-usr: ​ формат файла elf32-i386
 +
 +Диассемблирование раздела .init:
 +
 +0804836c <​_init>:​
 +
 +
 +
 +Диассемблирование раздела .plt:
 +
 +08048384 <​.plt>:​
 +
 +
 +
 +Диассемблирование раздела .text:
 +
 +08048410 <​_start>:​
 +
 +
 +
 +080484d4 <​main>:​
 +
 +
 +
 + ​8048555:​8d 85 78 f9 ff fflea0xfffff978(%ebp),​%eax
 +
 + ​804855b:​89 04 24mov%eax,​(%esp)
 +
 + ​804855e:​e8 51 fe ff ffcall80483b4 <​_init+0x48>;​ **_fgets**
 +
 + ​8048563:​c7 44 24 04 f4 86 04movl$0x80486f4,​0x4(%esp);​ **"​rb"​**
 +
 + ​804856b:​c7 04 24 f7 86 04 08movl$0x80486f7,​(%esp);​ **"​kpnc.dat"​**
 +
 + ​8048572:​e8 6d fe ff ffcall80483e4 <​_init+0x78>;​** _fopen**
 +
 + ​8048577:​89 85 70 f9 ff ffmov%eax,​0xfffff970(%ebp)
 +
 + ​804857d:​8b 85 70 f9 ff ffmov0xfffff970(%ebp),​%eax
 +
 + ​8048583:​89 44 24 0cmov%eax,​0xc(%esp)
 +
 + ​8048587:​c7 44 24 08 01 00 00movl$0x1,​0x8(%esp)
 +
 + ​804858f:​c7 44 24 04 66 06 00movl$0x666,​0x4(%esp)
 +
 + ​8048597:​8d 85 78 f9 ff fflea0xfffff978(%ebp),​%eax
 +
 + ​804859d:​89 04 24mov%eax,​(%esp)
 +
 + ​80485a0:​e8 ff fd ff ffcall80483a4 <​_init+0x38>;​ **_fread**
 +
 + ​80485a5:​b8 00 00 00 00mov$0x0,​%eax
 +
 + ​80485aa:​c9leave
 +
 + ​80485ab:​c3ret
 +
 +Листинг 3 дизассемблирование программы с помощью штатной утилиты objdump (все комментарии расставлены автором)
 +
 +===== поиск и добыча коры в заповедном лесу Linux и xBSD =====
 +
 +FreeBSD по умолчанию сбрасывает кору в файл, имеющий то же самое имя, что и упавшая программа,​ с добавлением расширения "​.core"​ и сохраняемый в одной директории с программой. Кора доступа только администратору,​ что идеологически правильно,​ поскольку доступ к дампу памяти может повлечь за собой утечку конфиденциальной информации.
 +
 +Часть дистрибутивов Linux'​а сбрасывают кору в файл "​core",​ находящийся в одной директории с упавшей программой (что при падении нескольких программ вызывает путаницу и прочие неудобства). Часть же — не сбрасывает ее вообще! При этом, после сообщения "​segmentation fault" строка "(core dumped)"​ отсутствует. Чем вызван такой беспредел?​! Очень просто — поскольку рядовые пользователи Linux'​а обладают крайне невысокой квалификацией,​ кора оседает мощными пластами,​ транжирящими дисковое пространство,​ которое просто некому подчистить! Но даже те немногие,​ кто знают, что такое кора, практически никогда не пользуются ею по прямому назначению,​ а немедленно стирают. Вот составители дистрибутивов и пошли им на встречу,​ запретив сброс коры установкой лимитов.
 +
 +Узнать текущее состояние лимитов можно с помощью штатной утилиты "​ulimit",​ запущенной с ключом "​-a"​. Результат ее выполнения на мыщъхином компьютере следующий:​
 +
 +root@6[core-gdb]#​ ulimit -a
 +
 +**core file size(blocks,​ -c)0**
 +
 +data seg size(kbytes,​ -d)unlimited
 +
 +file size(blocks,​ -f)unlimited
 +
 +max locked memory(kbytes,​ -l)unlimited
 +
 +max memory size(kbytes,​ -m)unlimited
 +
 +open files(-n)1024
 +
 +pipe size(512 bytes, -p)8
 +
 +stack size(kbytes,​ -s)unlimited
 +
 +cpu time(seconds,​ -t)unlimited
 +
 +max user processes(-u)1024
 +
 +virtual memory(kbytes,​ -v)unlimited
 +
 +Листинг 4 просмотр текущих лимитов
 +
 +Как видно, предельный размер файла коры выставлен в ноль, а потому кора и не создается. Изменить статус-кво можно (и нужно!) с помощью все той же утилиты "​ulimit",​ запущенной следующим образом:​
 +
 +**root@6[core-gdb]#​ ****ulimit -c unlimited**
 +
 +root@6[core-gdb]#​ ulimit -a
 +
 +**core file size(blocks,​ -c)**unlimited
 +
 +data seg size(kbytes,​ -d)unlimited
 +
 +file size(blocks,​ -f)unlimited
 +
 +max locked memory(kbytes,​ -l)unlimited
 +
 +max memory size(kbytes,​ -m)unlimited
 +
 +open files(-n)1024
 +
 +pipe size(512 bytes, -p)8
 +
 +stack size(kbytes,​ -s)unlimited
 +
 +cpu time(seconds,​ -t)unlimited
 +
 +max user processes(-u)1024
 +
 +virtual memory(kbytes,​ -v)unlimited
 +
 +Листинг 5 снятие лимитов с коры
 +
 +Вот теперь другое дело!!! Теперь кора будет создаваться всегда! (Если, конечно,​ дискового места хватит). Маленькое замечание мимоходом:​ установка лимитов носит локальный характер (только для данного пользователя) и после перезапуска системы лимиты будут восстановлены в значения по умолчанию,​ так что смело экспериментируйте без риска угробить систему.
 +
 +Кстати,​ кора может быть сброшена с приложения в любой момент,​ независимо от его предрасположенности к падению (это может потребоваться,​ например,​ для снятия дампа с упакованного файла).
 +
 +Набираем в командной строке:​ "​$kill -3 <​pid>",​ где <​pid>​ – идентификатор процесса,​ с которого необходимо содрать кору (сам процесс при этом будет завершен). И (если только процесс активно не сопротивляется дампу) файл коры тут же образуется в текущем каталоге процесса.
 +
 +===== анализ коры различными средствами =====
 +
 +И вот, после стольких мучений,​ файл коры лежит перед нами. Что же с ним можно сделать?​ Отослать разработчику?​ Не торопитесь! Сначала проведем небольшой эксперимент:​ откроем файл коры, сброшенный приложением test-core-usr,​ в любом hex-редакторе (например,​ том, что встроен в Midnight Commander) и попробуем найти строку "​KPNC%69"​ (ту самую, которую мы ввели в качестве нашего имени).
 +
 +Вот так номер! Строка присутствует в коре прямым текстом (см. рис. 5).
 +
 +{{core-linux_Image_4.png?​552}}
 +
 +Рисунок 5 кора содержит в себе все стековые переменные
 +
 +Хорошо (то есть, как раз ничего хорошего),​ а как на счет кучи? Как мы помним,​ наша стендовая программа выделяла блок динамической памяти порядочных размеров и забивала его логотипами "​nezumi-souriz-elraton-"​ (это все мыщъхи — на японском,​ французском и испанском языках). Вводим искомую строку и ищем следы ее присутствия в дампе памяти.
 +
 +hex-редактор должно ждать не заставляет и немедленно отображает результат,​ высаживающий нас на полную измену (см. рис. 6).
 +
 +{{core-linux_Image_5.png?​553}}
 +
 +Рисунок 6 кора содержит в себе данные динамической памяти
 +
 +Это что же такое получается?​! Если передать программисту кору, то вместе с дампом программы он получит всю нашу информацию,​ с которой,​ возможно,​ еще не снят гриф секретности. И вообще,​ нечего всяким там программистам видеть,​ чем мы тут занимаемся! Где гарантия,​ что они окажутся честными людьми и не поимеют нас по полной программе?​!
 +
 +Кстати,​ сохранение стековых и динамических данных в коре позволяет написать программу,​ вытягивающую из дампа памяти всю не сохраненную информацию. В случае текстовых редакторов (почтовых клиентов) это можно сделать и лапами — непосредственнов hex-редакторе. Более сложные приложения,​ работающие с графикой,​ электронными таблицами,​ так просто не сдаются и над ними приходится попыхтеть,​ проанализировав внутреннее представления данных,​ которые могут быть организованы и в виде списков,​ и виде двоичных деревьев или еще как (однако,​ при наличии исходных текстов — это не такая уж и большая проблема).
 +
 +Но все же вернемся к нашим баранам. В смысле к разработчикам. Как им сообщить об ошибке без ущерба для своей конфиденциальности. Да очень просто! И в этом нам поможет могущий отладчик gdb, входящий в штатный комплект поставки большинства дистрибутивов:​
 +
 +**root@6[core-gdb]****$#​ ****запускаем ****gdb****,​ ****"​-c"​ ****указывает на кору, ****"​./​core"​ – ****имя файла с корой**
 +
 +**root@6[core-gdb]****$gdb -c ./​core****>​ error_log**
 +
 +where
 +
 +info registers
 +
 +q
 +
 +**root@6[core-gdb]****$**
 +
 +Листинг 6 выуживание из дампа памяти значений регистров и стека вызовов
 +
 +После выхода из отладчика образуется файл "​error_log",​ содержащий значения регистров общего назначения и стек вызовов. Для дислокации ошибки в программе этой информации обычно оказывается вполне достаточно. Никакой конфиденциальной информации в нем нет (не верите — откройте error_log в любом текстовом редакторе),​ поэтому,​ его можно совершенно безбоязненно передавать разработчикам. Если нам повезет,​ в будущих версиях баг будет исправлен.
 +
 +===== заключение или жизнь после смерти =====
 +
 +Сейчас мыщъх вплотную работает над секретным стратегическим проектом,​ конечной целью которого является оживление упавших программ с возможностью продолжения их нормальной работы (правда без всяких гарантий стабильности). Работа достаточно сложная,​ сопряженная с необходимостью написания ядерных моделей и потому продвигается не так быстро как этого бы хотелось,​ так что посильная помощь (в виде тестирования программы на дампах различных программ во всевозможных конфигурациях) только приветствуется. Оставляйте свои координаты для связи на http://​slut96.blogspot.com.
 +
 +