Различия

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

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

articles:exploit-reviews-0x007 [2017/09/05 02:55] (текущий)
Строка 1: Строка 1:
 +====== exploit-reviews-0x007 ======
 +<​sub>​{{exploit-reviews-0x007.odt|Original file}}</​sub>​
 +
 +====== exploits review\\ 7йвыпуск ======
 +
 +крис касперски ака мыщъх, no-email
 +
 +===== WikyBlog: HTML-инжектинг =====
 +
 +**brief****:​**впопулярном (и при том совершенно бесплатном!) программном обеспечении для создания blog'​ов — **WikyBlog** (www.wikyblog.com),​ отпочковавшимся от не менее популярной свободной энциклопедии Wikipedia (www.wikipedia.org),​ обнаружены множественные дыры, связанные с некорректной фильтрацией пользовательского ввода данных в полях "​login"​ и "​search",​ размещенных на странице Панели Управления (www.wikyblog.com/​Special/​Main/​ControlPanel) и позволяющие атакующему выполнять произвольный HTML/​JavaScript/​VBScript-код в контексте уязвимого сайта, воруя cookies, содержащие данные авторизации. уязвимость была обнаружена хакерским коллективом HackersCenterITSecurityResearchTeam [HSC], описавших ее на своем сайте в короткой заметке,​ датируемой 1 декабря 2006 года http://​www.hackerscenter.com/​archive/​view.asp?​id=26544 и уже на следующий день перекочевавшую на Security Focus:​ http://​www.securityfocus.com/​bid/​21406.
 +
 +**targets**:​уязвимости подвержена самая последняя на данный момент версия WikyBlog – 1.3.2, выпущенной 14 ноября 2006 года, о более древних версиях (доступных для скачивания на http://​sourceforge.net/​projects/​wikyblog) пока ничего не известно;​
 +
 +**exploit****:​**исходный текст proof-of-conceptexploit'​а (представляющий собой простейший XSS-скрипт) можно найти на сайте HTS-группы по ссылке,​ приведенной выше, там же находится и скриншет атакованного сайта (см. рис. 1);​
 +
 +**solution******ведущий разработчик WikiBlog'​а (известный под ником Cobalt –justinms66@users.sourceforge.net) пока никак не отреагировал на сообщение об уязвимости,​ так что пользователям WikiBlog'​ов ничего другого не остается как сидеть на измене и ждать новостей (ну или латать дыры самостоятельно,​ благо исходные тексты открыты);​
 +
 +{{exploit-reviews-0x007_Image_0.png?​494}}\\ Рисунок 1 сайт www.wikyblog.com после атаки
 +
 +===== GNU GV: удаленное переполнение буфера =====
 +
 +**brief**:​6 октября Renaud Lifchitz (r.lifchitz@sysdream.com) – ведущий сотрудник компании Sysdream (www.sysdream.com) обнаружил ошибку переполнения в **GNU gv**,​ приводящую к возможности удаленного выполнения shell-кода в контексте уязвимого приложения. GNU gv — это де-факто стандартный вьювер ps и pdf файлов под X'​ми,​ входящий практически во все LINUX-дистрибутивы и используемый некоторыми браузерами (в частности,​ Epiphany) по умолчанию,​ а так же входящий в состав других продуктов,​ одним из которых является вьювер Evince. дыра кроется в функции ps_gettext, находящейся в "​ps.c"​ файле и представляет собой классический пример "​срыва стека",​ возникающего при передаче слишком длинных комментариях в некоторых полях заголовка (например,​ поле "​%%DocumentMedia"​),​ копируемых в text-буфер фиксированного размера 257 байт со всеми вытекающими отсюда последствиями. ошибка была подтверждена разработчиками тремя днями спустя,​ тогда же она появилась и на http://​www.securityfocus.com/​bid/​20978;​
 +
 +**targets**:​уязвимости подвержена версия 3.6.2, остальные пока не проверялись,​ но, судя по всему, эта дыра присутствует и в них;
 +
 +**exploits**:​для реализации атаки имеется большое количество вполне боевых exploit'​ов,​ вот только некоторых из них: **Linux IA32 Reverse TCP Shell on 192.168.110.247:​4321** —http://​www.securityfocus.com/​data/​vulnerabilities/​exploits/​hello-reverseshell.ps (ps-файл) и его исходный код на Си —www.securityfocus.com/​data/​vulnerabilities/​exploits/​evince-ps-field-bof.c;​ генератор ps-файлов с shell-кодом на борту в исходных текстах на Си: http://​www.securityfocus.com/​archive/​1/​452868;​
 +
 +solutionобновленная версия GNU gv может быть скачена как непосредственно с его "​родного"​ сайта http://​www.gnu.org/​software/​gv,​ так и с сайтов производителей LINUX-дистрибутивов,​ большинство из которых уже выпустило свои заплатки;​
 +
 +{{exploit-reviews-0x007_Image_1.png?​490}}
 +
 +Рисунок 2 Evince – один из многих просмотрщиков документов,​ использующих уязвимую версию GNU gv
 +
 +===== LinuxKernel:​ удаленное переполнение буфера =====
 +
 +**brief**:​в ходе очередной проверки исходных текстов ядра Linux'​а,​ Евгений Тео (EugeneTeo),​ входящий в коллектив разработчиков,​ обнаружил довольно экзотичную ошибку целочисленного переполнения в функции Get_FDB_Entries,​ о чем и поведал народу на своем blog'​е в заметке "​MOKB-29-11-2006:​ Linux 2.6.7 - 2.6.18.3 get_fdb_entries() Integer Overflow",​ датируемой 29 ноября 2006 года:​ http://​projects.info-pull.com/​mokb/​MOKB-29-11-2006.html;​ дыра кроется в функции get_fdb_entries (находящийся в файле net/​bridge/​br_ioctl.c),​ которая при передаче определенных аргументов (и наличии двух или более сетевых адаптеров на машине) может затирать память ядра функцией memcpy, что (при успешной атаке) позволяет выполнять shell-код на уровне нулевого кольца,​ то есть с наивысшими привилегиями! и хотя возможность удаленных атак поставлена под сомнение,​ потенциальная угроза все-таки есть;
 +
 +**targets**:​уязвимости подвержено множество версий семейства 2.6.x.x (и, по некоторым данным некоторые версии семейства 2.4.x.x), неполный перечень которых содержится на http://​www.securityfocus.com/​bid/​21353/​info,​ причем в версии **2.6.18.4** уязвимость отсутствует;​
 +
 +**exploit**:​на данный момент уязвимость не подкреплена никаким exploit'​ом и вообще о ней очень мало что известно,​ что открывает большой оперативный простор для всевозможных экспериментов и исследований;​
 +
 +**solution**одновременно с публикацией сообщения о дыре был выпущен "​лечебны"​ патч — "​bridge:​ fixpossibleoverflowinget_fdb_entries",​ выложенный на официальном сайте: http://​www.kernel.org/​git/?​p=linux/​kernel/​git/​torvalds/​linux-2.6.git;​a=commit;​h=ba8379b220509e9448c00a77cf6c15ac2a559cc7,​ а коллектив разработчиков ядра оперативно выпустил свежие версии **2.6.17.10** и **2.4.33.2** специально для устранения этой проблемы;​
 +
 +{{exploit-reviews-0x007_Image_2.png?​504}}
 +
 +Рисунок 3 фрагмент уязвимой функции get_fdb_entries
 +
 +===== full disclose\\ MS Windows:​ отказ в обслуживании из-за переполнения в суплере печати =====
 +
 +**brief**:​2 декабря 2006 года польским хакером по кличке h07 (h07@interia.pl) был опубликован exploit (написанный на языке Питон),​ подключающийся к службе печати через NetBIOS и вызывающий необрабатываемое исключение в суплере печати,​ приводящее к отказу в обслуживании:​ http://​downloads.securityfocus.com/​vulnerabilities/​exploits/​21404.py. ошибка кроется в функции GetPrinterData,​ экспортируемой динамической библиотекой WINSPOOL.DRV (да не введет нас ее расширение в заблуждение — никакой это не драйвер,​ а самый обыкновенная DLL, исполняющаяся на прикладном уровне),​ принимающей в одном из аргументов количество байт, которое необходимо выделить для записи конфигурации принтера,​ но не проверяющий его значение на "​политкорретность",​ в результате чего при запросе >​=512Мбайт функция VirtualAlloc обламывается с выделением,​ возвращая вместо памяти нулевой указатель,​ сигнализирующий об ошибке,​ который так же никто не проверяет и при попытке обращения к нему процессор генерирует исключение,​ приводящее к аварийному завершению процесса spoolsv.exe (Служба Печати),​ что, конечно,​ не смертельно,​ но все-таки очень неприятно. тем не менее, возможность захвата управления отсутствует,​ что внушает некоторый оптимизм;​
 +
 +{{exploit-reviews-0x007_Image_3.png?​494}}
 +
 +Рисунок 4 боевой exploit польского хакера h07, срывающий крышу службе печати
 +
 +**target**:​уязвимости подвержена вся линейка Windows 2000 как со всеми установленными заплатками (вплоть до SP4), так и без них. о других системах ничего не известно,​ но, вероятнее всего, дыра присутствует и в них;
 +
 +**exploit**:​http://​downloads.securityfocus.com/​vulnerabilities/​exploits/​21404.py;​
 +
 +**solution**Microsoft пока не представила никаких заплаток,​ что не есть хорошо. Можно даже сказать,​ что это совсем хреново. Отключать службу печати — не предлагать,​ поскольку количество принтеров в большинстве контор не совпадает с количеством машин и без разделения ресурсов никак не обойтись. Можно, правда,​ отсечь удаленных пользователей брандмауэром,​ но гораздо интереснее изготовить заплатку самому!
 +
 +**disclose**:​начнем исследование с того, что заглянем в MSDN (например,​ тот, что идет в одном комплекте с MicrosoftVisualStudio 6.0) и посмотрим на прототип функции GetPrinterData,​ который выглядит так:
 +
 +DWORD GetPrinterData(
 +
 + ​HANDLE hPrinter, ​ // handle to a printer or print server
 +
 + ​LPTSTR pValueName, ​ // string that identifies the data to retrieve
 +
 + ​LPDWORD pType, ​ // variable that receives the type of data
 +
 + ​LPBYTE pData, ​ // buffer that receives the configuration data
 +
 + DWORD nSize, ​ // size, in bytes, of buffer
 +
 + ​LPDWORD pcbNeeded ​ // receives the required buffer size, in bytes 
 +
 +);
 +
 +Листинг 1 прототип функции GetPrinterData
 +
 +Параметр nSize задает размер буфера в байтах,​ выделяемого функцией. Вообще,​ это, конечно,​ глупость — поручать выделение памяти функции. Передача указателя на блок памяти,​ выделенный программистом,​ выглядела бы более логично и… безопасно. Но горячие парни из Microsoft программируют быстрее,​ чем думают,​ а думают они не головой,​ а совсем другой частью тела. Индусы,​ короче. Ну что с них возьмешь?​! Ладно, пускай и дальше не думают. Нам, хакерам,​ это только на пользу идет. Достаточно передавать в качестве nSize такой размер памяти,​ который заведомо не может быть выделен и результат себя ждать не заставит. Вне зависимости от количества физической памяти,​ адресное пространство процессов на 32-разрядных платформах составляет 4 Гбайта,​ из которых обычно половина выделяется системе,​ а половина — на стек, секции кода/​данных PE-файла и всех загруженных динамических библиотек. Остаток занимает куча. На серверах имеется возможность ужать систему до одного гигабайта,​ отдав его куче, поэтому больше 3 Гигабайт запрашивать не имеет смысла. Все равно не дадут и крах наступает уже на отметке в 512 Мбайт.
 +
 +{{exploit-reviews-0x007_Image_4.png?​495}}
 +
 +Рисунок 5 результаты поиска функции OpenPrinterEx в Platform SDK
 +
 +Но чтобы реализовать атаку, необходимо в первом параметре hPrinter передать дескриптор принтера,​ открываемый (по документации!) функцией OpenPrinter,​ экспортируемый все той же самой динамической библиотекой WINSPOOL.DRV. Вот только exploit использует не OpenPrinter,​ а OpenPrinterEx,​ которой ни в старом MSDN (тот, что идет с VisualStudio 6.0), ни в свежем Platform SDK что-то не наблюдается. Недокументированная функция?​ Но в таблице экспорта WINSPOOL.DRV она отсутствует (в чем легко убедится с помощью утилиты DUMPBIN.EXE "​DUMPBIN /​EXPORTS WINSPOOL.DRV >​ out"​) и возникает резонный вопрос — как же, черт возьми,​ все это работает?​! А в том, что exploit работает — можно не сомневаться.
 +
 +class OpenPrinterEx(Structure):​
 +
 +alignment = 4
 +
 +opnum = 69
 +
 +structure = (
 +
 +('​printer',​ ':',​ B1),
 +
 +('​null',​ '<​L=0'​),​
 +
 +('​str',​ '<​L=0'​),​
 +
 +('​null2',​ '<​L=0'​),​
 +
 +('​access',​ '<​L=0'​),​
 +
 +('​level',​ '<​L=1'​),​
 +
 +('​id1',​ '<​L=1'​),​
 +
 +('​level2',​ '<​L=10941724'​),​
 +
 +('​size',​ '<​L=28'​),​
 +
 +('​id2',​ '<​L=0x42424242'​),​
 +
 +('​id3',​ '<​L=0x43434343'​),​
 +
 +('​build',​ '<​L=2600'​),​
 +
 +('​major',​ '<​L=3'​),​
 +
 +('​minor',​ '<​L=0'​),​
 +
 +('​processor',​ '<​L=0xFFFFFFFF'​),​
 +
 +('​client',​ ':',​ B2),
 +
 +('​user',​ ':',​ B2),
 +
 +)
 +
 +
 +
 +class GetPrinterData(Structure):​
 +
 +alignment = 4
 +
 +opnum = 26
 +
 +structure = (
 +
 +('​handle',​ '​%s'​),​
 +
 +('​value',​ ':',​ B2),
 +
 +('​offered',​ '<​L'​),​
 +
 +)
 +
 +query = OpenPrinterEx()
 +
 +printer = "​\\\\%s\x00"​ % (host)
 +
 +query['​printer'​] = B1()
 +
 +query['​printer'​]['​id'​] = 0x41414141
 +
 +query['​printer'​]['​max'​] = len(printer)
 +
 +query['​printer'​]['​actual'​] = len(printer)
 +
 +query['​printer'​]['​str'​] = printer.encode('​utf_16_le'​)
 +
 +
 +
 +query = GetPrinterData()
 +
 +value = "​blah_blah\x00"​
 +
 +query['​handle'​] = handle
 +
 +query['​value'​] = B2()
 +
 +query['​value'​]['​max'​] = len(value)
 +
 +query['​value'​]['​actual'​] = len(value)
 +
 +query['​value'​]['​str'​] = value.encode('​utf_16_le'​)
 +
 +query['​offered'​] = memory_size
 +
 +Листинг 2 фрагмент exploit'​а,​ демонстрирующий технику вызова GetPriterData,​ принимающую дескриптор,​ возвращенный OpenPrinterEx
 +
 +Поиск по сайту Microsoft дает всего лишь одну ссылку на OpenPrinterEx,​ вскользь упоминаемую при описании структуры PRINTPROVIDOR в DDK и реализуемую драйвером принтера:​ http://​msdn2.microsoft.com/​en-us/​library/​aa506552.aspx. Чуть-чуть более подробное описание содержится в технической документации на Самбу (см. раздел "​SambaPrintingInternals"​ — http://​samba.org/​samba/​docs/​man/​Samba-Developers-Guide/​devprinting.html),​ после прочтения которого становится ясно, что exploit вызывает OpenPrinterEx через механизм удаленного вызова процедур – RemoteProcedureCall (RPC), без обращения к WINSPOOL.DRV. Собственно говоря,​ и в самом WINSPOOL.DRV функция OpenPrinter реализована через RPC (см. листинг 3).
 +
 +.text:​777D47B9 sub_777D47B9proc near; CODE XREF: sub_777D4634+68↑p
 +
 +.text:​777D47B9
 +
 +.text:​777D47B9 arg_0= dword ptr  4
 +
 +.text:​777D47B9
 +
 +.text:​777D47B9leaeax,​ [esp+arg_0]
 +
 +.text:​777D47BDpusheax
 +
 +.text:​777D47BEpushoffset dword_777D1AD8
 +
 +.text:​777D47C3pushoffset off_777D1A28
 +
 +.text:​777D47C8callNdrClientCall2
 +
 +.text:​777D47CDaddesp,​ 0Ch
 +
 +.text:​777D47D0retn14h
 +
 +.text:​777D47D0 sub_777D47B9endp
 +
 +Листинг 3 фрагмент WINSPOOL.DRV,​ реализующий функцию OpenPrinter через механизм RPC
 +
 +При создании exploit'​а на эти тонкости можно не обращать внимания,​ достаточно лишь взять любой сырец, печатающий на принтере через NetBIOS (в смысле — удаленно) и сразу же после OpenPrinter/​OpenPrinterEx воткнуть вызов GetPriterData с некорректным значением nSize. Какая в конце концов разница какие механизмы задействует Windows и какие функции при этом реально вызываются. Главное,​ что незалатанный спулер печати падает! А это — хорошо! Ну, кому-то,​ может быть и хорошо,​ а тому, кто падает — как-то не очень. Особенно,​ если падать приходится много раз на дню при печати многостраничного документа. Но как только мы захотим заштопать систему своими лапами,​ тут уже абстрагироваться от анатомических подробностей внутренней реализации Windows никак не получится.
 +
 +{{exploit-reviews-0x007_Image_5.png?​490}}
 +
 +Рисунок 6 дизассемблирование динамической библиотеки WINSPOOL.DRV,​ являющийся всего лишь "​оберткой"​ вокруг реальных принтерных функций,​ вызываемых через механизм RPC
 +
 +На первый взгляд проблема решается легкой правкой WINSPOOL.DRV — ставим в начало функции GetPrinterData переходник на свободное место, достаточно просторное для размещения нескольких машинных команд,​ проверяющих корректность аргумента nSize. Естественно,​ придется скорректировать контрольную сумму файла WINSPOOL.DRV (что можно сделать при помощи утилиты EDITBIN.EXE,​ входящей в состав SDK) и усмирить SFC, путем копирования исправленной версии WINSPOOL.DRV в WINNT\System32\dllcache (естественно,​ делать это надо при выключенном SFC или загрузившись с другой системы). Вот только… эффект от проделанной операции будет, мягко говоря,​ нулевой. А все потому,​ что WINSPOOL.DRV используется только локально,​ а при печати через NetBIOS все вызовы идут через RPC и перехватывать следует NdrClientCall2 из RPCRT4.DLL, описание которой отсутствует в SDK, но, к счастью,​ IDA Pro знает ее прототип:​ CLIENT_CALL_RETURN _imp_NdrClientCall2(PMIDL_STUB_DESCpStubDescriptor,​ PFORMAT_STRINGpFormat,​...),​ где pFormat — указатель на строку параметров,​ описывающих вызываемый метод и его параметры. В частности,​ метод GetPrinterData проходит под кодовым обозначаем 1Ah (см. листинг 2) и передается в 6'​ом,​ считая от нуля, байте форматной строки (которая на самом деле никакая не строка,​ поскольку содержит внутри себя нули и прочие непечатные символы). Параметр nSize передается через стек следующим образом (см. листинг 4):​
 +
 +.text:​777D53D3push ​ [ebp+pcbNeeded]
 +
 +.text:​777D53D6push ​ [ebp+nSize]
 +
 +.text:​777D53D9push ​ [ebp+pData]
 +
 +.text:​777D53DCpush ​ [ebp+pType]
 +
 +.text:​777D53DFpush ​ [ebp+pValueName]
 +
 +.text:​777D53E2mov ​ eax, [ebp+var_44]
 +
 +.text:​777D53E5push ​ dword ptr [eax+4]
 +
 +.text:​777D53E8call ​ sub_777D542F
 +
 +
 +
 +.text:​777D542F sub_777D542Fproc near; CODE XREF: GetPrinterDataW+A4↑p
 +
 +.text:​777D542F
 +
 +.text:​777D542F arg_0= dword ptr  4
 +
 +.text:​777D542F
 +
 +.text:​777D542Flea ​ eax, [esp+arg_0]
 +
 +.text:​777D5433push ​ eax
 +
 +.text:​777D5434push ​ offset pFormat
 +
 +.text:​777D5439push ​ offset pStubDescriptor
 +
 +.text:​777D543Ecall ​ NdrClientCall2
 +
 +.text:​777D5443add ​ esp, 0Ch
 +
 +.text:​777D5446retn ​ 18h
 +
 +.text:​777D5446 sub_777D542F ​ endp
 +
 +
 +
 +.text:​777D20C0 pFormatdb ​ 0; DATA XREF: sub_777D542F+5↓o
 +
 +.text:​777D20C1db ​ 48h ; H
 +
 +.text:​777D20C2db ​ 0
 +
 +.text:​777D20C3db ​ 0
 +
 +.text:​777D20C4db ​ 0
 +
 +.text:​777D20C5db ​ 0
 +
 +**.text:​777D20C6db ​ 1Ah ; ****метод**** GetPrinterData**
 +
 +.text:​777D20C7db ​ 0
 +
 +Листинг 4 вызов GetPrinterData черезмеханизм RPC
 +
 +Таким образом,​ в момент вызова функции NdrClientCall2,​ указатель на параметры лежит в стеке по смещению [ESP-0Ch], а по смещению +14h от его начала находится nSize, который мы и должны проверять на корректность. Но прежде необходимо проанализировать указатель на форматную строку,​ находящуюся в стеке по смещению [ESP-08h], убедившись,​ что 6'й байт равен 1Ah, т.е. вызывается метод GetPrinterData,​ а не что-то иное. Ассемблерный код труда написать не составит и каждый сможет это сделать сам. Главное — не забывать о проверках на нулевые указатели,​ чтобы исправляя одну ошибку,​ не создавать на ее месте десяток новых. Так же, проверочный код нельзя размещать в секции данных — хоть там полно свободного места, но на машинах с аппаратной поддержкой DEP это работать не будет и тут же возникнет исключение. Поскольку,​ механизм RPC – это, фактически,​ фундамент,​ на котором базируется Windows NT,​ править RPCRT4.DLL стоит с огромной осторожностью,​ поскольку,​ если он окажется поврежден,​ загрузить систему не удастся. С другой стороны,​ при правке файла на диске, мы всегда сможем сделать откат, воткнув винчестер с поврежденной NT в компьютер вторым,​ или воспользовавшись консолью восстановления (находится на дистрибутивном CD) и скопировав оригинальный RPCRT4.DLL поверх исправленного.
 +
 +В качестве альтернативного варианта,​ можно воспользоваться правкой RPCRT4.DLL в памяти по методике,​ описанной в статье "​метафизика wmf файлов":​ прописываем в следующей ветке реестра HKLM\Software\Microsoft\WindowsNT\CurrentVersion\Windows\AppInit_DLLs специально созданную для этих целей динамическую библиотеку,​ которая будет отображаться на адресное пространство каждого процессора. Внутри DllEntry мы выполняем загрузку RPCRT4.DLL через LoadLibrary,​ правим ее и все! Если приложение не использует RPCRT4.DLL (что навряд ли), мы просто теряем немного памяти. Если же приложение подгружает RPCRT4.DLL через таблицу импорта или через LoadLibrary (что намного более вероятно),​ оно просто отсылается к уже загруженной (и исправленной!) копии RPCRT4.DLL и хакер не имеет никаких шансов атаковать систему! Естественно,​ по сравнению с правкой на диске, время загрузки файлов и потребность системы в памяти ощутимо возрастут,​ а риск угробить систему — все тот же. Если динамическая библиотека,​ осуществляющая правку,​ будет реализована с ошибками,​ система упадет прежде,​ чем загрузиться пользовательский интерфейс и для исправления ситуации придется к помощи все той же консоли восстановления (втыкать винчестер с убитой NT "​вторым"​),​ удаляя нашу динамическую библиотеку на хрен. Впрочем,​ это уже детали. Главное,​ что изготовление заплаток своим лапами — вполне возможно и пока другие ждут помощи от Microsoft, правильные хакеры защищаются самостоятельно (залатанный RPCRT4.DLL из-за долбанных лицензионных ограничений к статье,​ естественно,​ не прилагается),​ во всем полагаясь на свой хвост, который,​ если верить энциклопедиям,​ может достигать 45 см длины — http://​ru.wikipedia.org/​wiki/​%D0%9C%D1%8B%D1%88%D1%8C. Ну, на счет 45 см это они, конечно,​ загнули. В природе таких мышей не встречается,​ иначе меня начнет мучить жуткий комплекс неполноценности.
 +
 +{{exploit-reviews-0x007_Image_6.png?​498}}
 +
 +Рисунок 7 мышь познается по длине хвоста
 +
 +