SLAE: Assignment 1 of 7

Assignment #1 objectives:
- Create a shell_bind_tcp assembly shellcode which:
- binds to a port
- execs shell on incoming connection
- The port number should be easily configurable (for instance via marked byte in shellcode, or a wrapper)
==========================================
For this assignment, I decided to not return to the videos to see how the original bind shell was done by Vivek.I remember that Vivek used a syscall to create a socket, and then fed /bin/sh to it. I will use the same approach.
Note: All commands are run on Ubuntu 12.04.5 32-bit

Note: First, I list all the syscalls in Linux:

Note, there are so many calls, it is easier to grep for socket:

#define __NR_socketcall        102
Note: This is a decimal number since the number is not prepended with 0x

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.

SEE ALSO
accept(2),  bind(2),  connect(2),  getpeername(2), getsockname(2), get-
sockopt(2),  listen(2),  recv(2),  recvfrom(2),  recvmsg(2),   send(2),
sendmsg(2),  sendto(2),  setsockopt(2), shutdown(2), socket(2), socket-
pair(2)

Note: socketcall() isn't just a simple syscall; it's a syscall chain. Time for research.

An IP socket is created by calling the socket(2) function as socket(AF_INET, socket_type, protocol).   Valid  socket types are SOCK_STREAM to open a tcp(7) socket, SOCK_DGRAM to open a udp(7) socket, or SOCK_RAW to open a raw(7) socket to access the IP protocol directly.  protocol is the IP  protocol  in the IP header to be received or sent.  The only valid values for protocol are 0 and IPPROTO_TCP for TCP sockets, and 0 and IPPROTO_UDP for UDP sockets.  For  SOCK_RAW  you  may specify a valid IANA IP protocol defined in RFC 1700 assigned numbers.
When  a process wants to receive new incoming packets or connections, it should bind a socket to a local interface address using bind(2).  Only one IP socket may be bound  to  any  given  local (address,  port)  pair.  When INADDR_ANY is specified in the bind call, the socket will be bound to all local interfaces.  When listen(2) or connect(2) are called on an unbound  socket,  it  is automatically bound to a random free port with the local address set to INADDR_ANY.

Note: So in summary, these are the steps:
- create socket
- bind the socket to an IP and port
- Listen for incoming connections
- Accept incoming connections
- Pass /bin/sh to new client socket using execve

Note: The following command shows the call numbers

#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)        */
#define SYS_GETSOCKNAME    6        /* sys_getsockname(2)        */
#define SYS_GETPEERNAME    7        /* sys_getpeername(2)        */
#define SYS_SOCKETPAIR    8        /* sys_socketpair(2)        */
#define SYS_SEND    9        /* sys_send(2)            */
#define SYS_RECV    10        /* sys_recv(2)            */
#define SYS_SENDTO    11        /* sys_sendto(2)        */
#define SYS_RECVFROM    12        /* sys_recvfrom(2)        */
#define SYS_SHUTDOWN    13        /* sys_shutdown(2)        */
#define SYS_SETSOCKOPT    14        /* sys_setsockopt(2)        */
#define SYS_GETSOCKOPT    15        /* sys_getsockopt(2)        */
#define SYS_SENDMSG    16        /* sys_sendmsg(2)        */
#define SYS_RECVMSG    17        /* sys_recvmsg(2)        */
#define SYS_ACCEPT4    18        /* sys_accept4(2)        */
#define SYS_RECVMMSG    19        /* sys_recvmmsg(2)        */
#define SYS_SENDMMSG    20        /* sys_sendmmsg(2)        */

Note: The following command confirms the TCP protocol number (0)

