Linux: простой web сервер на asm

Спятил, да? А ещё если напишу, что планировал сделать многопоточность и нечто CGI-интерфейса? Вполне может быть, в рамках just for fun

Но остановился на том, чтоб отдавать статические страницы – время не дало допилить, да и энтузиазм уменьшился. Идея мне пришла из за курсовой работа по “Системному программированию” В СПбГУ ИТМО. Всех заставили писать резидент под DOS, на 8086 архитектуре. Не торт.

Основные фитчи, которые я успел реализовать:

  • Форк, при необходимости
  • Чтение конфига(ini-like)
  • Создание слушающего сокета, установка параметров
  • Парсер заголовков и запроса
  • Обработчик запросов статического контента(не в /cgi-bin/)
  • В некоторых местах совершенно корявую работу со стеком

Реализовано на NetwideASM, c частичным использованием функций glibc. Работать сервер будет только на 32-битном процессоре i686+, из за того, что дёргается прерывание ядра Linux – а номера функций отличаются в зависимости от процессора.

Алгоритм работы – довольно очевидный: пришло соединение, получили запрос, разобрали, прочитали запрашиваемый файл, выдали содержимое в сокет, закончили, в ожидании нового клиента. Сразу про строку запроса – на выход из DocumentRoot НЕ проверяется – это потенциальная дыра. [offtop]Я же говорил, энтузиазм вместе со временем закончились быстро.[/offtop]. Отсутствие файла так же не обрабатывается – клиенту выдаётся пустой ответ

Начиная(по иерархии) с парсера запроса память выделяется только из стека или из кучи – это прямой путь к многопоточности. И вроде даже корректно везде освобождается.

Файлы названы по смыслу, где то иногда мелькают переменные, со странным содержимым – это для отладки. А ещё в коде очень много комментариев, на английском есс-но. Плохом английском. Особенно мне надоело писать комменты к “add esp, 4 * n” – потому как очень много вызовов glibc, следовательно много раз нужно подчищать стек. Лучше чаще чем реже, но запутаться в один прекрасный момент, а потом искать в gdb где находится бага.

GDB заслуживает отдельных ругательств слов. Это не удобный, очень сложный и гибкий отладчик. К ему сложно, но вполне реально привыкнуть. Хbотя взгляд моего знакомого – профессионального дизассемблерщика(если можно так назвать), который всегда пользовался IDA и увидел gdb – не передать :) Спасибо, Тимофей(killer_of_mouse)!

Screenshot

Чтобы разбавить сухой текст описания – скриншот отданной страницы:

Source

Дальше – много много кода. Аккуратнее :)

aweb.asm


EXTERN kSysExit, kFork, loadConfig, startupMessage, sock
EXTERN cfgBindIp, cfgBindPort, isDoFork, strProcessForked

SECTION .code
GLOBAL _start ; program start point
_start:

mov eax, dword [isDoFork] ; should we do fork() ?
cmp eax, 0 ; really?
jz .afterFork ; no, just continue loading

.doFork:
call kFork ; do fork()
cmp eax, 0 ; 0 means we are forked
je .afterFork ; run the server

jmp .exitProgram ; parent process should exit

.afterFork:
call loadConfig ; load configuration from file

call startupMessage ; echo startup message to console

call sock ; create and listen network socket

.exitProgram:
call kSysExit ; stop the process

config.asm

; External functions
EXTERN kSysExit

; External C functions
EXTERN fgets, fopen, fclose, feof, printf
EXTERN strchr, strlen, strtok, strcmp, atoi, strcpy
EXTERN inet_addr

; External data
EXTERN strFileNotFound, strTerminating, fOpenRead, fileConfig

; Exported symbols
GLOBAL loadConfig:FUNCTION
GLOBAL cfgBindPort:DATA, cfgBindIp:DATA, cfgDocumentRoot:DATA, cfgRoot:DATA

SECTION .data
cfgBindIp: dd 127 << 0 | 0 << 8 | 0 << 16 | 1 << 24 cfgBindPort: dd 808 cfgRoot: db '/srv/www/local/' times 120 db 0 SECTION .data var: .fHandle dd 0 .num db 'num = %d', 10, 0 .pair db '|%s = %s|', 10, 0 .hint db ' hint = "%s"', 10, 0 .hintd db ' hint = "%d"', 10, 0 .delimiter db '= ', 0 .key dd 0 .value dd 0 .kPort db 'port', 0 .kIp db 'ip', 0 .kRoot db 'root', 0 SECTION .bss buffLen: equ 1024 buff: times buffLen db SECTION .code ; procedure load and parse configuration data loadConfig: push dword fOpenRead ; mode - read push dword fileConfig ; config location CALL fopen ; do fopen() add esp, 2 * 4 ; correct stack mov [var.fHandle], eax ; var.fHandle = eax cmp eax, 0 ; eax ? jne .readFile ; eax != 0 - that's ok! .cantOpen: ; Output error message push dword fileConfig push dword strFileNotFound CALL printf add esp, 2 * 4 push dword strTerminating CALL printf add esp, 4 CALL kSysExit ; quit... jmp .endProc .readFile: .checkEof: ; Check for end of file push dword [var.fHandle] ; push file handle CALL feof ; feof() add esp, 4 ; correct stack head test eax, eax ; eax? jnz .closeFile ; EOF?! .getLine: ; get 1 line from file push dword [var.fHandle] ; push handle again push dword buffLen ; buffer length push dword buff ; buffer pointer CALL fgets ; call fgets() add esp, 4 * 3 ; corrent stack test eax, eax ; check for error jz .readFile ; eax = 0 means error .correctString: ; Correct end of string - where '#' located push dword '#' ; symbol push dword buff ; config string CALL strchr ; call strchr() add esp, 4 * 2 ; stack correct cmp eax, 0 ; eax == 0 ? je .checkLength ; so check length next mov [eax], byte 0 ; set end of string .checkLength: ; check for zero length push dword buff ; string from file CALL strlen ; strlen() add esp, 4 ; esp+=4.. test eax, eax ; what about eax? jz .readFile ; It's empty string!? .removeChr10: ; remove end-of string symbol push dword 10 ; symbol push dword buff ; config string CALL strchr ; call strchr() add esp, 4 * 2 ; stack correct test eax, eax jz .isKeyValuePair mov [eax], byte 0 .isKeyValuePair: ; Search for '=', which split name = value" pair push dword '=' ; symbol push dword buff ; config string CALL strchr ; call strchr() add esp, 4 * 2 ; stack correct test eax, eax ; eax?! jz .readFile ; if eax == 0, read next line .split: ; Splits pair push dword var.delimiter ; push addr to delimiter '=' push dword buff ; string from config file CALL strtok ; strtok() add esp, 4 * 2 ; esp += 8 mov [var.key], eax ; key string push dword var.delimiter; string push dword 0 ; push NULL to retrive next token CALL strtok ; strtok(NULL, delimiter) add esp, 4 * 2 ; esp += 8 mov [var.value], eax ; value string .checkForPort: ; is key == port? push dword [var.key] ; string 1 push dword var.kPort ; string 2 CALL strcmp ; do compare add esp, 4 * 2 ; fix stack test eax, eax ; what about eax? jne .checkForIp ; not equals? next check push dword [var.value] ; addr of string with number CALL atoi ; do calculate integer value add esp, 4 ; correct stack mov [cfgBindPort], ax ; save value jmp .checkEnd ; we are done .checkForIp: ; is key == ip? push dword [var.key] ; string 1 push dword var.kIp ; string 2 CALL strcmp ; do strcmp() add esp, 4 * 2 ; correct stack test eax, eax ; what about eax? jne .checkForRoot ; not equals? next check push dword [var.value] ; ip addr CALL inet_addr ; do transform add esp, 4 ; correct stack mov [cfgBindIp], eax ; save value .checkForRoot: ; is key == root? push dword [var.key] ; string 1 push dword var.kRoot ; string 2 CALL strcmp ; do strcmp() add esp, 4 * 2 ; correct stack test eax, eax ; what about eax? jne .checkEnd ; not equals? next check push dword [var.value] ; copy from push dword cfgRoot ; copy to CALL strcpy ; copy config value item add esp, 4 * 2 ; correct stack .checkEnd: jmp .readFile push dword [var.value] push dword [var.key] push dword var.pair call printf add esp, 4 * 3 jmp .readFile ; read next line .closeFile: push dword [var.fHandle] ; file handle to close CALL fclose ; make fclose() add esp, 4 ; stack.. .endProc: ret

consts.asm

SEGMENT .rodata

GLOBAL fOpenRead:DATA, fOpenWrite:DATA, fOpenAppend:DATA
fOpenRead: db 'r', 0 ; read only flag
fOpenWrite: db 'w', 0 ; write flag
fOpenAppend: db 'a', 0 ; append file

GLOBAL strFileNotFound:DATA, strTerminating:DATA, strDebugGeneral:DATA
GLOBAL strStartMsg:DATA, strProcessForked:DATA, strHandlerStatic:DATA
strFileNotFound: db 'File "%s" is not found!', 10, 0
strTerminating: db 'Terminating application..', 10, 0
strDebugGeneral: db '%s: %s', 10, 0
strStartMsg: db 10, 'Starting up server, build date: ', \
__DATE__, ' ',__TIME__, \
'. Bind to %s:%d', 10, 0
strProcessForked: db 'Process forked, pid: %d', 10, 0
strHandlerStatic: db 'handlerStatic: %s', 10, 0

GLOBAL fileDebug:DATA, fileConfig:DATA
fileDebug: db './debug.log',0 ; file for debug info
fileConfig: db './aweb.conf',0 ; config file

GLOBAL maxConnections:DATA
maxConnections: dd 1024

GLOBAL maxHeaders:DATA
maxHeaders: dd 1024

GLOBAL cgiBinFolder:DATA
cgiBinFolder: db '/cgi-bin', 0 ; prefix for cgi programs

GLOBAL headerHTTP200:DATA, headerStd:DATA, headerServer:DATA
headerHTTP200: db 'HTTP/1.0 200 OK', 10, 13, 0
headerServer: db "Server: ruX's simple web server written in NASM", 10, 13, 0
headerStd:
db 'Connection: close', 10, 13
db 'Cache-Control: no-cache,no-store,max-age=0,must-revalidate', 10, 13
db 10, 13
db 0

GLOBAL isDoFork:DATA
isDoFork: dd 1

sockets.asm


EXTERN cfgBindIp, cfgBindPort, maxConnections, headerServer
EXTERN htons, shutdown, fprintf, printf, malloc, free, realloc, strlen
EXTERN kSocketCall, kClose, processRequest
GLOBAL sendServerHeader:FUNCTION

; Constants used for arguments(from kernel source)
%define AF_INET 2
%define IPPROTO_TCP 6
%define SOCK_STREAM 1
%define PF_INET 2
%define MSG_PEEK 2
%define MSG_WAITALL 100h
%define SOL_SOCKET 1
%define SO_REUSEADDR 2

; Function numbers
%define SYS_SOCKET 1
%define SYS_BIND 2
%define SYS_LISTEN 4
%define SYS_ACCEPT 5
%define SYS_SEND 9
%define SYS_RECV 10
%define SYS_SETSOCKOPT 14

; Step increase memory for receive buffer
%define REALLOC_STEP 64

SECTION .data

connect db "We have a connection!", 10, 0 ; First string...
hello db "Hello... =]", 10, 0 ; Second string...
goodbye db "Goodbye... =[", 0 ; Third string...
socksbroke db "ERROR: socket() failed!", 10, 0 ; Fourth string...
bindsbroke db "ERROR: bind() failed!", 10, 0 ; Fifth string...
listensbroke db "ERROR: listen() failed!", 10, 0 ; Sixth string...
acceptsbroke db "ERROR: accept() failed!", 10, 0 ; Seventh string...
retval db "value buffer: %d, recv: %d", 10, 0
retstr db "recived: %s", 10 , 0
html db "HTTP 1.1", 10, 0, '$'
html_len equ $ - html
one dd 1

sa: ; sockaddr_in structure
.sin_family dw 0
.sin_port dw 0
.sin_addr dd 0

sock_args: ; socket() function argument
dd PF_INET, SOCK_STREAM, IPPROTO_TCP

bind_args: ; bind() function arguments
.fd dd 0 ; socket handle
.sockaddr dd sa ; pointer to sockaddr structure
.socklen_t dd 16 ; socklen_t addrlen

listen_args: ; listen() function arguments
.sock dd 0 ; sock handle
.backlog dd maxConnections ; max connections

accept_args: ; accept() arguments
.sockfd dd 0 ; socket handle
.addr dd 0 ; struct sockaddr *addr
.addrlen dd 0 ; socklen_t *addrlen

sockopts_args: ; setsockopt() and getsockopt() args
.sockfd dd 0 ; socket handle
.level dd SOL_SOCKET ; manipulation level
.optname dd SO_REUSEADDR ; option in selected level
.optval dd one ; option value
.optlen dd $ - one ; size of option value

socketfd: dd 0 ; socket handle

section .code ; Section Declaration
GLOBAL sock

sock:

; mov ebp, esp

;int socket(int domain, int type, int protocol);
.doSocket:
mov ebx, SYS_SOCKET ; socket() = int call 1
mov ecx, dword sock_args ; arguments to socket()
CALL kSocketCall
mov [socketfd], eax ; save socket handle
;
cmp eax, -1
je sockerr

;int setsockopt(int s, int level, int optname,
; const void *optval, socklen_t optlen);
.setSockOpt:
; ignoring port in state TIME_WAIT
mov ebx, SYS_SETSOCKOPT
mov eax, dword [socketfd]
mov [sockopts_args.sockfd], eax
mov ecx, dword sockopts_args
CALL kSocketCall

cmp eax, -1
je sockerr

;int bind(int socket, const struct sockaddr *address,
; socklen_t address_len);
.doBind:
; fillup sockaddr
mov [sa.sin_family], word AF_INET ; address family
mov al, byte [cfgBindPort+1] ; reverse bytes hi=>lo
mov ah, byte [cfgBindPort] ; reverse bytes lo=>hi
mov [sa.sin_port], ax ; set port
mov eax, dword [cfgBindIp] ; get ip from config
mov [sa.sin_addr], eax ; set ip to structure

; form arguments
mov eax, dword [socketfd] ; copy socket
mov [bind_args.fd], eax ; handle to struct

mov ebx, SYS_BIND ; require bind()
mov ecx, bind_args ; bind arguments
CALL kSocketCall
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Do a little error-echecking here...
cmp eax, -1
je binderr

; int listen(int socket, int backlog);
.doListen:
mov eax, [socketfd] ; copy socket handle
mov [listen_args.sock], eax ; to structure listen_args
mov ebx, SYS_LISTEN ; require listen()
mov ecx, listen_args ; arguments
CALL kSocketCall
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Do a little error-echecking here...
cmp eax, -1
je listenerr

;accept(fd, NULL, 0);
.doAccept:
mov eax, [socketfd] ; copy socket handle
mov [accept_args.sockfd], eax ; to structure

mov ebx, SYS_ACCEPT ; we need accept() function
mov ecx, accept_args ; ptr to arguments
CALL kSocketCall

; Do a little error-echecking here...
cmp eax, -1
je accepterr

.processConnection: ; here we have a connected socket peer in eax
push eax
CALL connectionHandler

jmp .doAccept
;
;exit_s(0)
exit_s:
; mov esp, ebp
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; These are just to print an error message to stdout should
; one of our socketcall()'s fail.
sockerr:
mov eax, 4
mov ebx, 1
mov ecx, socksbroke
mov edx, 24
int 0x80
jmp exit_s

binderr:
mov eax, 4
mov ebx, 1
mov ecx, bindsbroke
mov edx, 22
int 0x80
jmp exit_s

listenerr:
mov eax, 4
mov ebx, 1
mov ecx, listensbroke
mov edx, 24
int 0x80
jmp exit_s

accepterr:
mov eax, 4
mov ebx, 1
mov ecx, acceptsbroke
mov edx, 24
int 0x80
jmp exit_s

; connection handler function
; get 1 arg - socket handle
connectionHandler:
push ebp
mov ebp, esp ; preserve esp
%define sock dword [ebp+8] ; connected socket
jmp .initVars

.vars: ; declare variables (allocate memory for each)
; begin recv_args structure (argument for recv function)
%define recv_args 8 ; main offset from esp
%define recv_args_sock esp+recv_args
%define recv_args_buffer esp+4+recv_args
%define recv_args_length esp+8+recv_args
%define recv_args_flags esp+12+recv_args
sub esp, 16 ; allocate memory for recv_args
%define send_args 16
%define send_args_sock 16

.initVars:
mov eax, sock
mov [recv_args_sock], eax
mov [recv_args_flags], dword MSG_PEEK
mov [recv_args_length], dword REALLOC_STEP

.code:

.alloc:
push dword [recv_args_length]; count of bytes we need
CALL malloc ; allocate memory from heap
add esp, 4 ; correct stack
mov [recv_args_buffer], eax ; save pointer to buffer

.recvLength:
mov ebx, SYS_RECV ; we need recv()
mov ecx, esp
add ecx, recv_args ; arguments
CALL kSocketCall

cmp eax, dword -11 ; try again code?
je .recvLength ; recieve again

mov edx, eax
mov ebx, dword [recv_args_length]
push edx
push ebx
push retval
call printf
add esp, 8
pop eax

cmp eax, [recv_args_length]
jb .recv

.realoc:
add [recv_args_length], dword REALLOC_STEP
mov eax, dword [recv_args_buffer]
push dword [recv_args_length]
push eax
CALL realloc
add esp, 2 * 4
mov [recv_args_buffer], eax

jmp .recvLength

.recv:

mov ebx, SYS_RECV ; we need recv()
mov [recv_args_flags], dword 0 ; without flags!
mov ecx, esp ; point ecx ..
add ecx, recv_args ; .. to arguments
CALL kSocketCall ; do recv()
mov [recv_args_length], eax ; store received message length

cmp eax, 0 ; is there any error?
jbe .closeSocket ; then cancel request

push dword [recv_args_buffer]
push dword sock
CALL processRequest ; let's process request!

.closeSocket:
push sock
call kClose

.freeMem:
mov eax, dword [recv_args_buffer]
push eax
CALL free
add esp, 8
mov [recv_args_length], dword 0 ; mark block as free

.return:
cmp [recv_args_length], dword 0 ; we used malloc?
jne .freeMem ; yeah, so we need free memory

mov esp, ebp ; restore stack poiter
pop ebp
ret 4

;write(1, "We have a connection!", 22);
mov eax, 4 ; write() syscall
mov ebx, 1 ; stdout
mov ecx, connect ; our string
mov edx, 22 ; 22 characters in length
int 0x80 ; Call the kernel
;
;write(fd, "Hello... =]", 12);
mov eax, 4 ; write() syscall
mov ebx, edi ; sockfd
mov ecx, hello ; our string to send
mov edx, 12 ; 12 characters in length
int 0x80 ; Call the kernel
;push dword hello
;push dword edi
;call fprintf
;add esp, 8
;
;write(fd, "Goodbye... =[", 14);
mov eax, 4 ; write() syscall
mov ebx, edi ; sockfd
mov ecx, goodbye ; our string to send
mov edx, 14 ; 14 charactes in length
int 0x80 ; Call the kernel
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Now we clean up (Close the open file descriptor/s etc.) and exit_s
push dword edi
Call shutdown
pop eax

;close(fd)
push edi
CALL kClose

mov eax, 0
ret

; send to socket 'Server' header
; first argument in stack - sock handle
sendServerHeader:
push ebp
mov ebp, esp

push dword headerServer ; string to send
call strlen ; get string length
add esp, 4 ; correct stack

push dword 0 ; flags
push eax ; length
push dword headerServer ; header string
push dword [ebp+8] ; socket handle
mov ecx, esp ; ptr to struct in stack
mov ebx, 9 ; send() function number
call kSocketCall ; call it
add esp, 4 * 4 ; correct stack space

mov esp, ebp ; restore base stack pointer
pop ebp

ret 4

helpers.asm

; debugLine TYPE, INFO
%macro debugLine 2
EXTERN fileDebug, fopen, fclose, fprintf, fOpenAppend, strDebugGeneral
jmp %%code ; go to code area
%%type: db %1, 0 ; type of message
%%info: db %2, 0 ; message

%%code:
pusha ; save registers

push fOpenAppend ; just 'a\0' string
push fileDebug ; filename
CALL fopen ; call fopen()
add esp, 2 * 4 ; clean stack

push eax ; small trick - push arg for fclose

push %%type ; offset to first %s
push %%info ; second %s
push strDebugGeneral; format string
push eax ; file handle
CALL fprintf ; fprintf()
add esp, 4 * 4 ; correct stack only for fprintf

