Problem
This problem comes from pwn.college - Building a Web Server ⤴.
Once your socket is listening, it’s time to actively accept incoming connections. In this challenge, you will use the accept ⤴ syscall, which waits for a client to connect. When a connection is established, it returns a new socket file descriptor dedicated to communication with that client and fills in a provided address structure (such as a
struct sockaddr_in) with the client’s details. This process is a critical step in transforming your server from a passive listener into an active communicator.
Solution
Goal
Running /challenge/run server (using the program compiled in listen) prints to the console
===== Expected: Parent Process =====
[ ] execve(<execve_args>) = 0
[ ] socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
[ ] bind(3, {sa_family=AF_INET, sin_port=htons(<bind_port>), sin_addr=inet_addr("<bind_address>")}, 16) = 0
- Bind to port 80
- Bind to address 0.0.0.0
[ ] listen(3, 0) = 0
[ ] accept(3, NULL, NULL) = 4
[ ] exit(0) = ?
Alright! We can see that we need to pass in sockfd and NULL to the two other parameters. It looks like we get back another file descriptor, this one being 4.
But let’s verify and check out the accept function.
Accept Function
The accept function has the following function definition
int accept(int sockfd,
struct sockaddr *_Nullable restrict addr,
socklen_t *_Nullable restrict addrlen);
I’ve written a “decent” amount of C++, but clearly not enough, because this looks like black magic to me.
But given the fact there’s _Nullable in there and we’re passing NULL to the last two parameters, I’m going to have to risk my neck and
say…the parameters accept NULL.
Either way,
sockfdis the same file descriptor we’ve gotten from calling thesocketfunction. We’re accepting on our socket.sockaddr(sockaddr_infor us) is what we’ve seen in the bind post, but this time for the incoming connection. In our case, it’s set toNULLthough.socklen_tis the size ofsockaddr(sockaddr_infor us) for the incoming connection. BecausesockaddrisNULLfor this challenge, this isNULLtoo.
The accept function returns a file descriptor for the accepted socket. See accept man page ⤴ for more details.
Code
.intel_syntax noprefix
.global _start
.section .data
sock_in: # struct sockaddr_in
.word 2 # AF_INET (IPv4)
.word 0x5000 # Port 80 in hex
.long 0 # IP address of 0.0.0.0
.byte 0 # Padding
.section .text
_start:
Socket:
mov rdi, 2 # AF_INET = 2, for IPv4
mov rsi, 1 # SOCK_STREAM = 1, for TCP
xor rdx, rdx # 0 for protocol
mov rax, 41 # socket syscall number: 41
syscall
mov rbx, rax # Save sockfd
Bind:
mov rdi, rax # Send file descriptor to sockfd
lea rsi, [sock_in] # Pointer to struct sockaddr
mov rdx, 16 # 16 bytes expected
mov rax, 49 # bind syscall number: 49
syscall
Listen:
mov rdi, rbx # Send file descriptor to sockfd
mov rsi, 0 # Expected backlog/queue length
mov rax, 50 # listen syscall number: 50
syscall
Accept:
mov rdi, rbx # Send file descriptor to sockfd
mov rsi, 0x0 # Null
mov rdx, 0x0 # Null
mov rax, 43 # accept syscall number: 43
syscall
Exit:
mov rdi, 0
mov rax, 60 # exit syscall number: 60
syscall