HTB Business CTF 2021: [Forensic] Compromised

Kevin K
7 min readAug 14, 2021

Challenge info:
We are certain that our internal network has been breached and the attacker tries to move laterally. We managed to capture some suspicious traffic and create a memory dump from a compromised server. I hope you are skilled enough to bring this incident to its end.

We are given two files — a PCAP and a memory dump file.

PCAP

Wireshark shows us that the PCAP contains SSH traffic between several hosts with one of the streams involving OpenSSH 7.6.

Protocol Hierarchy of capture.pcap
Conversations of capture.pcap

We can see that the SSH communication was established using a SSH-2.0-OpenSSH_7.6 protocol and software version.

SSH and software version exchange

We also see the encryption and key exchange algorithm used were aes256-gcm and curve25519-sha256 respectively.

KEX Init

Other than the initial key exchange information, there’s nothing much else to gather from the PCAP. Let’s move over to the memory dump file for now.

Volatility Profile

Volatility is a well known open-source memory forensics framework, and would be an excellent tool to analyze our memory dump.

Before we can perform the analysis, we need to provide Volatility a profile of the target system to ensure its tools know how to locate and parse the system-specific kernel data structures of the dump.

Lucky for us, the Volatility wiki provides a handy guide that covers the necessary procedure.

We’ll first have to determine the OS and kernel version of the dumped system, and if we already have a pre-built profile for that.

OS and kernel version from the dump

Unfortunately, we don’t see any pre-built profile matching an Ubuntu 18.04.5 version with a 4.15.0–142-generic kernel.

As pointed out by the wiki page, the alternative way is to replicate the target environment and build our own profile from it.

Download the Ubuntu 18.04.5 ISO image from the releases page and complete the installation.

Once installed, check its system information. We’ll have to upgrade the kernel version from -122 to -142 to match our target environment.

uname output of a vanilla Ubuntu 18.04.5

The pkgs site reveals to us the package name to install our desired kernel version — linux-image-4.15.0–142-generic.

Install the package and reboot the system, and you’ll see that our Ubuntu machine matches our target system version.

uname output of our patched Ubuntu 18.04.5

Finally, follow Andrea Fortuna’s excellent guide to generate a Volatility profile from our Ubuntu machine, and transfer over to our analysis machine.

Verify that Volatility can locate our target profile, and we are game!

Volatility with our target profile stored

Memory dump analysis

First things first, let’s gather some basic information about our target system.

linux_ifconfig plugin output
linux_netstat plugin output excluding UNIX sockets

So this dump belonged to a host on 192.168.1.11 and the host was acting as a SSH server for a 192.168.1.10 host. This matches one of the SSH conversations captured in our earlier PCAP file.

Process-wise, the suppoie process of PID 1728 spawned from sshd stands out. Based on its name, the supplied config file, and the IP the process calls out to, the process could potentially be attributed to a Monero miner malware.

linux_pstree plugin output
linux_psaux plugin output

These are all interesting, but none of these findings seem to point us anywhere closer to our flag.

Given the fact that we are given a memory dump of a SSH server serving one of the SSH conversations captured in the PCAP file, let’s see if we can extract anything useful from this dump to decrypt that traffic.

SSH Key Extraction

Google search revealed a blog post from NCC Group pointing to a Volatility community plugin that extracts SSH session keys from a memory dump. Excellent!

The plugin successfully extracts two session keys from the dump.

Extracted SSH session keys

With the keys in our hands, let’s now decrypt the SSH traffic in our PCAP file. Conveniently, the author of the earlier blog post also provides a tool to do exactly this.

SSH Decryption

Unfortunately, the tool required a non-trivial amount of effort to set it up. Here are the steps I took to finally get it to working.

Environment Setup

Let’s create a virtual environment before installing any packages this tool may require:

python2 -m virtualenv venv
source venv/bin/activate

Dependency Install

From the root of the tool repository, set up the tool and install any dependency the tool requires:

setup.py error #1

The install fails with the message that it can’t download the pynids@ module.

I’m not too well-versed with Python’s setuptools, but the pynids line may not be written in the way setuptools expects to be.

setup.py output

I followed the python-packaging documentation to move the pynids URL into a dependency_links key, but that led me to another error:

setup.py error #2

From the cryptography documentation, this occurs because building the module requires having a Rust toolchain, and that upgrading pip to the latest version should resolve it. Unfortunately, this problem consisted even after my pip upgrade on my python 2.7 environment. I could potentially have dug this path deeper, but I decided to cheat and install the required modules manually.

pip install dissect.cstruct==1.0.0 psutil tabulate gevent libnacl cryptography

As for pynids, clone its repository, untar the libnids-1.25 archive in the root folder, and follow the instructions to build it.

If everything goes well, you should see a nidsmodule.so under the build/ folder of the pynids root folder. Copy the library file to your venv packages folder. For me, it was venv/lib/python2.7/site-packages/.

Finally, run the tool’s setup.py install one last time. It still errors out but should work for our purposes.

Python patching

With all the dependencies ironed out, let’s format the earlier extracted keys into the expected keys.json file, and run the tool.

keys.json output
network_parser.py error #1

Interestingly, we see from the traceback a syntax error on the usage of af-strings, a Python3 construct, from thedissect.cstruct module. This error makes sense because we are running the tool from a Python2 environment and the Python interpreter would not be able to understand any Python3 feature used in the tool’s dependency module.

I ended up patching all instances of the f-strings into a Python2 compatible formatting construct, like format().

# Python3
code_object = compile(
source,
f'<compiled {structure_name}>',
'exec',
)
# Python2
code_object = compile(
source,
'<compiled {}>'.format(structure_name),
'exec',
)

dissect.cstruct module uses another Python3 construct — the “super() method with no arguments”.

Fix all instances of this feature in the following manner too:

# Python3class PackedType(RawType):
"""Implements a packed type that uses Python struct packing characters."""
def __init__(self, cstruct, name, size, packchar):
super().__init__(cstruct, name, size)
self.packchar = packchar
# Python 2class PackedType(RawType):
"""Implements a packed type that uses Python struct packing characters."""
def __init__(self, cstruct, name, size, packchar):
super(PackedType, self).__init__(cstruct, name, size)
self.packchar = packchar

Once they are all fixed, let’s run the tool again.

network_parser.py error #2

Ok, the tool still errors out, but it doesn’t look like a dependency issue anymore. We are making progress here.

Traceback tells us cstruct.py encountered an unexpected token type string, which was invoked from structdef.py at line 243.

It turns out the tools’ structdef.py had a typo in the C snippet.

OpenSSH-Network-Parser/openssh_network_parser/protocols/ssh/structdef.py

The tool also had issues parsing the keys.json file. I fixed the way the tool parses the json lines in the following manner:

OpenSSH-Network-Parser/openssh_network_parser/protocols/ssh/protocol.py

Giving the tool another run, we finally begin to see it returning the key exchange metadata, but it fails to parse the session keys from the json file.

network_parser.py error #3

It looks like the tool expects to parse the keys.json line as a dictionary, but it currently is not (but rather as a string, probably). I added the following line to allow the key value to be interpreted as a dictionary.

OpenSSH-Network-Parser/openssh_network_parser/protocols/ssh/state.py

Running the tool 🙏

We have finally tamed the tool to return us the decrypted SSH traffic!

network_parser.py output

We see the first half of the flag in the Password field. The second half is base64 encoded in the command history.

Decoding the base64 string

Combining the two, we have the flag:

HTB{w3ll_1_th0ught_n0_0n3_w0uld_f1nd_m3!!!}

--

--