Программирование сокетов на ассемблере

Сетевые извращения на ассемблере v. 1.1

Все началось с курсового проекта по предмету ЯПНУ (Языки Программирования Низкого Уровня) по завершению которого нам в обязательном порядке следовало вложить все наши творческие и технические навыки в создание программы на великом и могучем Ассемблере. Творческая часть заключалась в выборе темы. Не долго думая я решил взять что нибудь с клиент-серверными технологиями и вот что из этого получилось

Суть задания была такова: Есть клиентская и серверная части, между которыми должны посылаться текстовые сообщения (Аля смс), притом при всем общаться они должны через другую программку которая выступает шлюзом между ними, перенаправляя сообщения на определенные указанные порты и мониторя все передаваемые сообщения.

Воодушевленный наполеоновскими идеями я приступил к реализации задуманного. Пролистав немало книг данной тематики, посетив множество форумов посвященных программированию на низком уровне, и почти отчаявшись неожиданно для себя наткнулся на статью (на горячо любимом мною сайте wasm.ru) в которой описывалась библиотека ws2_32. После этого переломного события дело забурлило. Мною был выбран masm32, как единственное орудие в борьбе с поставленной задачей.

Клиент

Разработка клиента заняла у меня больше всего времени, около 3 часов. Так как приложения у меня оконное пришлось немного помучиться с его созданием (благо документации по этому вопросу много). Скопипастив участок кода отвечающий за инициализацию окна, разобравшись в нем и заточив под свой проект у меня получилось нечто такое:

include \masm32\include\masm32rt.inc
include Our_Socket.inc

invoke GetModuleHandle,NULL
mov hInstance,eax
invoke GetCommandLine

invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWDEFAULT
invoke WSACleanup
invoke ExitProcess,eax
invoke InitCommonControls

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_BYTEALIGNCLIENT
mov wc.lpfnWndProc,offset WndProc
mov wc.cbClsExtra,NULL
mov wc.cbWndExtra,NULL
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszClassName,OFFSET ClassName
invoke LoadIcon,NULL,IDI_APPLICATION
mov wc.hIcon,eax
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx,addr wc
invoke CreateWindowEx,0,ADDR ClassName,ADDR FormCaption,WS_SYSMENU or WS_MINIMIZEBOX,400,80,300,200,0,0,hInst,0

mov hwnd,eax
invoke MessageBox,hwnd,offset Hello,offset Header,MB_ICONINFORMATION
INVOKE ShowWindow,hwnd,SW_SHOWNORMAL
INVOKE UpdateWindow,hwnd
.WHILE TRUE
invoke GetMessage,ADDR msg,0,0,0
.BREAK .IF (!eax)
invoke TranslateMessage,ADDR msg
invoke DispatchMessage,ADDR msg
.ENDW
mov eax,msg.wParam
ret

WndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
.IF uMsg == WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg == WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClName,ADDR TextEdit1,WS_CHILD or ES_LEFT or ES_AUTOHSCROLL or WS_VISIBLE,8,8,121,21,hWnd,Edit1ID,hInstance,0

mov hwndEdit1,eax
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClName,ADDR TextEdit2,WS_CHILD or ES_LEFT or ES_AUTOHSCROLL or WS_VISIBLE,8,35,185,21,hWnd,Edit2ID,hInstance,0

mov hwndEdit2,eax
invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR EditClName,ADDR TextMemo1,WS_CHILD or ES_LEFT or ES_AUTOHSCROLL or ES_AUTOVSCROLL or ES_MULTILINE or WS_VISIBLE,8,60,185,89,hWnd,Memo1ID,hInstance,0

mov hwndMemo1,eax
invoke CreateWindowEx,0,ADDR BtnClName,ADDR TextButton1,WS_CHILD or BS_DEFPUSHBUTTON or WS_VISIBLE,130,6,75,25,hWnd,Button1ID,hInstance,0

mov hwndButton1,eax
invoke CreateWindowEx,0,ADDR BtnClName,ADDR TextButton2,WS_CHILD or BS_DEFPUSHBUTTON or WS_VISIBLE,205,6,75,25,hWnd,Button2ID,hInstance,0

mov hwndButton2,eax
invoke CreateWindowEx,0,ADDR BtnClName,ADDR TextButton3,WS_CHILD or BS_DEFPUSHBUTTON or WS_VISIBLE,205,32,75,25,hWnd,Button3ID,hInstance,0

mov hwndButton3,eax
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start

Сразу хочу уточнить объявление всех переменных я вынес в отдельный файл под названием Our_Socket.inc так как по большому счету он такой же и для Сервера и для Шлюза.
Ниже его содержимое:

include \masm32\include\ws2_32.inc ;работа с сокетами — winsock
includelib \masm32\lib\ws2_32.lib
option casemap :none

