This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-1294
This first assignment is to create an original x86 Linux shellcode for a TCP bind shell. When invoked it will use Linux system calls to accept a TCP connection and provide an interactive /bin/sh shell. The shellcode can accomplish this by making the following calls in order:
socket()- Obtain an AF_INET socket
bind()- Bind to a particular TCP port - in this case we will bind to all available interfaces (IP addresses)
listen()- Indicate that we want to receive connections on the bound port
accept()- Wait for somebody to connect
dup2()x3 - Replace stdin, stdout and stderr with the new connection’s file descriptor (fd) so input and output goes over the network
execve()- Execute /bin/sh in place of the current code, inheriting the open file descriptors
The full source code is available on GitHub. In this post I will discuss each step separately.
Obtaining a socket
The socket() syscall lets us create an AF_INET socket. This will be returned to us as a file descriptor, an integer such as 3, depending on how many files are currently open. The manpage defines it like this:
The manpage ip(7) further explains that a TCP socket should be created with these parameters:
I require the actual values of the AF_INET and SOCK_STREAM constants. With a bit of searching the constants can be located in the include files on my Kali Linux VM:
root@kali:/usr/include/i386-linux-gnu# egrep -ir "AF_INET|PF_INET|SOCK_STREAM"
bits/socket_type.h: SOCK_STREAM = 1, /* Sequenced, reliable, connection-based
bits/socket_type.h:#define SOCK_STREAM SOCK_STREAM
bits/socket.h:#define PF_INET 2 /* IP protocol family. */
bits/socket.h:#define PF_INET6 10 /* IP version 6. */
bits/socket.h:#define AF_INET PF_INET
bits/socket.h:#define AF_INET6 PF_INET6
So AF_INET is the integer 2 and SOCK_STREAM is the integer 1.
I also need to know the syscall numbers. On my system accept4 is defined and accept is not—it is the same syscall, just with an extra parameter.
root@kali:/usr/include/i386-linux-gnu/asm# egrep "_socket |_accept |_bind |_listen |_accept4 |_dup2 |_execve " unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socket 359
#define __NR_bind 361
#define __NR_listen 363
#define __NR_accept4 364
With all these numbers on hand, I can define some appropriate constants for this first step.
This is the entry point of the shellcode so I don’t know what values already exist in the registers EAX, EBX, etc. I will set them to zero first using XOR instructions. EAX must contain the syscall number so I move SYS_SOCKET to AX, overwriting the bottom word of the register with the number.
The three integer parameters must be placed in EBX, ECX and EDX. AF_INET and SOCK_STREAM are <256 so they are assigned to BL and CL. EDX needs to be zero so it is XORed. These word and byte-sized registers are used to avoid creating null bytes in the resulting shellcode.
int 0x80 will trigger Linux’s syscall handler, which will create the socket, assign it a file descriptor and return that in EAX. Since EAX will be overwritten many times for other syscalls I copy the fd to EDI for safekeeping.
Binding to an address/port
The newly created socket can be used to bind to a port. The bind() syscall is defined as follows:
Its parameters are the fd, a pointer to a sockaddr structure, and the size in memory of that structure. The structure is also defined in the bind manpage:
What type exactly is
sa_family_t? I located its definition in /usr/include/i386-linux-gnu/bits/sockaddr.h:
A short int is 2 bytes in size. This means the total size of a struct sockaddr will be 16 bytes on this system.
How should the data be formatted? A more useful definition comes from the manpage ip(7).
So for this kind of socket, the
sa_data will contain a port followed by an IP address. What is
in_port_t? I located it in /usr/include/netinet/in.h:
Putting it all together, the 16 byte struct sockaddr will look like this in memory:
^ lower addresses
0: AF_INET, least significant byte (system is little endian)
1: AF_INET, most significant byte
2: port, MSB (network byte order = big endian)
3: port, LSB
4: IP address, MSB
5: IP address
6: IP address
7: IP address, LSB (all zeroes)
8-15: zero padding
v higher addresses
For the IP address I will specify 0.0.0.0, which binds to all available interfaces. This is probably what we want for a shellcode. I want the port to be user configurable. I will define a constant LISTEN_PORT at compile time. The assembly will assume that it is a 16-bit number that has already been flipped to network byte order.
First, push the struct sockaddr onto the stack in reverse order. ESP will end up pointing at the beginning of the structure.
Now I can set up the parameters for the bind syscall and run the syscall handler. EDI still contains the FD from the previous call to socket(). The stack pointer is currently pointing to our structure, so copy that into ECX. We know the size is 16 bytes so place that in DL directly.
Listening on the socket
This will cause Linux to start accepting TCP connections on this port. It is defined:
The first parameter is the same socket fd from earlier, stored in EDI. Since we will only ever handle one connection the backlog can be safely set to zero.
listen() will return 0 on success, so eax should be cleared.
Accepting a connection
The definition of the accept4() system call on my machine is:
sockfd is the listening socket, which we have stored in EDI. addr/addrlen specify a struct that will be filled in with the remote address when somebody connects. Since we don’t need that information they may both be set to NULL. flags can also be 0.
The return value will be a new file descriptor for the client socket. This is the one that’s important for the bind shell. Since we are only trying to accept a single connection, we will overwrite the listening socket in EDI with this new value.
Redirecting stdin, stdout, stderr
When we exec the shell it will assume that fds 0, 1 and 2 are stdin, stdout and stderr respectively. If we replace all of those fds with our new TCP connection, the shell’s input and output will be forwarded to the remote user. This can be done with the dup2 syscall—it will duplicate an fd to the number of our choosing, replacing anything that was already there.
oldfd came from the accept() call and is now stored in edi. We will call this three times, using 0, 1 and 2 as values for newfd.
The final step is to execute the shell. We will use the execve syscall for this:
filename must be a null-terminated path to the program to launch. This will be set to “/bin//sh” with the double slash because a length divisible by four makes it easy to push on the stack.
argv is a NULL-terminated array of pointers to command line arguments. The first argument should normally always be the program’s name. So this will be a pointer to 8 bytes of memory - the first 4 bytes are a pointer to “/bin//sh” and the second 4 bytes are NULL.
envp provides environment variables. We don’t need to add any so we can pass a pointer to NULL. For convenience we will point to the same NULL that is the second half of argp.
EDX is already known to contain 0x0 from when it was used as a parameter to accept(). Set up the stack in reverse order:
PTR to "/bin//sh" <-- esp points here after everything pushed
NULL <-- esp + 4
The shellcode is built into raw opcodes and a test C program, using a build script that includes the desired listening port. See the GitHub repo for details.
It compiles and builds:
After running the test wrapper we can see that it is listening on port 4444:
Now a remote user can connect and use the shell.
Once the remote user exits the shell, the shellcode ends and the wrapper terminates.