CALL fclose ; call fclose(), handle pushed before
add esp, 4 ; correct stack

%%endmacro:
popa ; restore back registers

%endmacro

requests.asm


GLOBAL processRequest
EXTERN strlen, printf, fprintf, strtok, malloc, memset, strchr, free, strncmp, send
EXTERN strDebugGeneral, maxHeaders, cgiBinFolder, handlerStatic
EXTERN kSocketCall

SEGMENT .data
header_sep: db 10, 13, 0
juststr: db "%s",10,0

SEGMENT .code

STRUC tRequest, -30
.filename: resd 1
.method: resd 1
.socket: resd 1
.request: resd 1
.requestLen: resd 1
ENDSTRUC

; Process user requests
; arguments: socket/(dword) request(dword,ptr)
processRequest:
push ebp ; preserve ebp
mov ebp, esp ; new local offset
sub esp, 30 ; space for structure in stack
%define lv_offset 30
sub esp, lv_offset
%define headers ebp - 30 - lv_offset + 0
%define i ebp - 30 - lv_offset + 4
%define j ebp - 30 - lv_offset + 8
%define buf ebp - 30 - lv_offset + 12
%define len ebp - 30 - lv_offset + 16
%define str ebp - 30 - lv_offset + 20

mov eax, dword [ebp + 8] ; get socket id from stack
mov dword [ebp + tRequest.socket], eax
mov eax, dword [ebp + 12] ; get request string
mov dword [ebp + tRequest.request], eax

push dword [ebp + tRequest.request] ; calculate
call strlen ; request length
add esp, 4 ; cleanup stack
mov dword [ebp + tRequest.requestLen], eax ; remember length

push dword 0
push '->> '
mov eax, esp
push dword 0
push '<<- ' push esp push eax push strDebugGeneral call printf add esp, 4 * 3 .allocBuffers: ;.. for header line push dword 1 << 14 ; 16Kb call malloc ; requre some memory add esp, 4 ; correct stack mov [buf], eax ; save pointer to memory block mov [eax], dword 0 ; length = 0 ; ..for array of pointers to string(enviroment) mov eax, dword [maxHeaders] mov bl, 4 mul bl push eax call malloc mov [headers], eax push dword 0 push dword [headers] call memset add esp, 4 * 3 ; ..for url buffer push dword 2048 ; maximum url length (rfc) call malloc ; do allocate add esp, 4 ; correct stack mov [ebp + tRequest.filename], eax ; save point to url .breakHeaders: push dword header_sep push dword [ebp + tRequest.request] call strtok add esp, 4 * 2 cmp eax, 0 je .breakHeadersEnd .httpRequest: mov [str], eax ; save pointer to header string mov ebx, [str] mov [ebp + tRequest.method], ebx push dword ' ' push dword [str] call strchr add esp, 4 * 2 mov [eax], byte 0 ; break method and url inc eax mov [ebp + tRequest.filename], eax push dword ' ' push dword [ebp + tRequest.filename] call strchr add esp, 4 * 2 mov [eax], byte 0 ; break url and http push dword header_sep push dword 0 call strtok ; AAAAAA strtok_r add esp, 4 * 2 cmp eax, 0 je .breakHeadersEnd .breakHeadersLoop: mov [str], eax ; save pointer to header string push dword [str] ; get current header length push juststr call printf pop eax pop eax ; jmp .breakHeadersEnd push dword header_sep push dword 0 call strtok ; AAAAAA strtok_r add esp, 4 * 2 cmp eax, 0 jne .breakHeadersLoop .breakHeadersEnd: push dword [ebp + tRequest.filename] push dword juststr call printf mov eax, ebp add eax, tRequest push eax CALL preprocessRequest .endproc: .freeBuffers: push dword [headers] call free push dword [buf] call free mov esp, ebp ; restore base stack pointer pop ebp ret 4 * 2 preprocessRequest: push ebp ; preserve ebp mov ebp, esp ; new local offset %define reqFilename ebp+16 push dword 0 push dword 10 ; count of bytes push dword [reqFilename] push dword [ebp+8] pop eax add eax, 8 push eax call send add esp, 4*4 push dword cgiBinFolder ; get '/cgi-bin' call strlen ; get length of string add esp, 4 ; correct stack push eax ; compare length is a last argument push dword cgiBinFolder ; address of string push dword [reqFilename]; addr of file name call strncmp ; compare start of strings add esp, 4 * 3 ; correct stack cmp eax, 0 ; is start are same in both strings? jz .cgiHere ; hey! there is cgi program! .staticHere: mov ebx, ebp ; filename - first field in structure add ebx, 16 push dword [ebx] call handlerStatic ; call static content processor jmp .endproc .cgiHere: jmp .cgiHereNext .cgiherestring: db 'Hey! There is cgi interface!', 10, 0 .cgiHereNext: push dword .cgiherestring call printf add esp, 4 jmp .endproc .endproc: mov esp, ebp ; restore base stack pointer pop ebp ret 4