VERSION1_0 equ 0100h
VERSION1_1 equ 0101h
VERSION2_0 equ 0200h
AF_INET equ 2
SOCK_STREAM equ 1
SOCKET_ERR equ -1
HOSTENT_IP equ 10h
Port equ 9999
buffsz equ 255
WM_SOCKET equ WM_USER+100

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
Conecting_people proto :DWORD
Error proto :DWORD
Disconecting_people proto :DWORD

.const
Edit1ID equ 1
Edit2ID equ 4
Button1ID equ 2
Button2ID equ 3
Button3ID equ 5
Memo1ID equ 6
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit1 HWND ?
hwndEdit2 HWND ?
hwndButton1 HWND ?
hwndButton2 HWND ?
hwndButton3 HWND ?
hwndMemo1 HWND ?
buff dd ?
;_______________
.data
wsadata WSADATA
saServer sockaddr_in
sockaddrsz dd sizeof sockaddr_in
tmp dd 0
FgSend dd ?
;buff db buffsz dup (0)
;_______________
text db 255 dup (0)
CompName dd 100 dup (0)
skt dd 0
Hello db «Программа для организации обмена небольшими сообщениями . © Gotech»,0
Header db «SmallMessageSender 1.0 Alpha»,0
Err_mess1 db «Машина с таким иминем не найдена!»,0
Err_mess2 db «Сокет небыл создан!»,0
Err_mess3 db «В коннекте отказанно!»,0
Err_mess4 db «Сокет не закрывается!»,0
Err_mess5 db «Косяк!»,0
Err_mess6 db «Привязка к порту не удалась!»,0
Err_mess7 db «Listen непрокатил!»,0
Err_mess8 db «Неудалось принять!»,0
Suc_mess1 db «Приконектились! УРА!»,0
Suc_mess2 db «Accepted!»,0
Suc_mess3 db «Sucess2»,0
Suc_mess4 db «Дисконектед!»,0
Suc_mess5 db «Sucess4»,0
Suc_mess6 db «Sucess5»,0
Suc_mess7 db «Sucess6»,0
Suc_mess8 db «Sucess7»,0
;_______________
ClassName db «Mega_client»,0
BtnClName db «button»,0
EditClName db «edit»,0
StatClName db «static»,0
LboxClName db «listbox»,0
CboxClName db «combobox»,0
ReditClName db «richedit»,0
RichEditLib db «riched32.dll»,0
FormCaption db «SmallMessageClient 1.0 Alpha»,0
;_______________
TextEdit1 db «death-star»,0
TextEdit2 db «I agree with you. «,0
TextButton1 db «Connect»,0
TextButton2 db «Disconect»,0
TextButton3 db «Send»,0
TextMemo1 db 0

Далее все как во взрослой программе пишем процедуру для создания сокета:

Conecting_people proc hWnd:HWND
invoke WSAStartup,010001h,offset wsadata
invoke gethostbyname,addr CompName
.if eax == 0
mov edx,offset Err_mess1
invoke Error,hWnd
ret
.endif

assume eax:ptr hostent
mov ebx,[eax].h_list
mov ebx,[ebx]
mov ebx,[ebx]
mov saServer.sin_addr,ebx
assume eax:nothing

mov saServer.sin_family,AF_INET
invoke htons,Port
mov saServer.sin_port,ax
invoke socket, AF_INET,\
SOCK_STREAM,\
0
.if eax == INVALID_SOCKET
mov edx,offset Err_mess2
invoke Error,hWnd
ret
.endif
mov skt,eax

invoke WSAAsyncSelect, skt, hWnd, WM_SOCKET, FD_ACCEPT or FD_READ or FD_WRITE or FD_CLOSE or FD_CONNECT
.if eax == SOCKET_ERROR
mov edx,offset Err_mess5
invoke Error,hWnd
ret
.endif

invoke connect,skt,\
offset saServer,\
sockaddrsz
.if eax == SOCKET_ERROR
.if ecx == 2733h
.else
mov edx,offset Err_mess3
invoke Error,hWnd
.endif
ret
.endif
ret
Conecting_people endp

Для большего понимания процессов которые там происходят (на самом деле чтобы лишний раз не запускать Ollydbg) я впихнул еще и обработчик ошибок в более или менее критических участках. Ну и процедура вывода их на экран прилагается:

Error proc hWnd:HWND
invoke MessageBox,hWnd,edx,0,MB_ICONINFORMATION
ret
Error endp

Принцип процедуры Conecting_people, ни чем не отличается от подобной процедуры в других языках программирования. Так же как и везде заполняется структура sockaddr_in создается сокет и собственно создается канал между двумя машинами. Пожалуй единственное отличие здесь заключается в использовании процедуры WSAAsyncSelect (описанной в библиотек ws2_32) которая указывает с какого сокета и какие события следует обрабатывать.

Забыл упомянуть, обработка всех событий происходит в процедуре WndProc, будь то перемещение курсора, сворачивание окна, изменение его размеров, в общем полностью все действия которые совершаются над окном в том числе и все действия с нашим созданным сокетом.

