Skip to content

Instantly share code, notes, and snippets.

@xenomuta
Last active January 4, 2024 13:53
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 9 You must be signed in to fork a gist
  • Save xenomuta/4450368 to your computer and use it in GitHub Desktop.
Save xenomuta/4450368 to your computer and use it in GitHub Desktop.
httpd.asm: Arguably the world smallest web server. ( for GNU/Linux i386. Compile with nasm )
section .text
global _start
_start:
xor eax, eax
xor ebx, ebx
xor esi, esi
jmp _socket
_socket_call:
mov al, 0x66
inc byte bl
mov ecx, esp
int 0x80
jmp esi
_socket:
push byte 6
push byte 1
push byte 2
mov esi, _bind
jmp _socket_call
_bind:
mov edi, eax
xor edx, edx
push dword edx
push word 0x6022
push word bx
mov ecx, esp
push byte 0x10
push ecx
push edi
mov esi, _listen
jmp _socket_call
_listen:
inc bl
push byte 0x01
push edi
mov esi, _accept
jmp _socket_call
_accept:
push edx
push edx
push edi
mov esi, _fork
jmp _socket_call
_fork:
mov esi, eax
mov al, 0x02
int 0x80
test eax, eax
jz _write
xor eax, eax
xor ebx, ebx
mov bl, 0x02
jmp _listen
_write:
mov ebx, esi
push edx
push dword 0x0a0d3e31
push dword 0x682f3c21
push dword 0x64334e77
push dword 0x503e3168
push dword 0x3c0a0d0a
push dword 0x0d6c6d74
push dword 0x682f7478
push dword 0x6574203a
push dword 0x65707954
push dword 0x2d746e65
push dword 0x746e6f43
push dword 0x0a4b4f20
push dword 0x30303220
push dword 0x302e312f
push dword 0x50545448
mov al, 0x04
mov ecx, esp
mov dl, 64
int 0x80
_close:
mov al, 6
mov ebx, esi
int 0x80
mov al, 6
mov ebx, edi
int 0x80
_exit:
mov eax, 0x01
xor ebx, ebx
int 0x80
@IanSeyler
Copy link

Would you be willing to add comments to this? Especially for the values you are pushing to the stack?

@DGivney
Copy link

DGivney commented Jul 3, 2013

I've got a fork with some comments here:
https://gist.github.com/DGivney/5917914

@xenomuta
Copy link
Author

xenomuta commented Aug 5, 2013

Thanks @DGivney a lot for your interest in this code and taking your time to comment the code. I'm sure you noticed there is no NULL bytes in the code, so It can be overflowed on to a buffer.

@pcordes
Copy link

pcordes commented May 8, 2021

mov eax, 0x01 contains three 0 bytes, and could be done more efficiently (in 32-bit mode) with xor eax,eax / inc eax. Or if you can assume close didn't return an error, mov al,1

Also, inc bl is 2 bytes; inc ebx is 1 byte.

See https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code

It would probably also be worthwhile to include the string for write as literal data (maybe at the end with the jmp / call / pop trick to get its address into a register), instead of spending 1 byte for every 4 on a push opcode.

Also, note that push byte 0x01 is still a dword push (because only word and the default operand-size are available for push). And NASM would pick a 1-byte immediate by default, so IMO it's more misleading than helpful especially when other places in the source use push word ... which looks similar but is very different.
If you want to enforce the size of the immediate, use push strict byte 1. The strict keyword exists to force encodings.

@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

(Same behaviour as https://gist.github.com/DGivney/5917914 which was forked from this.)

@Potherca
Copy link

Potherca commented May 9, 2021

nash-f is another small HTTP server, coming down to 194B without content (or 229 bytes with the obligatory <h1>Hello!</h1>)

@xenomuta
Copy link
Author

xenomuta commented May 20, 2021

mov eax, 0x01 contains three 0 bytes, and could be done more efficiently (in 32-bit mode) with xor eax,eax / inc eax. Or if you can assume close didn't return an error, mov al,1

@pcordes xor/inc is definitely smaller, but the fact is that code's purpose was to be used as a null-free shellcode (as used by Cymothoa Shellcode Injector featured on Phrack issue #64 Crossbower's article on parasite code for linux. So, by the time you land on the code you don't want to take chances the high EAX (AH) might not be zeroed.

Also, inc bl is 2 bytes; inc ebx is 1 byte.

See https://codegolf.stackexchange.com/questions/132981/tips-for-golfing-in-x86-x64-machine-code

I'm pretty sure I could optimize the hell out of this code and make much smaller (and ugglier), but It would not be so useful today. Maybe porting this to x64 or ARM would be more beneficial.

It would probably also be worthwhile to include the string for write as literal data (maybe at the end with the jmp / call / pop trick to get its address into a register), instead of spending 1 byte for every 4 on a push opcode.

Also, note that push byte 0x01 is still a dword push (because only word and the default operand-size are available for push). And NASM would pick a 1-byte immediate by default, so IMO it's more misleading than helpful especially when other places in the source use push word ... which looks similar but is very different.
If you want to enforce the size of the immediate, use push strict byte 1. The strict keyword exists to force encodings.

About this... pretty sure I got missed some gotchas translating this from ATT&T/Motorola syntax into NASM's Intel-lish syntax. To clear any doubts, better check my original raw shellcode and assembly code as posted on milw0rm.com and later exploit-db circa 2009.

Maybe that is also the reason bind() ends up with an unclean structure from stack with wrong sock type.

@xenomuta
Copy link
Author

nash-f is an even smaller HTTP server, coming down to 194B without content (or 229 bytes with the obligatory <h1>Hello!</h1>)

No it's not:

@xenomuta
Copy link
Author

xenomuta commented May 20, 2021

Also, the purpose of the original httpd.asm was to avoid null bytes.
nash-f's server code is full of 00s, which then trims strings and kills exploitability of some vulnerable string functions.

@Potherca
Copy link

@xenomuta My apologies. I'm still somewhat new to all of this. I've edited my comment.

Also, thank you for the clarifications, very educational!

@xenomuta
Copy link
Author

@xenomuta My apologies. I'm still somewhat new to all of this. I've edited my comment.

Also, thank you for the clarifications, very educational!

Oh that's ok, no need to apologize. You're welcome.
Keep the good stuff going.

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