Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Сокеты 2 (Кодинг блокирующих сокетов)

Автор: 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. Статья и программы предоставлены в целях обучения и вся ответственность за использование ложится на твои хилые плечи.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay