;modificar szRoot para indicar la ubicacion del servidor, default C:\Server
;msvcrt no viene en el paquete de fasm, la crean con la utilidad dll2inc
;o hacen los include a mano^^
format PE Console
entry start
include '%fasminc%/win32a.inc'
include '%fasminc%/macro/if.inc'
MAX_QUEUE equ 100 ;max namber of pending connections
BUFFER_SIZE equ 2048 ;max size of the header sent by the client
MAX_URL_SIZE equ 1024 ;max size of url in the header send
SHOW_DETAILED_MSG equ TRUE ;set TRUE if want to see the full client message
section '.data' data readable writeable
wsa WSADATA
port dd 80
szPause db "PAUSE",0
h404 db "HTTP/1.1 404 Not Found",13,10,"Server: EnkoHttpServer 1.0.0.0 ",13,10,13,10,"<HTML><BODY>404 Not Found</BODY></HTML>",13,10
.size = $ - h404
h200 db "HTTP/1.1 200 OK",13,10,"Server: EnkoHttpServer 1.0.0.0 ",13,10,"Allow: GET",13,10,13,10
.size = $ - h200
szRoot db "C:\Server",0
szSlashes db '/\',0
szSlash db '\',0
szTockens db " ",13,10,0
szError db "ERROR: %i",13,10,0
szDot db ".",0
szFileNotExists db "File Not Exists",13,10,0
szInt db "%i",0
szFile db "FILE: %s",13,10,0
szClient db "Client IP: %s",13,10,0
szRequest db "REQUEST: %s",13,10,0
szStatus db "STATUS: %s",13,10,0
szString db "%s",0
szEndLine db 13,10,0
.size = $ - szEndLine
szBr db "<br>",0
szIndex db "index.html",0
szFileSent db "File Sent ok",13,10,0
.size = $ - szBr
wSocketVersion dd 0x0101
transmiteBuffer dd h200,h200.size,NULL,NULL
thread dd ?
peer dd ?
peerAddr sockaddr_in
sizePeerAddr dd sizeof.sockaddr_in
sock dd ?
sock_addr sockaddr_in
szBuffer db 32 dup ?
szIp db 16 dup ?
section '.code' code readable executable
start:
;inicializacion socket
invoke WSAStartup, [wSocketVersion], wsa
invoke socket,AF_INET,SOCK_STREAM,NULL
mov [sock],eax
mov [sock_addr.sin_family], AF_INET
invoke htons,[port]
mov [sock_addr.sin_port],ax
mov [sock_addr.sin_addr],NULL
invoke bind, [sock], sock_addr,sizeof.sockaddr_in
.if eax <> 0
invoke WSAGetLastError
cinvoke printf, szError, eax
.endif
invoke listen, [sock],MAX_QUEUE
;activamos la escucha del socket
accepted:
invoke accept, [sock],peerAddr,sizePeerAddr
mov [peer],eax
stdcall ipToString,[peerAddr.sin_addr],szIp
cinvoke printf, szClient,szIp
;nuevo hilo para cada conexion
invoke CreateThread, NULL,NULL, resolveConnection,[peer],NULL,NULL
;o mejor la funcion de abajo, ayudaria a la estabilidad y contra ataques DOS
;invoke QueueUserWorkItem, resolveConnection, [peer], NULL ;WT_EXECUTEDEFAUL
jmp accepted
invoke ExitProcess,0
;hilo que resuelve las peticiones
proc resolveConnection, lpParam
local lpeer: DWORD, lbuffer: DWORD, lurl: DWORD, lfile: DWORD
mov eax, [lpParam] Dermatoesqueleto mov [lpeer],eax
cinvoke malloc, BUFFER_SIZE
mov [lbuffer],eax
invoke recv, [lpeer],[lbuffer],BUFFER_SIZE,0
;vemos si hay un mensaje en el buffer y continuamos
.if eax <> 0
mov ebx,[lbuffer]
mov byte [ebx+eax],0
.if SHOW_DETAILED_MSG
cinvoke printf,szRequest, [lbuffer]
.endif
cinvoke strtok, [lbuffer], szTockens
.if eax <> 0
;solamente respondemos al mensaje GET
.if dword[eax] = "GET"
;url sring allocation
;buscamos la url en la peticion GET
;solamente las peticiones que terminan con alguna extensión
cinvoke malloc, MAX_URL_SIZE
mov [lurl],eax
cinvoke strcpy, [lurl],szRoot
cinvoke strtok, NULL, szTockens
cinvoke strcat,[lurl],eax
mov ebx,eax
cinvoke strlen, eax
mov edx,eax
dec edx
.repeat
dec eax
cmp byte[ebx+eax],"/"
.if ZERO?
mov byte[ebx+eax],"\"
.endif
.until eax = 0
.if byte[ebx+edx] = "\"
cinvoke strcat,[lurl],szIndex
.endif
cinvoke printf, szFile, [lurl]
;ya tenemos el nombre del archivo, ahora lo abrimos desde el disco
invoke CreateFile,[lurl],GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,0
;si el archivo existe, lo enviamos
.if eax <> INVALID_HANDLE_VALUE
mov [lfile],eax
invoke GetFileSize, [lfile],NULL
invoke TransmitFile, [lpeer],[lfile],eax,NULL,NULL,transmiteBuffer,NULL
.if eax
;escribimos en la consola el detalle
cinvoke printf, szStatus, szFileSent
.endif
invoke CloseHandle, [lfile]
.else
;no existe el archivo, error 404
cinvoke printf, szStatus, szFileNotExists
invoke send, [lpeer], h404, h404.size, 0
.endif
cinvoke free, [lurl]
.endif
.endif
.endif
.exit:
cinvoke free, [lbuffer]
invoke CloseHandle, [lpeer]
invoke ExitThread
ret
endp
;convertimos DWORD ip a una cadena
proc ipToString, ip, string
local buffer: DWORD
mov ebx, [ip]
mov esi,4
mov eax,[string]
mov byte [eax],0
.repeat
xor eax,eax
mov al,bl
cinvoke sprintf,[buffer],szInt,eax
cinvoke strcat,[string],[buffer]
cinvoke strcat,[string],szDot
shr ebx,8
dec esi
.until esi=0
cinvoke strlen, [string]
mov ebx, [string]
mov byte[ebx+eax-1],0
mov byte[buffer],0
ret
endp
section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL',\
msvcrt,'msvcrt.dll',\
wsock32, 'WSOCK32.DLL'
include '%fasminc%\api\kernel32.inc'
include '%fasminc%\api\user32.inc'
include '%fasminc%\api\msvcrt.inc'
include '%fasminc%\api\wsock32.inc'
El codigo funciona bastante bien. Si la url es complicada es probable que no la pueda resolver. En canto a saturacion o DDOS, sumamente susceptible.
Screenshot
http://i41.tinypic.com/5lxyfk.png
hay una duda que tengo:
Si se usa CreateThreade es necesario usar ExitThread... hasta ahi bien.
Ahora, si se usa QueueUserWorkItem, ¿Hace falta?
en la msdn no especifica, hice la prueba y aparentemente no. Ya que si se usa, en una prueba rapida se nota que deja de funcionar la segunda vez que se llama.
Saludos.
Si llama a ExitThread terminas el hilo trabajador ese, mala idea. Lo ideal es que nunca llames a esa funcion, con retornar es suficiente (fijate que codigo se ejecuta justo despues cuando retornas de un hilo creado con CreateThread).
Hola, la idea es que no es un hilo solo.
Es decir, para cada conexion nueva, se genera un hilo nuevo. Y cuando se desconecta el usuario, creo que tendria que terminar el hilo.
la tecnica es super saturable. 100 peticiones equivaldrian a 100 hilos.
Por eso creo que cada vez que el hilo hace su trabajo, deberia terminarlo.
(Hilo creado con CreateThread)
Citar
Si llama a ExitThread terminas el hilo trabajador ese, mala idea.
con usar ret, el hilo es eliminado?
Con hilo trabajador me refiero al que procesa el work item que encolas con QueueUserWorkItem (Queues a work item to a worker thread in the thread pool), el punto es que el hilo trabajador no lo creaste vos y tampoco lo tenes que terminar (un hilo trabajador de estos es, simplificando, un bucle esperando a que haya un work item en la cola para llamar al LPTHREAD_START_ROUTINE del mismo y continuar el bucle en cuanto este retorne). Con usar ret le devolves el control al hilo trabajador y su bucle, en el caso de un hilo creado con CreateThread la funcion a la que retornas llama a ExitThread inmediatamente. Es asi:
kernel32!BaseThreadInitThunk:
755c3388 8bff mov edi,edi
755c338a 55 push ebp
755c338b 8bec mov ebp,esp
755c338d 85c9 test ecx,ecx
755c338f 0f85752e0000 jne kernel32!BaseThreadInitThunk+0x15 (755c620a)
755c3395 ff7508 push dword ptr [ebp+8]
755c3398 ffd2 call edx ;el LPTHREAD_START_ROUTINE
755c339a 50 push eax
755c339b ff1504075c75 call dword ptr [kernel32!_imp__RtlExitUserThread (755c0704)] ds:002b:755c0704={ntdll!RtlExitUserThread (77e2d598)}
A... no habia entendido "hilo trabajador"....
QueueUserWorkItem, solo se usa RET. Confirmado :)
Gracias Nuevamente.
De nadas :)