I came across arguably the smallest HTTP server in docker (written in assembly), and I would love to see it in action!
I think they took the code from https://gist.github.com/DGivney/5917914 :
section .text
global _start
_start:
xor eax, eax ; init eax 0
xor ebx, ebx ; init ebx 0
xor esi, esi ; init esi 0
jmp _socket ; jmp to _socket
_socket_call:
mov al, 0x66 ; invoke SYS_SOCKET (kernel opcode 102)
inc byte bl ; increment bl (1=socket, 2=bind, 3=listen, 4=accept)
mov ecx, esp ; move address arguments struct into ecx
int 0x80 ; call SYS_SOCKET
jmp esi ; esi is loaded with a return address each call to _socket_call
_socket:
push byte 6 ; push 6 onto the stack (IPPROTO_TCP)
push byte 1 ; push 1 onto the stack (SOCK_STREAM)
push byte 2 ; push 2 onto the stack (PF_INET)
mov esi, _bind ; move address of _bind into ESI
jmp _socket_call ; jmp to _socket_call
_bind:
mov edi, eax ; move return value of SYS_SOCKET into edi (file descriptor for new socket, or -1 on error)
xor edx, edx ; init edx 0
push dword edx ; end struct on stack (arguments get pushed in reverse order)
push word 0x6022 ; move 24610 dec onto stack
push word bx ; move 1 dec onto stack AF_FILE
mov ecx, esp ; move address of stack pointer into ecx
push byte 0x10 ; move 16 dec onto stack
push ecx ; push the address of arguments onto stack
push edi ; push the file descriptor onto stack
mov esi, _listen ; move address of _listen onto stack
jmp _socket_call ; jmp to _socket_call
_listen:
inc bl ; bl = 3
push byte 0x01 ; move 1 onto stack (max queue length argument)
push edi ; push the file descriptor onto stack
mov esi, _accept ; move address of _accept onto stack
jmp _socket_call ; jmp to socket call
_accept:
push edx ; push 0 dec onto stack (address length argument)
push edx ; push 0 dec onto stack (address argument)
push edi ; push the file descriptor onto stack
mov esi, _fork ; move address of _fork onto stack
jmp _socket_call ; jmp to _socket_call
_fork:
mov esi, eax ; move return value of SYS_SOCKET into esi (file descriptor for accepted socket, or -1 on error)
mov al, 0x02 ; invoke SYS_FORK (kernel opcode 2)
int 0x80 ; call SYS_FORK
test eax, eax ; if return value of SYS_FORK in eax is zero we are in the child process
jz _write ; jmp in child process to _write
xor eax, eax ; init eax 0
xor ebx, ebx ; init ebx 0
mov bl, 0x02 ; move 2 dec in ebx lower bits
jmp _listen ; jmp in parent process to _listen
_write:
mov ebx, esi ; move file descriptor into ebx (accepted socket id)
push edx ; push 0 dec onto stack then push a bunch of ascii (http headers & reponse body)
push dword 0x0a0d3e31 ; [\n][\r]>1
push dword 0x682f3c21 ; h/<!
push dword 0x6f6c6c65 ; ello
push dword 0x683e3148 ; H<1h
push dword 0x3c0a0d0a ; >[\n][\r][\n]
push dword 0x0d6c6d74 ; [\r]lmt
push dword 0x682f7478 ; h/tx
push dword 0x6574203a ; et :
push dword 0x65707954 ; epyT
push dword 0x2d746e65 ; -tne
push dword 0x746e6f43 ; tnoC
push dword 0x0a4b4f20 ; \nKO
push dword 0x30303220 ; 002
push dword 0x302e312f ; 0.1/
push dword 0x50545448 ; PTTH
mov al, 0x04 ; invoke SYS_WRITE (kernel opcode 4)
mov ecx, esp ; move address of stack arguments into ecx
mov dl, 64 ; move 64 dec into edx lower bits (length in bytes to write)
int 0x80 ; call SYS_WRITE
_close:
mov al, 6 ; invoke SYS_CLOSE (kernel opcode 6)
mov ebx, esi ; move esi into ebx (accepted socket file descriptor)
int 0x80 ; call SYS_CLOSE
mov al, 6 ; invoke SYS_CLOSE (kernel opcode 6)
mov ebx, edi ; move edi into ebx (new socket file descriptor)
int 0x80 ; call SYS_CLOSE
_exit:
mov eax, 0x01 ; invoke SYS_EXIT (kernel opcode 1)
xor ebx, ebx ; 0 errors
int 0x80 ; call SYS_EXIT
I can assemble and link the code without any errors.
But when I run it, nothing seems to happen.
What do I need to do in order to see the output from the assembly HTTP server in my browser?
Seems to barely sort of work for me, although it is buggy as Margaret Bloom noticed. (It listens on a random port since it makes a bad bind
syscall. Presumably passing the wrong number for sa_family
)
After building / linking with nasm -felf32
/ ld -melf_i386
, I ran it under strace to see what it did.
$ strace ./httpd
execve("./httpd", ["./httpd"], 0x7ffde685ac10 /* 54 vars */) = 0
[ Process PID=615796 runs in 32 bit mode. ]
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
bind(3, {sa_family=AF_UNIX, sun_path="\"`"}, 16) = -1 EAFNOSUPPORT (Address family not supported by protocol)
syscall_0xffffffffffffff66(0x4, 0xffd53c58, 0, 0x8049043, 0x3, 0) = -1 ENOSYS (Function not implemented)
syscall_0xffffffffffffff66(0x5, 0xffd53c4c, 0, 0x804904d, 0x3, 0) = -1 ENOSYS (Function not implemented)
syscall_0xffffffffffffff02(0x5, 0xffd53c4c, 0, 0xffffffda, 0x3, 0) = -1 ENOSYS (Function not implemented)
listen(3, 1) = 0
accept(3, NULL, NULL
The mov al, callnum
trick to save bytes assumes the upper bytes of EAX are still 0. If they aren't (all-ones from a -errno
return), the next few syscalls are with invalid call numbers. But eventually it does listen(3,1)
and accept
, so it is listening somewhere. I found its PID with ps
, then used lsof
to find out what port it was listening on:
$ lsof -p 615796
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
httpd 615796 peter cwd DIR 0,55 940 1 /tmp
httpd 615796 peter rtd DIR 0,27 158 256 /
httpd 615796 peter txt REG 0,55 5412 56241 /tmp/httpd
httpd 615796 peter 0u CHR 136,20 0t0 23 /dev/pts/20
httpd 615796 peter 1u CHR 136,20 0t0 23 /dev/pts/20
httpd 615796 peter 2u CHR 136,20 0t0 23 /dev/pts/20
httpd 615796 peter 3u IPv4 86480691 0t0 TCP *:36047 (LISTEN)
Connecting to that port with nc
(netcat) gets it to dump its fixed string payload and keep the connection open:
$ nc localhost 36047
HTTP/1.0 200 OK
Content-Type: text/html
<h1>PwN3d!</h1>
CONTROL-C
$
Pointing Chromium at http://localhost:36047/ also loaded a page, but since the connection stays open it's still spinning waiting for more data.
The strace output after a few connections has grown to
accept(3, NULL, NULL) = 4
fork() = 615904
listen(3, 1) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=615904, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL) = 5
fork() = 615986
listen(3, 1) = 0
accept(3, NULL, NULL) = ? ERESTARTSYS (To be restarted if SA_RESTART is set)
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=615986, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
accept(3, NULL, NULL
BTW, another minimal HTTPD, one that builds to an even smaller binary thanks to its build scripts, is https://github.com/Francesco149/nolibc-httpd . There's a C version (which calls handwritten asm wrappers for system calls, instead of just using inline asm). See How does this C program without libc work? about it.
As I mentioned there, I got the C version down from 1.2k to 992 bytes, by switching to clang -Oz
and using inline asm for system calls, so the whole thing could be a leaf function. (Not needing to save/restore registers around anything.) https://github.com/pcordes/nolibc-httpd/commit/ad3a80b89b98379304f1525339fa71700bf1a15d
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.