Для того чтобы данная процедура работала ее следует вызвать, а вызывать мы ее будем по нажатию кнопки Connect находящейся на нашем окошке. Вставляем следующий код в процедуру WndProc:

.ELSEIF uMsg == WM_COMMAND
mov eax,wParam
.IF lParam != 0
.IF ax == Button1ID
shr eax,16
.IF ax == BN_CLICKED
invoke GetWindowTextLength,hwndEdit1
mov tmp,eax
inc tmp
invoke SendMessage,hwndEdit1,WM_GETTEXT,tmp,addr CompName
invoke Conecting_people,hWnd
.ENDIF

Теперь по нажатию кнопки Connect, у нас будет считываться текст из Edit1 и помещаться в переменную CompName, после чего будет вызвана наша процедура.

Аналогичным образом поступаем с процедурой отвечающей за убивания созданного сокета:

Disconecting_people proc hWnd:HWND
invoke closesocket,skt
.if eax != 0
mov edx,offset Err_mess4
invoke Error,hWnd
ret
.endif
ret
Disconecting_people endp

Здесь по моему комментарии излишни.
И так же обрабатываем нажатие кнопки Disconect вызывая процедуру Disconecting_people.

.ELSEIF ax == Button2ID
shr eax,16
.IF ax == BN_CLICKED
invoke Disconecting_people,hWnd
.ENDIF

Теперь нам надо как то отправлять сообщения. Делать мы это будем как вы наверное уже догадались по нажатию клавиши Send. Я не стал выносить это все дело в отдельную процедуру, а прямо как есть написал в WndProc

.ELSEIF ax == Button3ID
shr eax,16
.IF ax == BN_CLICKED
.if FgSend!=FALSE
invoke GetWindowTextLength,hwndEdit2
mov tmp,eax
inc tmp
inc tmp
invoke SendMessage,hwndEdit2,WM_GETTEXT,tmp,addr buff
invoke send, skt, addr buff, tmp, 0
.if eax == SOCKET_ERROR
mov edx,offset Err_mess8
invoke Error,hWnd
ret
.endif
invoke SendMessage,hwndEdit2,WM_SETTEXT,NULL,0
.endif
.ENDIF
.ENDIF

Смысл тут такой. Если нам разрешено писать в сокет (за это у нас отвечать будет переменная FgSend), тогда мы считываем размер текста находящегося в Edit2 (чтобы знать сколько нам надо байт отослать), и всё его содержимое кладем в переменную buff. Следующим делом мы отсылаем все это безобразия в наш сокет и при возникновении ошибок, выводим их на экран. По моему все достаточно просто.

Ах да, чуть не забыл. Следует еще написать обработку для сокета. Выкладываю код, ниже поясню:

.ELSEIF uMsg == WM_SOCKET
mov eax, lParam
mov edx, eax
shr edx, 16
.IF ax == FD_ACCEPT
invoke MessageBox,hWnd,offset Suc_mess2,0,MB_ICONINFORMATION
.ELSEIF ax == FD_READ
xor esi,esi

.while esi!=buffsz
mov [buff+esi],0
inc esi
.endw

invoke recv, wParam, addr buff, buffsz, 0

.if eax == SOCKET_ERROR
mov edx,offset Err_mess8
invoke Error,hWnd
ret
.endif
invoke SendMessage,hwndMemo1,WM_SETTEXT,NULL,addr buff
.ELSEIF ax == FD_CLOSE
invoke MessageBox,hWnd,offset Suc_mess4,0,MB_ICONINFORMATION
.ELSEIF ax == FD_WRITE
mov FgSend, TRUE
.ELSEIF ax == FD_CONNECT
invoke MessageBox,hWnd,offset Suc_mess1,0,MB_ICONINFORMATION
.ENDIF

Здесь на самом деле все тоже просто. Если у нас сокет установлен на чтение, мы весь поток информации засовываем в переменную buff, первоначально ее занулив. Проверяем на ошибки, и если все нормально, то все что мы получили мы выводим в Memo, затерев предыдущее его содержимое.
Если у нас сокет закрылся, мы выводим сообщение, аналогично и с событиями Connect и Accept. Ну а если у нас сокет установлен на запись, то мы просто взводим флаг сигнализирующий о том, что писать в сокет можно!

image

Сервер

Полностью все тоже самое различие лишь в одной процедуре, а именно Connecting_people. Все различие заключается заменой вызова процедуры connect на процедуры bind и listen:

invoke bind, skt,offset saServer, sockaddrsz
.if eax == SOCKET_ERROR
mov edx,offset Err_mess6
invoke Error,hWnd
ret
.endif

invoke listen, skt, 10
.if eax == SOCKET_ERROR
mov edx,offset Err_mess7
invoke Error,hWnd
ret
.endif

image

Шлюз

Про шлюз скажу только что он в себе совмещает клиентскую и серверную части, каждый раз получая сообщения с одного сокета перенаправляет их на другой.

image

Источник

Оцените статью