Доработка напильником
shell-код имеет сложную структуру и обычно состоит из несколько частей. Например, exploit http://milw0rm.com/exploits/1075 (приложенный в файле 1075.с) использует 6(!) "иероглифических" массивов: dce_rpc_header1, tag_private, dce_rpc_header2, dce_rpc_header3, offsets, bind_shellcode. Первые пять — это служебные структуры, атакующие жертву, срывающие буферу крышу и передающие управление на bind_shellcode. Последний представляет собой "чистый" shell-код, который может быть беспрепятственно заменен любым другим. На самом деле, тут все не так просто и произвола хоть отбавляй. Как минимум необходимо убедиться, что мы используем shell-код совместимый с атакуемой системой и точки входа у них совпадают. Часто (но не всегда) точка входа расположена в самом начале shell-кода, реже — в его конце или середине. Гораздо хуже, если exploit написан "пионером" и все блоки идут одним большим кусом, внутри которого присутствует в том числе и shell-код.
Чтобы определить положение дел, необходимо преобразовать "иероглифический" текст в двоичный файл и дизассемблировать его. Разыскивать соответствующий конвертор совершенно необязательно. Проще переложить эту задачу на плечи компилятора си, написав простенькую программку всего из одной строки:
main(){FILE *f;if(f=fopen("shellcode","wb"))fwrite(shellcode, sizeof(shellcode),1,f);}
Листинг 2 простейший конвертор для преобразования строковых констант в двоичный код
Сам shell-код должен быть размещен в массиве, объявленном как "char shellcode[]" (см. прилагаемый файл hex2bin.c) и приведенным к синтаксису си (то есть, если shell-код выдернут из perl'а необходимо удалить точки в конце строковых констант). Компилируем наш импровизированный конвертор, запускаем его на выполнение и тут же на диске образуется файл "shellcode", который можно загрузить в HTE, IDA Pro или любой другой дизассемблер по вкусу, не забывая, конечно, переключить его в 32-битный режим.
В данном случае мы получим следующий код:
00000000: 29C9 sub ecx,ecx
00000002: 83E9B0 sub ecx,-050 ;"P"
00000005: D9EE fldz
00000007: D97424F4 fstenv [esp][-000C]
0000000B: 5B pop ebx
0000000C: 81731319F50437 xor d,[ebx][00013],03704F519 ;"7¦ov"
00000013: 83EBFC sub ebx,-004 ;"¦"
00000016: E2F4 loop 00000000C -------- (1)
Листинг 3 первые 16-байт shell-кода содержат осмысленный код расшифровщика
Ага! Вполне типичный расшифровщик, значит, точка входа в shell-код действительно находится в начале массива и он может быть беспрепятственно заменен любым таким же. Если же вместо осмысленно кода нас встречает мусор, значит, нужно последовательно отступать на один байт от начала до тех пор, пока мы не получим что-то удобоваримое. Естественно, для этого необходимо знать ассемблер и хотя бы общих чертах представлять себе устройство операционной системы.
Правильно спроектированный shell-код работает на всех версиях операционных систем для которых он предназначен, однако, в последнее время все чаще и чаще приходится сталкиваться с "пионерством", привязывающимся к фиксированным адресам и функционирующих только под определенной сборкой LINUX-ядра или заранее заданным сервис-паком, наложенным на Windows. Постойте-постойте! Какой такой Windows?! Мы же ведь сидим на LINUX/BSD и никуда с этих замечательных систем сходить не собираемся! Все правильно! Но даже LINUX/BSD-хакерам частенько приходится атаковать Windows-машины и обходить этот вопрос стороной мыщъх просто не имеет моральных прав! (см. врезку)
UNIX-подобные системы в этом плане намного менее изменчивы и там проблема "фиксированных адресов" практически сведена на нет. Обычно shell-код вызывает необходимые ему функции через системные вызовы, интерфейс с которыми обеспечивается прерыванием INT 80h или дальним вызовом по адресу 0007h:00000000h, что позволяет shell-коду функционировать под всей линейкой осей, для которых он предназначен.Тем не менее, определенные системные вызовы в различных версиях ядер реализованы сильно неодинаково, что порождает проблемы совместимости. К счастью, базовый набор системных вызовов остается единым для всех осей и грамотно спроектированный exploit поражает как LINUX, так и BSD, а если не поражает, то не задумываясь отправляется в /dev/nul.