Build-A-Web-Server
我想在笔记中记录方法和思考。所以内容可能会变得迷惑~
SOURCE
WRITEUP
TOOLS-系统调用表
- socket
- bind
- listen
- accept
套接字系统调用-Socket
1 |
|
socket()创建一个通信端点,返回指向这个端点的文件描述符。
- domain:指定一个通信域;选择协议簇
- type:指定通信语义
- protocol:指定套接字使用的协议
为了建立Socket system call,首先需要找到相关参数的值(AF_INET)(SOCK_STREAM)
Great question! In C programming, these constants are defined in header files like <sys/socket.h>. However, when working in assembly, you need to use their numeric values directly. These values are standardized across systems:
AF_INET is usually defined as 2.
SOCK_STREAM is usually defined as 1.
You can find these definitions in the C header files on your system. For example, you can look at /usr/include/x86_64-linux-gnu/bits/socket.h or similar files depending on your system architecture.
If you’re curious, you can use the grep command to search for these definitions in your system’s include directories.
1 |
|
更便捷的办法可以是用python的pwn.constants速查
1 |
|
1 |
|
之后就是查找系统调用表。
注意:存入寄存器中的系统调用号必须是十六进制
系统调用-Bind
Q: 既然Socket是通信的端点,那为什么有了Socket还要Bind?
A: 套接字需要被分配到网络实体,这包括具体的IP地址和端口,还要接受收到的数据(这就意味着要与内存交互)
因此,Bind要求有3个参数:
1 |
|
- sockfd:指向套接字的文件描述符
- *addr:指向分配到套接字的地址(需要一个结构体)
- addrlen:指定由addr指向的地址结构的大小,以字节为单位。
1 |
|
更具体地,
{sa_family=AF_INET,
sin_port=htons(
sin_addr=inet_addr(“
写成汇编代码:
1 |
|
在这里可以看到,Port80,是按照大端存储的,这在网络通信中常见,而小端存储为内存中的操作带来了不少便捷
1 |
|
我还记得在《CSAPP》中,老师说过想清楚lea指令是关键。
之前只是知道是这样,但这次看到了实际使用=>指向地址的指针变量赋值用lea
lea指令专门用于计算地址,而mov指令用于加载数据值,两者的用途不同。
例如,lea rsi, [rip+sockaddr]会计算rip+sockaddr的地址,并将这个地址加载到rsi寄存器中。
系统调用-Listen
1 |
|
- sockfd:socket的文件描述符
- backlog:指定sockfd的待处理连接队列长度的最大值
如何保存rax=3
rax中存储的套接字描述符会被Bind Syscall的返回值覆盖
=>使用栈去保存
push rax
pop rdi
之后将backlog设置为0,这里不需要队列
mov rdi,0
mov rax,0x32
syscall
系统调用-Accept
用accept系统调用,它会等待客户端的连接。当建立连接时,它会返回一个新的套接字文件描述符,专门用于与该客户端通信,并且会用客户端的详细信息填充一个提供的地址结构(例如struct sockaddr_in)。这一过程是将你的服务器从被动监听者转变为积极通信者的关键步骤。
1 |
|
- sockfd
- addr:指向sockaddr结构体的指针
- addrlen:addr指向的结构体的长度
1 |
|
到现在,已经是一个可以建立连接的服务器了了。
静态响应数据-系统调用read&write
如何为尝试进行连接的客户端发送一个静态的HTTP响应?
HTTP/1.0 200 OK\r\n\r\n
This exercise is important because it teaches you how to format and deliver data over the network.
动态响应数据-系统调用open
这次,服务器程序需要对HTTP GET请求返回动态的数据。
- read 来自HTTP请求的客户套接字
- 解析请求内容
- open解析到的请求的文件
- read文件
- write 将文件内容write回套接字
- close关闭文件
- exit
(这次可以从汇编层面看到HTTP请求内容的解析过程)
解析请求内容
承接上一关在这里,accept系统调用返回的套接字文件操作符在rax中
从trace记录中看到这次读到的内容包含了文件名——一个随机字符串。
所以在write之前要先想办法解析请求
解析什么?
1 |
|
- *pathname:指向需要被打开的文件的指针
- flags:必须包含文件被打开的模式(O_RDONLY,O_WRONLY,or O_RDWR etc)
- mode:如果文件是新创建的,指定文件权限
假设这里需要先确认下grep命令行工具的用法:
man grep
搜索-r
添加-r选项,递归地搜索整个目录下的文件内容
1 |
|
在 Unix 和 Linux 系统中,fcntl.h 文件中定义的文件打开标志(如 O_RDONLY、O_WRONLY 等)通常使用八进制表示。这是因为八进制表示在位操作中更加直观。
可以查到用数字0设置O_RDONLY的值
=> mov rsi,0
以上过程由LLM完成会很方便,但这是先前的方法。
于是这里确定,要写汇编代码解析出文件名=>
- 一个指向文件名字符串开头的指针(寄存器)
- 一个存放着文件名长度的寄存器
完整的GET请求:
前一条read将内容读到了栈上(rsp)
1 |
|
这里从r10开始使用更多的寄存器。
1 |
|
1 |
|