IPPROTO_IP = 0,           /* Dummy protocol for TCP.  */
IPPROTO_HOPOPTS = 0,   /* IPv6 Hop-by-Hop options.  */
IPPROTO_ICMP = 1,       /* Internet Control Message Protocol.  */
IPPROTO_IGMP = 2,       /* Internet Group Management Protocol. */
IPPROTO_IPIP = 4,       /* IPIP tunnels (older KA9Q tunnels use 94).  */
IPPROTO_TCP = 6,       /* Transmission Control Protocol.  */
IPPROTO_EGP = 8,       /* Exterior Gateway Protocol.  */
IPPROTO_PUP = 12,       /* PUP protocol.  */
IPPROTO_UDP = 17,       /* User Datagram Protocol.  */
IPPROTO_IDP = 22,       /* XNS IDP protocol.  */
IPPROTO_TP = 29,       /* SO Transport Protocol Class 4.  */
IPPROTO_DCCP = 33,       /* Datagram Congestion Control Protocol.  */
IPPROTO_IPV6 = 41,     /* IPv6 header.  */
IPPROTO_ROUTING = 43,  /* IPv6 routing header.  */

Note: Reading through the man pages, I understood how to set up a socket. However, in 'man 2 bind', it states "addrlen  specifies  the  size, in  bytes, of the address structure pointed to by addr". In my case, the address structure was 8 bytes in length. When I set addrlen to 8 bytes, the code worked and the socket was created but the socket was bound to a random number.

Note: The following file provides a hint as to why the addrlen needs to be 16:

/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__    16        /* sizeof(struct sockaddr)    */
struct sockaddr_in {
__kernel_sa_family_t    sin_family;    /* Address family        */
__be16        sin_port;    /* Port number            */
struct in_addr    sin_addr;    /* Internet address        */

/* Pad to size of `struct sockaddr'. */
unsigned char        __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
#define sin_zero    __pad        /* for BSD UNIX comp. -FvK    */

Note: It seems that Linux pads sockaddr_in to the size of sockaddr:

/* Structure describing a generic socket address.  */
struct sockaddr
{
__SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
char sa_data[14];            /* Address data.  */
};

Note: sockaddr has a size of address family (1 byte) + length (1 byte) + address data (14 bytes) = 16 bytes. So even though our sockaddr_in only uses 8 bytes, its size is cast to 16. So 'addrlen' should be 16 and not 8.

The pseudo-code for a bind-shell looks like this:
sfd = socketcall.socket(int domain, int type, int protocol);
sockaddr = (2, 5555, 0.0.0.0)
socketcall.bind(sfd, pointer to sockaddr,16);
socketcall.listen(int sockfd, int backlog)
cfd = socketcall.accept(sfd, pointer to client-sockaddr or 0, sizeof(client-sockaddr) or 0);
dup2(cfd, 0); duplicate stdin to client socket
dup2(cfd, 1); duplicate stdout to client socket
dup2(cfd, 2); duplicate stderr to client socket
execve(/bin/sh,0,0) ; start /bin/sh with input/output duplicated into the socket

And here is the assembly code I came up with - It is not optimized:

Note: We compile the assembly and run it in gdb

gdb$ run
gdb$ continue
Note: In another terminal, connect to 127.0.0.1 on port 5555:

The assembly code works! Press CTRL-C in the window you used to connect to close the connection, then close gdb:
gdb$ quit

Note: We're not there yet: Null bytes (\x00) are typically shunned from shellcode because many programs use a null byte to terminate strings. If we were to inject this shellcode in a function that used a string, the first null byte in our shellcode would act as a string terminator and only part of our shellcode would be loaded.

I removed the zeroes from the shellcode as follows:

Note: We compile and test our null-free shellcode:

Note: Again we verify we can connect successfully:

The assembly code still works. Press CTRL-C in the window you used to connect to close the connection.

I optimized the code above by removing unneeded pieces of code and reusing stack values:

Note: To get the shellcode of the program, I use some command-line-fu:

This small piece of C code will load the shellcode for us:

We compile the shellcode loader as follows:

We test the shellcode works:

Note: The bindshell.c program dynamically sets the port number to port 1234 via the following instructions:
int port = 1234;
if (!shellcode_setport(shellcode, port)) {printf("ERROR: Invalid port\n");return 0;}
Note: Again we verify we can connect successfully:

The code works - Press CTRL-C in the window you used to connect to close the connection.

 

Filed under: Exclude from front page SLAE