简体   繁体   中英

Why doesn't this assembly HTTP server work?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM