Автор: Danil WEB-сайт: http://www.danil.dp.ua
Простейший клиент
Рассмотрим пример простейшего SMTP-клиента, отсылающего в скрытом режиме
некоторую информацию на указанный e-mail. Пример возьмем из моей статьи Troyan
#1. Используется блокирующий сокет. Рассмотрим более подробно немного измененный
кусок на Delphi, отвечающий за инициализацию и соединение с SMTP-сервером:
// Адрес в нормальном виде SMTP-сервера MySmtp:='freemail.ukr.net'; // Инициализируем сокетную DLL WSAStartUp(257, wsadata); // Инициализируем сокет для соединения с SMTP-сервером // Протокол - TCP/IP, получение данных - SOCK_STREAM sock:=socket(AF_INET,SOCK_STREAM,IPPROTO_IP); sin.sin_family := AF_INET; // Порт SMTP-сервера - 25 sin.sin_port := htons(25); // Преобразуем адрес в четырехбайтное число с помощью функции, // рассмотреной в первой статье sin.sin_addr.S_addr:=d_addr(MySmtp); // Соединение с сервером (адрес и порт указаны в структуре sin, // sock - сокет для работы с указанным сервером по протоколу TCP/IP connect(sock,sin,sizeof(sin));
|
Отправка данных
Как уже говорилось в первой статье, send - это неблокирующая операция. Здесь
есть небольшие грабли. Если нам необходимо отправить кусок данных ОПРЕДЕЛЕННОГО
размера, то send может мгновенно положить в очередь кусок данных (сколько
сможет), а остальное из-за неблокирующего режима просто не успеть отправить,
перейдя на выполнение следующего кода. Т.к. эта функция возвращает количество
отправленных байт, то можно написать функцию доотправки данных. Вот исходники на
Delphi:
// DTrSend. первый параметр - буфер для отправки, второй параметр - длинна, // третий параметр - сокет. function DTrsend(Buf : string; LenStr : Cardinal; DTrsock : TSocket) : Cardinal; var DopI, DopI1, DopI2, DopI3 : Integer; begin Screen.Cursor:=crHourGlass; Result:=send(DTrsock,Buf,0,0); // Проверка соединения - отправка 0 if Result=SOCKET_ERROR then begin Screen.Cursor:=crDefault; Application.MessageBox(PChar('Error send data. Server was disconnected, closed or not available.'), 'Error', mb_Ok + mb_TaskModal + mb_IconStop); closesocket(DTrsock); Result:=0; exit; end; // обнудение тайм-аута DopI:=0; // сколько отправлено DopI2:=0; // сколько нужно отправить DopI3:=LenStr; // цикл отправки while true do begin // отправка данных с нужной позиции нужного кол-ва DopI1:=send(DTrsock,Buf[DopI2+1],DopI3,0); // подсчет отправленного DopI2:=DopI2+DopI1; // подсчет сколько нужно отправить DopI3:=DopI3-DopI1; // если все отправили или тайм-аут - выход if (DopI2>=LenStr)or(DopI>666) then break; inc(DopI); sleep(100); end; Result:=DopI2; if Resultthen Screen.Cursor:=crDefault; end;
|
А вот исходники на MASM:
; MySend. первый параметр - буфер для отправки, ; второй параметр - длинна, третий параметр - сокет. MySend PROC StrSend : DWORD, LenStr : DWORD, clnt : DWORD ; edi указывает на буфер для отправки mov edi, StrSend ; ebx - сколько нужно отправить mov ebx, LenStr ; dll2 - сколько отправленно mov dll2, 0 ; dll0 - тайм-аут mov dll0, 0 ; Цикл отправки .WHILE TRUE ; Отправка данных invoke send, clnt, edi, ebx, 0 ; Добавление в dll0 количества отправленных данных add dll0, eax ; Если отправленно необходимое число - выход из цикла mov eax, LenStr .BREAK .IF ( dll0 >= eax ) ; Если тайм-аут - выход из цикла inc dll2 .BREAK .IF ( dll2 >= 666 ) ; переставить указатель на позицию данных в буфере, которые еще не отправленны add edi, dll0 ; пересчитать кол-во отправляемых данных sub ebx, dll0 .ENDW invoke Sleep,10 ret MySend ENDP
|
Также пишется и функции отправки данных. Только вместо send - recv. Только
recv может работать на синхронном или асинхронном сокете или на сервере. Для
клиента на блоктрующем сокете - все один к одному. Для неблокирующего сокета и
сервера на блокирующем - см. ниже.
Клиент на асинхронном сокетном движке
Рассмотрим другую мою статью Взлом e-mail #1. Для создания сокетного движка
использовались блокирующие сокеты и определенное количество отдельно работающих
процессов. Каждый процесс содержал сокет. Еще один пример сокетного движка в
асинхронном режиме можно посмотреть в моей программе "DScan v.1.2". Побольшому
счету, эти два примера отличаются только созданием процессов работы с сокетами.
Такой сокетный движок имеет свои преимущества по сравнению с неблокирующим
режимом и свои недостатки. Преимущества в том, что оператор select вызывается в
блокирующем режиме самой системой и нет постоянного цикла опроса, загружающего
процессор. Плюс то, что все процессы работают отдельно и при возникновении
какой-либо ошибки, процесс, в котором произошла ошибка, просто останавливается с
последующим освобождением занятой им памяти. Остальные при этом продолжают
работать. Как уже говорилось в первой статье, этот способ (по моему мнению)
идеально подходит для DiulUp и не очень широкого канала при выделенке. Для очень
хорошего канала и с очень большим количеством процессов, такой сокетный движок
будет очень загружать ядро системы. Особенно это неприемлимо для win-9x/me, где
отвратительно сделана многозадачность, распределение памяти, синхронизация и
"синий экран" при многопоточных приложениях - обычное дело.
Простейший сервер
Рассмотрим пример простейшего сервера на MASM (почему на MASM - смотри мои
статьи по BackDoor). Используется блокирующий сокет, с реакцией на соединение,
получение данных от клиента и разрыв связи. Реакция на эти действия производится
в функции обработки оконных сообщений, которую мы связываем с сокетом функцией
WSAAsyncSelect (см. первую статью). Этот наш сервер будет реагировать на
соединение и получение данных от клиента тем, что будет отвечать строкой
'hello'. Вот исходники на MASM:
.386 .model flat,stdcall option casemap:none
; Подгружаемые модули ; ВНИМАНИЕ!!! необходимо правильно прописать пути к файлам *.lib и *.inc include \masm32\include\windows.inc include \masm32\include\wsock32.inc include \masm32\include\user32.inc include \masm32\include\kernel32.inc includelib \masm32\lib\user32.lib includelib \masm32\lib\kernel32.lib includelib \masm32\lib\wsock32.lib
; ФУНКЦИЯ создания окна WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
; КОНСТАНТЫ .DATA Hello db "Hello",0 ClassName db "DTR13Class",0 AppName db "DTR13",0 IconName db "TDIcon",0 wsadata WSADATA <> sin sockaddr_in <> WM_SOCKET equ WM_USER + 100
; ПЕРЕМЕННЫЕ .DATA? hInstance dd ? CommandLine dd ? sock dd ? client dd ? BufStr db 6666 dup (?) ; Буфер приема Port dd ? ; Порт
; Раздел кода .CODE start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax
;--------------------------------------- WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND ; Создадим окно для обработки сообщений mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,hInstance,addr IconName mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc ; Создать маленькое окно invoke CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,WS_OVERLAPPEDWINDOW,500,400,100,50,NULL,NULL,hInst,NULL mov hwnd,eax ; Показать окно invoke ShowWindow, hwnd,SW_SHOWNORMAL invoke UpdateWindow, hwnd ; Инициализация сокетных функций invoke WSAStartup,101h,addr wsadata invoke socket,AF_INET,SOCK_STREAM,0 mov sock,eax ; реакция на соединенрие, получение данных и закрытие сокета invoke WSAAsyncSelect,sock,hwnd,WM_SOCKET,FD_ACCEPT+FD_READ+FD_CLOSE mov sin.sin_family,AF_INET ;порт = 10001 mov Port,10001 invoke htons,Port mov sin.sin_port,ax ; Адрес - любой для сервера mov sin.sin_addr,INADDR_ANY ; Связь адресов и порта с сервером invoke bind, sock,addr sin,sizeof sin invoke listen,sock,SOMAXCONN
; Цикл обработки сообщений от m$ windows .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ret WinMain endp
;--------------------------------------- ; Функция обработки сообщений окну WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM ; Реакция на создание окна .IF uMsg == WM_CREATE
; Реакция на закрытие проги .ELSEIF uMsg == WM_DESTROY invoke WSACleanup invoke PostQuitMessage,NULL
; Реакция на сообщение от сокета .ELSEIF uMsg == WM_SOCKET mov eax,lParam
; Соединение .IF ax == FD_ACCEPT shr ax,16 .IF ax == NULL ; При соединении инициализируем сокет и отправим клиенту строку invoke accept,sock,0,0 mov client,eax invoke send,client,addr Hello,sizeof Hello,0 invoke Sleep,10 .ENDIF
; Реакция на получение данных от клиента .ELSEIF ax == FD_READ ; Обнулим буфер для получения mov ecx,6666 mov edi,offset BufStr lll: mov byte ptr [edi],0 inc edi loop lll mov eax,wParam mov client,eax ; Получим данные в BufStr invoke recv,client,addr BufStr,sizeof BufStr,0 ; При ошибке - получим еще раз .IF eax == SOCKET_ERROR invoke recv,client,addr BufStr,sizeof BufStr,0 .ENDIF ; Отправим клиенту строку invoke send,client,addr Hello, sizeof Hello, 0 invoke Sleep,10
; Реакция на разрыв соединения .ELSEIF ax == FD_CLOSE mov eax,wParam ; Закроем сокет invoke closesocket, eax .ENDIF .ELSEIF invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp END start
|
Компилим, запускаем. Должно появиться маленькое окно. Теперь можно написать
клиент или просто соединиться TelNet-ом на 10001 порт. Сервер при соединении и
посылке ему любых данных, будет слать обратно строку "Hello". Можно этот код
переписать на Delphi или C - все используемые функции из ядра виндов. Отличается
только синтаксис. Также следует отметить, что принцип действия всех серверов и в
никсах и в виндах примерно одинаков. Поняв принцип работы одного - примерно
понимаешь принцип работы всех.
"Грабли" сервера
Мы примерно поняли принцип работы серверов. Теперь рассмотрим наиболее
распространенные ошибки при написании этих самых серверов. Серверу надо принять
столько данных, сколько мы ему послали. Так как принимает сервер их частями и в
функции реакции на прием данных в рассмотренном выше примере их нельзя как-то
докачать (после срабатывания FD_READ, recv для докачки не может получить из
стека остальные данные потому, что еще не была вызвана функция получения
сообщения от винды и доступ к очереди сокета закрыт). Значит необходимо хранить
где-то все сокетные соединения и часть уже пришедших на них данных и при
срабатывании FD_READ, накапливать эти данные. При достижении необходимого
размера или комбинации символов, соответствующих сигналу конца пакета,
соответственно реагировать. Структура сервера при этом усложнится. Необходимо
выбрать для себя режим работы с пакетами - по размеру или по определенной
комбинации символов - признак окончания пакета. Теперь о самом главном. В
виндоуз есть несколько очень уникальных WinAPI функций, которые допускают DoS.
Это функции lstr..., printf и прочие. Для сервера, нас интересуют именно функции
для работы со строками с нулевым символом на конце (lstr...). Например, буфер
накопления данных от клиента имеет определенный размер. И сервер понимает
окончание пакета, как получение от клиента куска данных этого размера. Пока не
накопит - не реагирует. Можно подобрать размер отправляемых данных таким
образом, что накапливаемые данные превысят размер буфера, попадут в область кода
или в область других данных и произойдет выполнение определенных команд или
отказ работы всего сервера. Если в конце принимаемого пакета обязательно должна
стоять определенная комбинация символов, то посылая большие куски данных, не
содержащие эти символы, мы также можем сделать переполнение буфера. Также можно
поиздеваться над сервером, посылая данные с разными флагами (например, MSG_OOB -
самый простой Nuke на 139 порт), попробовать инициализировать соединение как
UDP, ICMP и т.д. и т.п. Есть целый ряд уязвимостей различных сервисов,
основанных на этом принципе. В основном, это способы удаленного отказа работы
сервера. Найти уязвимость, заставляющую сервер исполнять определенный кусок
данных, переполнив буфер - это высший пилотаж. В следующей статье будут
рассмотрены несколько самых простых примеров удаленного отказа в обслуживании.
В следующей статье я рассмотрю работу с неблокирующими сокетами. Как пример,
будет рассмотрена уязвимость w2k, приводящая к зависанию всей системы в целом.
P.S. Статья и программы предоставлены в целях обучения и вся ответственность
за использование ложится на твои хилые плечи. |