Building a Web Server
———–ASU CSE 365: Introduction to Cybersecurity
Building a Web Server: Introduction
①Computation
②accessing hardware
instructions should first talk to the OS with the systemcall( syscall like mov rax, 42; syscall
) and then OS will operate with the hardware in Kernel
Building a Web Server: Linux Processes
①syscall
read(int fd, void *
buf, size_t count) attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
write(int fd, void *
buf, size_t count) writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.
open(char *
pathname, int flags, mode_t mode) opens the file specified by pathname. If the specified file doesn’t exist, it may optionally be created(if O_CREAT is specified in flags), the return value of it is a file descriptor that used in subsequent system calls(read(2),write(2),lseek(2),fcntl(2)) to refer to the open file.
②Linux process
struct task_struct *current
this blob of data is living in kernel memory
In the end, the file descriptor is 3 for ‘/flag’ and the RAX is set to 3 as the result of open() syscall
. It saved the data into the kernel memory.
③system calls
for more system call can see this
Building a Web Server: Network System Calls
①socket
1
2
|
int socket(in domain, int type, int protocol)
//socket() creates an endpoint for communication and returns a file descriptor that refers to that endpoint
|
②bind
1
2
|
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen)
//when a socket(2) is created with socket, it exits in a name space but has no address assigned to it.bind() assigns the address specified by addr to the socket referred to by the file descriptor sockfd.
|
③struct sockaddr_in
1
2
3
4
5
6
7
8
9
10
11
|
struct sockaddr{
uint16_t sa_family;
uint8_t sa_data[14];
};
struct sockaddr_in{
uint16_t sin_family;
uint16_t sin_port;
uint32_t sin_addr;
uint8_t __pad[8];
}
|
AF_INET is 2; htons() function used to convert the number from little endian to big endian for networking work on big ending integers; ipv4 address of 4 bytes is also big endian, the inet_addr() function converts an unsigned integer IP from a host end-order to a network end-order
④listen
1
2
|
int listen(int sockfd, int backlog)
//listen() marks the socket referred to by sockfd as a passive socket that will be used to accept incoming connection requests using accept(2)
|
⑤accept
1
2
|
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
//it's used with connection-based socket types(SOCK_STREAM,SOCK_SEQPACKET).It extracts the first connection request on the queue of pending connections for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket.
|
⑥accept TCP/IP network connections
Building a Web Server: HTTP
Building a Web Server: Multiprocessing
if there’re lots of http requests, we should another system call.
①fork()
creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.
On success, the PID of the child process is returned to the parent, and 0 is returned in the child.
babyserver
In this series of challenges, we should writing assembly to interact with the environment, and ultimately build a web server
Usage: /challenge/babyserver <path_to_web_server>
level1: exit a program
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
60 |
exit |
man/ cs/ |
3C |
int error_code |
- |
- |
- |
- |
- |
exit (0) : run the program normally and exit the program
exit (1) : abnormal operation causes the program to exit
1
2
3
4
5
6
7
8
9
10
11
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 0
mov rax, 60 # SYS_exit
syscall
.section .data
|
then use the as
(compiler) to generates an object file from the assembly file and use the ld
(linker) to link the object file to the library as an executable or library file
1
|
as -o server.o server.s && ld -o server server.o
|
strace : tracks system calls and received signals as a process executes
1
2
3
4
|
hacker@babyserver_level1:~/module4/1$ strace ./server
execve("./server", ["./server"], 0x7ffc21a0ed30 /* 29 vars */) = 0
exit(0) = ?
+++ exited with 0 +++
|
get flag:
1
2
3
4
5
6
7
8
9
10
11
12
|
/challenge/babyserver ./server
===== Expected: Parent Process =====
[ ] execve(<execve_args>) = 0
[ ] exit(0) = ?
===== Trace: Parent Process =====
[✓] execve("/proc/self/fd/3", ["/proc/self/fd/3"], 0x7f9ad0f54a80 /* 0 vars */) = 0
[✓] exit(0) = ?
[?] +++ exited with 0 +++
===== Result =====
[✓] Success
|
level2: create a socket
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
41 |
socket |
man/ cs/ |
29 |
int |
int |
int |
- |
- |
- |
1
2
3
4
|
===== Expected: Parent Process =====
[ ] execve(<execve_args>) = 0
[ ] socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
[ ] exit(0) = ?
|
int socket(int domain, int type, int protocol)
need: socket(AF_INET, SOCK_STREAM, IPPROTO_IP)
-
First, we can write it in a c program and look at the errors so that we can put the header files(.h) to c program, seeing each argument
-
then, use the find /usr/include | grep xxx.h
and then cat
it to find the define
things. Strace the c program can find out some syscalls
-
finally, use the objdump -D -M intel xx.out
to see the assembly program
(such as the IPPROTO_IP in netinet/in.h is 0)
1
2
3
4
5
6
7
|
#some useful tool
grep -r 'IPPROTO_IP' /usr/include | grep define
grep -r 'define AF_INET' /usr/include
#define AF_INET PF_INET
grep -r 'define PF_INET' /usr/include
#define PF_INET 2
----------------------------------
|
1
2
3
|
import pwn
pwn.constants.AF_INET #get the constant of AF_INET
print(int(pwn.constants.AF_INET))
|
so we can get the solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 2 ;domain:AF_INET
mov rsi, 1 ;type:SOCK_STREAM
mov rdx, 0 ;protocol:IPPROTO_IP
mov rax, 41
syscall ;socket
mov rdi, 0
mov rax, 60
syscall
.section .data
|
level3: bind an address to a socket
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
49 |
bind |
man/ cs/ |
31 |
struct sockaddr* |
int |
- |
- |
- |
- |
*int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen)
func: Bind a socket file that specifies the communication protocol to the IP and port
1
2
3
4
5
6
7
|
===== 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
[ ] exit(0) = ?
|
we can use the pwntools to set the little ending data(port):
1
2
|
import pwn
pwn.p16(80,endian="little").hex() #port
|
the solution:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 2
mov rsi, 1
mov rdx, 0
mov rax, 41 #socket
syscall
mov rdi, 3
lea rsi, [rip+sockaddr] #lea(load effective address)Take the source's offset address
mov rdx, 16
mov rax, 49 #bind
syscall
mov rdi, 0
mov rax, 60 #exit
syscall
.section .data
sockaddr:
.2byte 2 #AF_INET
.2byte 0x5000 #port:80--->little ending 50 00
.4byte 0 #addr:0.0.0.0
.8byte 0
|
we first find the file /usr/include/netinet/in.h
and find out the sockaddr_in struct
1
2
3
4
5
6
7
8
9
10
11
12
|
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port; /* Port number. */
struct in_addr sin_addr; /* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr)
- __SOCKADDR_COMMON_SIZE
- sizeof (in_port_t)
- sizeof (struct in_addr)];
};
|
and then we step by step find out the size of every type of data.
1
2
3
4
5
6
7
8
9
10
11
|
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int)) //2bytes
/* Type to represent a port. */
typedef uint16_t in_port_t; //2bytes
/* Internet address. */
typedef uint32_t in_addr_t; //4bytes
struct in_addr
{
in_addr_t s_addr;
};
|
level4: listen on a socket
1
2
3
4
5
6
7
8
|
===== 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
[ ] exit(0) = ?
|
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
50 |
listen |
man/ cs/ |
32 |
int |
int |
- |
- |
- |
- |
int listen(int sockfd, int backlog)
1
2
3
4
|
mov rdi, 3
mov rsi, 0
mov rax, 50
syscall
|
level4: accept a connection
1
2
3
4
5
6
7
8
9
|
===== 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) = ?
|
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
43 |
accept |
man/ cs/ |
2B |
int |
struct sockaddr * |
int * |
- |
- |
- |
*int accept(int sockfd, struct sockaddr *
addr, socklen_t addrlen)
I write a c program about the accept and objdump
it, then I find that the NULL
should only set to the 0x0
to the register. It works.
1
2
3
4
5
|
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
|
level6: respond to an http request
1
2
3
4
5
6
7
8
9
10
11
12
|
===== 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
[ ] read(4, <read_request>, <read_request_count>) = <read_request_result>
[ ] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[ ] close(4) = 0
[ ] exit(0) = ?
|
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
0 |
read |
man/ cs/ |
0 |
unsigned int fd |
char *buf |
size_t count |
- |
- |
- |
1 |
write |
man/ cs/ |
1 |
unsigned int fd |
const char *buf |
size_t count |
- |
- |
- |
3 |
close |
man/ cs/ |
3 |
unsigned int fd |
- |
- |
- |
- |
- |
ssize_t read(int fd, void *
buf, size_t count);
ssize_t write (int filedes, void *
buf, size_t nbytes);
int close(int fd);
Only show the newly added code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
mov rdi, 4
mov rsi, rsp
mov rdx, 256
mov rax, 0 #read
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
mov rdi, 4
mov rax, 3 #close
syscall
.section .data
msg:
.ascii "HTTP/1.0 200 OK\r\n\r\n"
|
level7: respond to a GET request for the contents of a specified file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
===== 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
[ ] read(4, <read_request>, <read_request_count>) = <read_request_result>
[ ] open("<open_path>", O_RDONLY) = 5
[ ] read(5, <read_file>, <read_file_count>) = <read_file_result>
[ ] close(5) = 0
[ ] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[ ] write(4, <write_file>, <write_file_count> = <write_file_result>
[ ] close(4) = 0
[ ] exit(0) = ?
|
find out the definition of the O_RDONLY
:
1
2
|
hacker@babyserver_level7:~/module4/7$ grep -r "O_RDONLY" /usr/include/
/usr/include/x86_64-linux-gnu/bits/fcntl-linux.h:#define O_RDONLY 00
|
There’re something to mention: the <open_path>
should be the file that accept from the client and it is automatic, so we can’t put a casual path to the open(). The second thing is that we must get the right content of the file so we should save the count after reading from the file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 2
mov rsi, 1
mov rdx, 0
mov rax, 41 #socket
syscall
mov rdi, 3
lea rsi, [rip+sockaddr]
mov rdx, 16
mov rax, 49 #bind
syscall
mov rdi, 3
mov rsi, 0
mov rax, 50 #listen
syscall
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
mov rdi, 4
mov rsi, rsp
mov rdx, 0x1000
mov rax, 0 #read
syscall
#GET /temp/xxxx HTTP/1.0
# ^
# r10 \0
#got the /temp/xxxx<----automatic
loop:
mov al, [rsp]
cmp al, ' '
je next
inc rsp
jmp loop
next:
inc rsp
mov r10, rsp
loop2:
mov al, [rsp]
cmp al, ' '
je next2
inc rsp
jmp loop2
next2:
mov byte ptr [rsp], 0
mov rdi, r10
#lea rdi, [rip+filepath]
mov rsi, 0
mov rax, 2 #open
syscall
mov rdi, 5
mov rsi, rsp
mov rdx, 0x1000
mov rax, 0 #read
syscall
mov r12, rax #tried the r11 will have errors so change to the r12
#save the size returning from the read func
mov rdi, 5
mov rax, 3
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
mov rdi, 4
mov rsi, rsp
mov rdx, r12
mov rax, 1
syscall
mov rdi, 4
mov rax, 3 #close
syscall
mov rdi, 0
mov rax, 60 #exit
syscall
.section .data
sockaddr:
.2byte 2
.2byte 0x5000
.4byte 0
.8byte 0
msg:
.ascii "HTTP/1.0 200 OK\r\n\r\n"
filepath:
.ascii "/tmp/" #didn't use
|
level8: accept multiple requests
Compare with the level7, delete the exit
and add the accept
again
level9: concurrently accept multiple requests
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
===== 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
[ ] fork() = <fork_result>
[ ] close(4) = 0
[ ] accept(3, NULL, NULL) = ?
===== Expected: Child Process =====
[ ] close(3) = 0
[ ] read(4, <read_request>, <read_request_count>) = <read_request_result>
[ ] open("<open_path>", O_RDONLY) = 3
[ ] read(3, <read_file>, <read_file_count>) = <read_file_result>
[ ] close(3) = 0
[ ] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[ ] write(4, <write_file>, <write_file_count> = <write_file_result>
[ ] exit(0) = ?
|
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
57 |
fork |
man/ cs/ |
39 |
- |
- |
- |
- |
- |
- |
pid_t fork(void);
For the parent process it will get the ID or return Number of the new created child processes and the child process get the 0
ID so we need to use conditional jumps to distinguish between them.
1
2
3
4
5
6
7
8
|
mov rax, 57 #fork
syscall
mov r8, rax
cmp r8, 7
je parent
cmp r8, 0
je child
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
===== Trace: Parent Process =====
[✓] execve("/proc/self/fd/3", ["/proc/self/fd/3"], 0x7f6c58e3aaa0 /* 0 vars */) = 0
[✓] socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
[✓] bind(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
[✓] listen(3, 0) = 0
[✓] accept(3, NULL, NULL) = 4
[✓] fork() = 7
[✓] close(4) = 0
[✓] accept(3, NULL, NULL) = ?
[?] +++ killed by SIGKILL +++
===== Trace: Child Process =====
[✓] close(3) = 0
[✓] read(4, "GET /tmp/tmpq9rbaedq HTTP/1.1\r\nHost: localhost\r\nUser-Agent: python-requests/2.28.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\n\r\n", 4096) = 155
[✓] open("/tmp/tmpq9rbaedq", O_RDONLY) = 3
[✓] read(3, "ppNPPIPL1y3rs5lyygp2Odm7P8TiMzFLKR1UyCySEFsG4uIHLByLEXMHsblOhc3kJOtwK30IGN6RUOt1JnTTVTiDXI4Db7ormkmN3up7rwCcLqhJk74J4rr4cYRbWta7RlJNciFqocdfIuTNMe4mIkg3XtItfDI2LZV5KN93", 4096) = 168
[✓] close(3) = 0
[✓] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[✓] write(4, "ppNPPIPL1y3rs5lyygp2Odm7P8TiMzFLKR1UyCySEFsG4uIHLByLEXMHsblOhc3kJOtwK30IGN6RUOt1JnTTVTiDXI4Db7ormkmN3up7rwCcLqhJk74J4rr4cYRbWta7RlJNciFqocdfIuTNMe4mIkg3XtItfDI2LZV5KN93", 168) = 168
[✓] exit(0) = ?
[?] +++ exited with 0 +++
===== Result =====
[✓] Success
|
level10: respond to a POST request with a specified file and update its contents
1
2
3
4
5
6
7
8
|
===== Expected: Child Process =====
[ ] close(3) = 0
[ ] read(4, <read_request>, <read_request_count>) = <read_request_result>
[ ] open("<open_path>", O_WRONLY|O_CREAT, 0777) = 3
[ ] write(3, <write_file>, <write_file_count> = <write_file_result>
[ ] close(3) = 0
[ ] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[ ] exit(0) = ?
|
NR |
SYSCALL NAME |
references |
RAX |
ARG0(rdi) |
ARG1(rsi) |
ARG2(rdx) |
ARG3(r10) |
ARG4(r8) |
ARG5(r9) |
2 |
open |
man/ cs/ |
2 |
const char *filename |
int flags |
umode_t mode |
- |
- |
- |
int open(const char *
pathname, int flags, mode_t mode);
1
2
|
/usr/include/x86_64-linux-gnu/bits/fcntl-linux.h:# define O_CREAT 0100 /* Not fcntl. */
/usr/include/x86_64-linux-gnu/bits/fcntl-linux.h:#define O_WRONLY 01
|
the 3rd argument of 0777
is OCT so we need to change it to the HEX 1FF
.
POST
:
and we should do in level10 is :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 2
mov rsi, 1
mov rdx, 0
mov rax, 41 #socket
syscall
mov rdi, 3
lea rsi, [rip+sockaddr]
mov rdx, 16
mov rax, 49 #bind
syscall
mov rdi, 3
mov rsi, 0
mov rax, 50 #listen
syscall
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
mov rax, 57 #fork
syscall
mov r8, rax
cmp r8, 7
je parent
cmp r8, 0
je child
parent:
mov rdi, 4
mov rax, 3 #close
syscall
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
child:
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
mov rsi, rsp
mov rdx, 0x1000
mov rax, 0 #read
syscall
mov r14, rax
sub r14, 177 #calculate the size
loop:
mov al, [rsp]
cmp al, ' '
je next
inc rsp
jmp loop
next:
inc rsp
mov r10, rsp
loop2:
mov al, [rsp]
cmp al, ' '
je next2
inc rsp
jmp loop2
next2:
mov byte ptr [rsp], 0
mov rdi, r10
mov rsi, 0100|01 #O_WRONLY|O_CREAT
mov rdx, 0x1ff
mov rax, 2 #open
syscall
loop3:
mov al, [rsp] #must in the loop
cmp al, 'H'
je next3
inc rsp
jmp loop3
next3:
inc rsp
loop4:
mov al, [rsp]
cmp al, 'L'
je next4
inc rsp
jmp loop4
next4:
add rsp, 15 #calculate the size
mov r8, rsp
mov rdi, 3
mov rsi, r8
mov rdx, r14
mov rax, 1 #write
syscall
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
mov rdi, 0
mov rax, 60 #exit
syscall
.section .data
sockaddr:
.2byte 2
.2byte 0x5000
.4byte 0
.8byte 0
msg:
.ascii "HTTP/1.0 200 OK\r\n\r\n"
|
emmmm, I feel like I’m using a little trick…..Sometimes it is wrong according to the counts.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
===== Trace: Parent Process =====
[✓] execve("/proc/self/fd/3", ["/proc/self/fd/3"], 0x7fbd50e91aa0 /* 0 vars */) = 0
[✓] socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
[✓] bind(3, {sa_family=AF_INET, sin_port=htons(80), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
[✓] listen(3, 0) = 0
[✓] accept(3, NULL, NULL) = 4
[✓] fork() = 7
[✓] close(4) = 0
[✓] accept(3, NULL, NULL) = ?
[?] +++ killed by SIGKILL +++
===== Trace: Child Process =====
[✓] close(3) = 0
[✓] read(4, "POST /tmp/tmpvx_ye4_7 HTTP/1.1\r\nHost: localhost\r\nUser-Agent: python-requests/2.28.1\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nConnection: keep-alive\r\nContent-Length: 212\r\n\r\nz7sUJ0m2d9IkQeghmuUTlpCXBPRbQGKpmILxptSYl9aJhMwGgC7PQpSTMR8qTr2q8uAfzsro0y9fSbM8anzW7BsK10vsfaOThRBw4B6R5y38IOedthK0iOoX5ymIWbuBnOBPgsCNh5C45wcMDNR9LFEfUGTz7bvFCHRQlFg4LXZNazadwka8pN7A0QsBhgmRHQ4VOg5iPqBiGeMpX2gD", 4096) = 389
[✓] open("/tmp/tmpvx_ye4_7", O_WRONLY|O_CREAT, 0777) = 3
[✓] write(3, "z7sUJ0m2d9IkQeghmuUTlpCXBPRbQGKpmILxptSYl9aJhMwGgC7PQpSTMR8qTr2q8uAfzsro0y9fSbM8anzW7BsK10vsfaOThRBw4B6R5y38IOedthK0iOoX5ymIWbuBnOBPgsCNh5C45wcMDNR9LFEfUGTz7bvFCHRQlFg4LXZNazadwka8pN7A0QsBhgmRHQ4VOg5iPqBiGeMpX2gD", 212) = 212
[✓] close(3) = 0
[✓] write(4, "HTTP/1.0 200 OK\r\n\r\n", 19) = 19
[✓] exit(0) = ?
[?] +++ exited with 0 +++
===== Result =====
[✓] Success
|
OK, fine…It turns out that we cannot be opportunistic. I modified the code to make it possible to judge three-digit numbers as well as two-digit numbers so that it won’t have errors several times. And this is good for editing the level11.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 2
mov rsi, 1
mov rdx, 0
mov rax, 41 #socket
syscall
mov rdi, 3
lea rsi, [rip+sockaddr]
mov rdx, 16
mov rax, 49 #bind
syscall
mov rdi, 3
mov rsi, 0
mov rax, 50 #listen
syscall
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
mov rax, 57 #fork
syscall
mov r8, rax
cmp r8, 7
je parent
cmp r8, 0
je child
parent:
mov rdi, 4
mov rax, 3 #close
syscall
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
child:
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
mov rsi, rsp
mov rdx, 0x1000
mov rax, 0 #read
syscall
xor r12, r12
mov r12, rax
loop:
mov al, [rsp]
cmp al, ' '
je next
inc rsp
jmp loop
next:
inc rsp
mov r10, rsp
loop2:
mov al, [rsp]
cmp al, ' '
je next2
inc rsp
jmp loop2
next2:
mov byte ptr [rsp], 0
mov rdi, r10
mov rsi, 0100|01
mov rdx, 0x1ff
mov rax, 2 #open
syscall
loop3:
mov al, [rsp]
cmp al, 'H'
je next3
inc rsp
jmp loop3
next3:
inc rsp
loop4:
mov al, [rsp]
cmp al, 'L'
je next4
inc rsp
jmp loop4
next4:
loop5:
mov al, [rsp]
cmp al, ' '
je next5
inc rsp
jmp loop5
next5:
xor r15, r15 #at the beginning it is in loop6 so r15 is always 0...
mov r15, 0
loop6:
mov al, [rsp]
cmp al, '\r'
je next6
inc rsp
add r15, 1
jmp loop6
next6:
add rsp, 4
mov r8, rsp
cmp r15, 3 #two is 3 and three is 4!!
jne three
two:#two-digit
xor rdx, rdx
sub r12, 176
mov rdi, 3
mov rsi, r8
mov rdx, r12
mov rax, 1 #write
syscall
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
mov rdi, 0
mov rax, 60 #exit
syscall
jmp done
three:#three-digit
xor rdx, rdx
sub r12, 177
mov rdi, 3
mov rsi, r8
mov rdx, r12
mov rax, 1 #write
syscall
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
done:
mov rdi, 0
mov rax, 60 #exit
syscall
.section .data
sockaddr:
.2byte 2
.2byte 0x5000
.4byte 0
.8byte 0
msg:
.ascii "HTTP/1.0 200 OK\r\n\r\n"
|
level11: respond to multiple concurrent GET and POST requests
(still trying…) (x)
(figure out!) (√)
The post was wrong at first. So I spent some time revising it. And then get is right. To understand the whole process and continue programming, it will be more effective with less effort.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
|
.global _start
.intel_syntax noprefix
.section .text
_start:
mov rdi, 2
mov rsi, 1
mov rdx, 0
mov rax, 41 #socket
syscall
mov rdi, 3
lea rsi, [rip+sockaddr]
mov rdx, 16
mov rax, 49 #bind
syscall
mov rdi, 3
mov rsi, 0
mov rax, 50 #listen
syscall
#xor r14, r14
#mov r14, 10
loopp: #this is important!! after socket,bind,listen should loop in accept and fork,continue forking to accept multiple requests of post and get
mov rdi, 3
mov rsi, 0x0
mov rdx, 0x0
mov rax, 43 #accept
syscall
mov rax, 57 #fork
syscall
mov r8, rax
cmp r8, 0
jne parent
cmp r8, 0
je child
child:
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
mov rsi, rsp
mov rdx, 0x1000
mov rax, 0 #read
syscall
mov cl, [rsp] #post or get
cmp cl, 'P'
je post
cmp cl, 'G'
je get
get:
getloop:
mov al, [rsp]
cmp al, ' '
je getnext
inc rsp
jmp getloop
getnext:
inc rsp
mov r10, rsp
getloop2:
mov al, [rsp]
cmp al, ' '
je getnext2
inc rsp
jmp getloop2
getnext2:
mov byte ptr [rsp], 0
mov rdi, r10
#lea rdi, [rip+filepath]
mov rsi, 0
mov rax, 2 #open
syscall
mov rdi, 3
mov rsi, rsp
mov rdx, 0x1000
mov rax, 0 #read
syscall
mov r12, rax
mov rdi, 3
mov rax, 3
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
mov rdi, 4
mov rsi, rsp
mov rdx, r12
mov rax, 1
syscall
mov rdi, 0
mov rax, 60 #exit
syscall
post:
xor r12, r12
mov r12, rax
loop:
mov al, [rsp]
cmp al, ' '
je next
inc rsp
jmp loop
next:
inc rsp
mov r10, rsp
loop2:
mov al, [rsp]
cmp al, ' '
je next2
inc rsp
jmp loop2
next2:
mov byte ptr [rsp], 0
mov rdi, r10
mov rsi, 0100|01
mov rdx, 0x1ff
mov rax, 2 #open
syscall
loop3:
mov al, [rsp]
cmp al, 'H'
je next3
inc rsp
jmp loop3
next3:
inc rsp
loop4:
mov al, [rsp]
cmp al, 'L'
je next4
inc rsp
jmp loop4
next4:
loop5:
mov al, [rsp]
cmp al, ' '
je next5
inc rsp
jmp loop5
next5:
xor r15, r15
mov r15, 0
loop6:
mov al, [rsp]
cmp al, '\r'
je next6
inc rsp
add r15, 1
jmp loop6
next6:
add rsp, 4
mov r8, rsp
cmp r15, 3
jne three
two:
xor rdx, rdx
sub r12, 176
mov rdi, 3
mov rsi, r8
mov rdx, r12
mov rax, 1 #write
syscall
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
mov rdi, 0
mov rax, 60 #exit
syscall
jmp done
three:
xor rdx, rdx
mov rdi, 3
mov rsi, r8
mov rdx, r12
sub rdx, 177
mov rax, 1 #write
syscall
mov rdi, 3
mov rax, 3 #close
syscall
mov rdi, 4
lea rsi, [rip+msg]
mov rdx, 19
mov rax, 1 #write
syscall
done:
mov rdi, 0
mov rax, 60 #exit
syscall
jmp donex
parent:
mov rdi, 4 #The 2nd error is that parent needn't accept but jump to loop accept
mov rax, 3 #close
syscall
#mov rdi, 3
#mov rsi, 0x0
#mov rdx, 0x0
#mov rax, 43 #accept
#syscall
#sub r14, 1
#cmp r14, 0
jmp loopp
donex:
.section .data
sockaddr:
.2byte 2
.2byte 0x5000
.4byte 0
.8byte 0
msg:
.ascii "HTTP/1.0 200 OK\r\n\r\n"
|
**for debugging: **
1
2
3
4
5
6
|
#in the first terminal:
strace -f(child process) ./server
#in the second terminal:
nc 127.0.0.1 80
GET /tmp/abc
POST /tmp/abc ...
|