Cyber Security & Data Protection
30 March, 2020 — Go back to homepage
Weevely is a versatile tool written in Python which serves as a web shell during the post-explotation phase. As an open-source project, it is available in GitHub – where weevely3 is the latest version being maintained – and can be installed through the APT Package Manager on Linux as well. It has powerful features by leveraging more than 30 modules to assist administrative tasks, maintain access, provide situational awareness, elevate privileges, and spread into the target network.
Considering its small and polymorphic nature, it is hardly detected by AV vendors and blue team operators might find its encrypted communication chain difficult to revert. Therefore, this article will examine traffic network this script generates in order to facilitate DFIR.
You can install Weevely by either cloning the GitHub repository or installing the Debian-based package through APT. We are going to use the latest and maintained version which is Weevely3.
For demonstration purposes, I will generate “artikrh.php” with a password of “artikrh”, which is then uploaded to the target web server and accessed through CLI:
For this section, let’s suppose we are performing digital forensics and incident response. We can use Wireshark to examine the network traffic by using the http.request.uri contains "artikrh.php"
filter to identify relevant packets to the suspicious PHP file:
Following the HTTP stream of these packets show gibberish POST data which may imply encryption and/or encoding:
If we take a close look at it from a high-level perspective, we may notice some random unidentified bytes at the beginning (.XJ_Aaf.Yzk+W,so
) followed by what seems to be hexadecimal values (11a12a4a68f2
) and Base64-encoded content (Gqx5ev6t4ATRBAeG5NExNHoRMQo
). At the end of this data, we may notice additional hex numbers followed by again random data in a structure as seen below:
At this point, the only part that we can make sense of is the Base64-encoded part, but which results in possible encrypted data when decoded:
Considering that we already got a data skeleton in the bigger picture, we may turn back to the original script to further advance.
The content of the extracted “artikrh.php” file from the attacked web server is as follows:
Clearly, the PHP is obfuscated, however we can already see suspicious functions such as:
ob_stQart
=> ob_start()evQal
=> eval()gzuncoQmpress
=> gzuncompress()base6Q4_deQcode
=> base64_decode()As a side note, you can google parts of this code in Google which may lead you eventually to Weevely3-related artifacts, so you know what the attacker has used. Do keep in mind that the script is polymorphic, therefore, the function order and variable names will always change randomly during generation.
We can either deobfuscate it partially by manually executing the above PHP code in CLI without including the last $X
variable:
Or we can use the UnPHP online which fully deobfuscates and formats the backdoor PHP code:
We notice some pre-defined variables, three which are in hex format ($k
, $kh
, and $kf
) – a total number of 32 characters together, which might indicate MD5 hashing – and an unknown $p
variable which seems to hold Base64-encoded data (decoding it outputs gibberish). Furthermore, there is a x()
function which seems to XOR encrypt input data $t
with input key $k
(we already know $k
).
On the other hand, there is the last section which seems to do all the action of communicating back and forth. We start breaking this part down by analyzing the following line of code:
This means that if HTTP request POST data contains $kh
(the initial 11a12a4a68f2
hex data we identified earlier) and $kf
(the enclosing 120bb3b9572c
) strings in it, store the match(es) in array $m
(as described in PHP’s file_get_contents()
built-in function). In this case, the array will be comprised of two items:
$m[0]
=> the whole match: $kh
(12 chars) + Base64 data + $kf
(12 chars)$m[1]
=> match in between static delimiters ($kh
, $kf
): Base64 dataIt is worth noting that the regular expression excludes the unidentified random data at both ends of POST request string, so the initial diagram is transformed into:
The eval()
bit is comprised in the following manner:
$m[1]
(data) is Base64 decoded$k
(in this case: b0270e74
)eval()
Output buffer (OB) functions are used to retrieve executed commands’ result:
ob_start()
: Turns on output bufferingob_get_contents()
: Standard out from the execution of arbitrary PHP code from eval()
ob_end_clean()
: Cleans the output buffer and turns off output bufferingSince we now have a clear idea on what the script does, we can decrypt traffic by backtracking. In summary, we can retrieve the first packet data in plaintext by storing it into a variable (in this case $phpinput
) and printing the raw PHP code instead of sending it to eval()
:
Always take care of special characters such as the dollar sign in this input. Executing the above PHP code outputs:
This might seem confusing at first but it makes sense, the tool makes a web-based check to ensure a proper connection end-point. If you decrypt the second packet, you get another validation check:
But this time, it ensures that is has necessary privileges to execute system commands on the victim server. Moreover, there are three more default packets which retrieve:
In short, the five above requests are always sent immediately after initiating a backdoor communication channel. Decrypting the sixth request actually gives us the commander that the attacker wrote:
Functions such as chdir()
, error_reporting()
, and system()
are always used; meanwhile the 2>&1
idiom is automatically appended after each user-entered command.
I refined a script to decrypt content given as an input parameter to the script in CLI:
Usage:
While generating a new backdoor creates different assembling variable names and function orders, one detail that I have noticed is that the core deobfuscated core code remains the same. Its artifacts such as MD5 hash components ($k
, $kh
, and $kf
) never change names or formats, only values; all of their static presence, including a str_replace()
function as per its Python logic, denote that the script is a Weevely backdoor: