core-linux

разбор коры в 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, — хотя, о вкусах, как говориться не спорят).

Рисунок 1 набивка программы в самом хакерском редакторе всех времен и народов — в знаменитом vi

Компилируем («$gcc test-core-usr.c -o test-core-usr») и запускаем образовавшийся файл ./test-core.usr на выполнение, который спрашивает наше имя и тут же грохается с сообщением «segmentation fault» – «ошибка сегментации» или «segmentation fault (code dumped)« – «ошибка сегментации (кора сброшена)« — (см. рис. 2).

Политика сброса коры определяется настройками, о которых мы поговорим позднее, а пока удалим исходный текст и попытается реконструировать ход событий, приведший к обрушению программы. Это совсем не сложно сделать!

Рисунок 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). Рисунок 3 поиск причины сбоя в IDA Pro Мы знаем, что исключение произошло в функции _fread (являющейся оберткой вокруг _IO_fread), поэтому анализировать необходимо код, непосредственно предшествующий ее вызову, при просмотре которого в глаза бросается вызов _fopen (см. рис. 3#2), пытающийся открыть несуществующий файл «kpnc.dat» (см. рис. 3#3), причем результат, возвращенный _fopen,никак не проверяется, что и приводит к падению. Создание файла kpnc.dat в текущем каталоге восстанавливает работоспособность программы (см. рис 4). Пример, конечно, слегка надуманный, но большинство приложений ремонтируются аналогичным образом (с той лишь разницей, что на анализ уходит намного больше времени). Рисунок 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). Рисунок 5 кора содержит в себе все стековые переменные Хорошо (то есть, как раз ничего хорошего), а как на счет кучи? Как мы помним, наша стендовая программа выделяла блок динамической памяти порядочных размеров и забивала его логотипами «nezumi-souriz-elraton-» (это все мыщъхи — на японском, французском и испанском языках). Вводим искомую строку и ищем следы ее присутствия в дампе памяти. hex-редактор должно ждать не заставляет и немедленно отображает результат, высаживающий нас на полную измену (см. рис. 6). Рисунок 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.