procs.asm


EXTERN cfgBindPort, cfgBindIp, strStartMsg
EXTERN inet_ntoa, printf

GLOBAL startupMessage:FUNCTION

; Just output startup message
startupMessage:
xor eax, eax
mov ax, [cfgBindPort]
push eax

push dword [cfgBindIp]
call inet_ntoa
add esp, 4

push eax
push strStartMsg
call printf
add esp, 4 * 3

ret

Makefile


##
# SPbSU IFMO, Zaharov Ruslan, 3156.
# 11.03.2010 -
# Course work - simple web server writen in 80386 asm
# Using NASM syntax
##

################################
# Some variables
NASM = nasm -t -g -O0 -f elf
NASM_DBG = -F dwarf
GCC = gcc -march=i686 -m32 -nostartfiles -Wall -g
PRG = aweb
TARGETS = aweb.o kcall.o config.o vars.o strings.o procs.o \
consts.o sockets.o requests.o \
handler_static.o handler_cgi.o

################################
# Default target
prog: all

################################
# Objects
kcall.o:
$(NASM) kcall.asm -o $@ -l $@.lst $(NASM_DBG)

aweb.o:
$(NASM) aweb.asm -o $@ -l $@.lst $(NASM_DBG)

config.o:
$(NASM) config.asm -o $@ -l $@.lst $(NASM_DBG)

consts.o:
$(NASM) consts.asm -o $@ -l $@.lst $(NASM_DBG)

vars.o:
$(NASM) vars.asm -o $@ -l $@.lst $(NASM_DBG)

strings.o:
$(NASM) strings.asm -o $@ -l $@.lst $(NASM_DBG)

procs.o:
$(NASM) procs.asm -o $@ -l $@.lst $(NASM_DBG)

sockets.o:
$(NASM) sockets.asm -o $@ -l $@.lst $(NASM_DBG)

requests.o:
$(NASM) requests.asm -o $@ -l $@.lst $(NASM_DBG)

handler_static.o:
$(NASM) handler_static.asm -o $@ -l $@.lst $(NASM_DBG)

handler_cgi.o:
$(NASM) handler_cgi.asm -o $@ -l $@.lst $(NASM_DBG)

################################
# General targets
all: $(TARGETS)
$(GCC) $(TARGETS) -g -o $(PRG)

strip: all
strip $(PRG)

clean:
rm -rf $(TARGETS) $(PRG) *.o.lst

run: all
./$(PRG)

gdb: all
gdb ./$(PRG)

ddd: all
ddd ./$(PRG) > /dev/null

stat-code:
@echo Total lines in source: `cat *asm Makefile | wc -l`
@echo Chars in source: `cat *asm Makefile | wc -c`

stat: stat-code

Statistics

Сделал простую статистику по коду: количество строк и символов в исходниках:

$ make stat
Total lines in source: 1477
Chars in source: 40455

Просто иногда интересно посмотреть бывает :)

Download

Можно скачать всё сразу в архиве: aweb.tar.gz
Перед запуском не забудьте поменять DocumentRoot

Final

Для чего я выложил обрезок от веб сервера, в котором есть уязвимости и нет CGI и многопоточности? Уверен, те, кто пришёл к этому посту из поисковика найдут для себя ОЧЕНЬ много полезного, потому как информации в рунете псро программирование под линукс на NASM очень мало, и большенство примеров - всего лишь hello word. Кстати, англоязычные ресурсы не блещут количеством информации.

  • woklex

    Мне сразу пришла это идея в голову, когда я начал изучать асм. И вот случайно наткнулся на реализацию :)