linux - 为什么这个程序集 HTTP 服务器不起作用?
问题描述
我遇到了可以说是 docker 中最小的 HTTP 服务器(用汇编编写的),我很想看到它的实际应用!
我认为他们从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
我可以组装和链接代码而不会出现任何错误。
但是当我运行它时,似乎什么也没有发生。
为了在浏览器中查看程序集 HTTP 服务器的输出,我需要做什么?
解决方案
似乎对我来说几乎没有什么工作,尽管玛格丽特布鲁姆注意到它有问题。(它在随机端口上侦听,因为它进行了错误的bind
系统调用。大概传递了错误的数字sa_family
)
nasm -felf32
在使用/构建 / 链接后ld -melf_i386
,我在 strace 下运行它以查看它做了什么。
$ 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
保存字节的mov al, callnum
技巧假设 EAX 的高字节仍然为 0。如果它们不是(来自-errno
返回的全一),那么接下来的几个系统调用的调用号无效。但最终它确实listen(3,1)
和accept
,所以它在某处听。我用 找到了它的 PID ps
,然后用来lsof
找出它正在侦听的端口:
$ 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)
使用 (netcat) 连接到该端口nc
会使其转储其固定字符串有效负载并保持连接打开:
$ nc localhost 36047
HTTP/1.0 200 OK
Content-Type: text/html
<h1>PwN3d!</h1>
CONTROL-C
$
将 Chromium 指向 http://localhost:36047/ 也加载了一个页面,但由于连接保持打开状态,它仍在旋转等待更多数据。
几次连接后的 strace 输出已增长到
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
顺便说一句,另一个最小的 HTTPD 是https://github.com/Francesco149/nolibc-httpd。有一个 C 版本(它为系统调用调用手写的 asm 包装器,而不仅仅是使用内联 asm)。请参阅这个没有 libc 的 C 程序如何工作?关于它。
正如我在那里提到的,通过切换到并使用内联 asm 进行系统调用,我将 C 版本从 1.2k 降低到 992 字节,clang -Oz
所以整个事情可能是一个叶函数。(不需要保存/恢复任何东西的寄存器。) https://github.com/pcordes/nolibc-httpd/commit/ad3a80b89b98379304f1525339fa71700bf1a15d
推荐阅读
- javascript - 如何让浏览器知道用户已经查看了特定页面 - Ioinic
- javascript - (Ajax)在另一个 ajax 请求(而不是它内部)的代码成功之后执行一个 ajax 请求
- mysql - 如何仅为查询更改数据单元格的值。我似乎无法翻译此查询
- php - 带有锚标签的 div 在另一个 div 中加载 php 页面
- r - 将项目添加到现有图例
- r - 准备要在 XGBoost 中使用的数据时出错
- rust - 为什么协程有未来?
- odoo - 挣扎于本地插件odoo
- asp.net-mvc - 如何在 asp.net mvc 中将静态 html 转换为剃刀视图?
- ios - AddressSanitizer 报告断点命中:动态堆栈缓冲区溢出