Skip to content

Instantly share code, notes, and snippets.

@DGivney
Forked from xenomuta/httpd.asm
Last active August 29, 2024 12:31
Show Gist options
  • Save DGivney/5917914 to your computer and use it in GitHub Desktop.
Save DGivney/5917914 to your computer and use it in GitHub Desktop.
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 0x64334e77 ; d3Nw
push dword 0x503e3168 ; P>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
@pcordes
Copy link

pcordes commented May 8, 2021

Note that this is buggy; the bind system call passes sa_family=AF_UNIX, and doesn't end up binding to a specific TCP port, instead letting the kernel randomly pick which one it listens on.
https://stackoverflow.com/questions/67445637/why-doesnt-this-assembly-http-server-work

$ 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

@xenomuta
Copy link

Note that this is buggy; the bind system call passes sa_family=AF_UNIX, and doesn't end up binding to a specific TCP port, instead letting the kernel randomly pick which one it listens on.
https://stackoverflow.com/questions/67445637/why-doesnt-this-assembly-http-server-work

$ 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

please, refer to answer on original Gist here

@vinodkmwt
Copy link

guys i randomly stumbled upon this code. I wish I had the brains to understand this.

@xenomuta
Copy link

@vinod-somebody You do, my friend. Just have to get the basics.
Read about basic linux system calls, basic calling convention, and basic assembly.

@ludolpif
Copy link

Hi, I get the same bad strace result as @pcordes with invalid syscalls. I use 6.1.0-22-amd64 kernel on debian. I fixed the asm by changing :

- push word bx ; 1 AF_FILE
+ push word 0x02 ; 2 AF_INET

Then I use this Makefile :

.PHONY: all clean

all: httpd

clean:
        rm -f httpd

httpd: httpd.o
        ld -m elf_i386 -n -o httpd httpd.o
        strip httpd

httpd.o: httpd.asm
        yasm -f ELF httpd.asm

And I get a working 436 bytes x86 (32 bits) binary :

$ ls -l httpd
-rwxr-xr-x 1 ludolpif ludolpif 436 29 août  14:24 httpd
$ strace ./httpd 
execve("./httpd", ["./httpd"], 0x7ffc46bb40f0 /* 48 vars */) = 0
[ Process PID=429633 runs in 32 bit mode. ]
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(8800), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1)                            = 0
accept(3, NULL, NULL)                   = 4
fork()                                  = 429921
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=429921, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
listen(3, 1)                            = 0
accept(3, NULL, NULL
[waiting next connection]

On client side :

$ nc -v localhost 8800
localhost [127.0.0.1] 8800 (?) open
HTTP/1.0 200 OK
Content-Type: text/html

<h1>PwN3d!</h1>
^C

Remarks : this code send http reply right after TCP syn/ack, without waiting a http request like "GET /".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment